diff --git a/src/bonsai/bonsai/bim/module/geometry/decorator.py b/src/bonsai/bonsai/bim/module/geometry/decorator.py index d9e8505437..7e618c9546 100644 --- a/src/bonsai/bonsai/bim/module/geometry/decorator.py +++ b/src/bonsai/bonsai/bim/module/geometry/decorator.py @@ -36,15 +36,18 @@ class ItemDecorator: if cls.is_installed: cls.uninstall() obj_is_selected = {} + obj_is_boolean = {} objs = {} for item_obj in context.scene.BIMGeometryProperties.item_objs: if obj := item_obj.obj: objs[obj.name] = cls.get_obj_data(obj) obj_is_selected[obj.name] = obj.select_get() + obj_is_boolean[obj.name] = item_obj.is_boolean handler = cls() handler.objs = objs handler.obj_is_selected = obj_is_selected + handler.obj_is_boolean = obj_is_boolean cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_text, (context,), "WINDOW", "POST_PIXEL")) cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw, (context,), "WINDOW", "POST_VIEW")) cls.is_installed = True @@ -138,7 +141,7 @@ class ItemDecorator: self.line_shader.bind() # required to be able to change uniforms of the shader # POLYLINE_UNIFORM_COLOR specific uniforms self.line_shader.uniform_float("viewportSize", (context.region.width, context.region.height)) - self.line_shader.uniform_float("lineWidth", 1.0) + self.line_shader.uniform_float("lineWidth", 2.0) # general shader self.shader = gpu.shader.from_builtin("UNIFORM_COLOR") @@ -154,6 +157,9 @@ class ItemDecorator: continue self.draw_batch("LINES", data["verts"], selected_elements_color, data["edges"]) self.draw_batch("TRIS", data["verts"], transparent_color(selected_elements_color), data["tris"]) - else: - self.draw_batch("LINES", data["verts"], decorator_color_background, data["edges"]) + elif self.obj_is_boolean[obj_name]: + self.draw_batch("LINES", data["verts"], special_elements_color, data["edges"]) + self.draw_batch("TRIS", data["verts"], transparent_color(special_elements_color), data["tris"]) + else: + self.draw_batch("LINES", data["verts"], transparent_color(unselected_elements_color, alpha=0.2), data["edges"]) self.draw_batch("TRIS", data["verts"], transparent_color(special_elements_color), data["tris"]) diff --git a/src/bonsai/bonsai/bim/module/geometry/operator.py b/src/bonsai/bonsai/bim/module/geometry/operator.py index 19e15a2f01..7799924c14 100644 --- a/src/bonsai/bonsai/bim/module/geometry/operator.py +++ b/src/bonsai/bonsai/bim/module/geometry/operator.py @@ -2694,8 +2694,19 @@ class ImportRepresentationItems(bpy.types.Operator, tool.Ifc.Operator): data = obj.data assert data item_ids = data["ios_item_ids"] if "ios_item_ids" in data else data["ios_edges_item_ids"] - for item_id in set(item_ids): + queue = list(set(item_ids)) + processed_ids = set() + boolean_ids = set() + while queue: + item_id = queue.pop() + if item_id in processed_ids: + continue # In theory, an item can be used multiple times (e.g. in a boolean stack). item = tool.Ifc.get().by_id(item_id) + if item.is_a("IfcBooleanResult"): + queue.append(item.FirstOperand.id()) + queue.append(item.SecondOperand.id()) + boolean_ids.add(item.SecondOperand.id()) + continue item_mesh = bpy.data.meshes.new(f"Item/{item.is_a()}/{item_id}") item_mesh.BIMMeshProperties.ifc_definition_id = item_id @@ -2705,6 +2716,7 @@ class ImportRepresentationItems(bpy.types.Operator, tool.Ifc.Operator): new = props.item_objs.add() new.obj = item_obj + new.is_boolean = item_id in boolean_ids tool.Geometry.import_item(item_obj) tool.Geometry.import_item_attributes(item_obj) @@ -2712,11 +2724,9 @@ class ImportRepresentationItems(bpy.types.Operator, tool.Ifc.Operator): if not tool.Geometry.is_movable(item): tool.Geometry.lock_object(item_obj) - if "IfcOpeningElement" in obj.name: - item_obj.display_type = "WIRE" - item_obj.select_set(True) # so you can quickly hit tab again, for edit mode context.view_layer.objects.active = item_obj + processed_ids.add(item_id) tool.Root.reload_item_decorator() diff --git a/src/bonsai/bonsai/bim/module/geometry/prop.py b/src/bonsai/bonsai/bim/module/geometry/prop.py index bae2cefa1d..bc45a7f4e0 100644 --- a/src/bonsai/bonsai/bim/module/geometry/prop.py +++ b/src/bonsai/bonsai/bim/module/geometry/prop.py @@ -131,10 +131,7 @@ class RepresentationItemObject(PropertyGroup): name: StringProperty(name="Name") ifc_definition_id: IntProperty(name="IFC Definition ID") obj: PointerProperty(type=bpy.types.Object) - verts: StringProperty(name="Verts") - edges: StringProperty(name="Edges") - special_verts: StringProperty(name="Special Verts") - special_edges: StringProperty(name="Special Edges") + is_boolean: BoolProperty(name="Is Boolean", default=False) class ShapeAspect(PropertyGroup): diff --git a/src/bonsai/bonsai/tool/geometry.py b/src/bonsai/bonsai/tool/geometry.py index c0faa6942a..06ccd346ed 100644 --- a/src/bonsai/bonsai/tool/geometry.py +++ b/src/bonsai/bonsai/tool/geometry.py @@ -952,7 +952,7 @@ class Geometry(bonsai.core.tool.Geometry): @classmethod def is_movable(cls, item: ifcopenshell.entity_instance) -> bool: - return item.is_a("IfcSweptAreaSolid") + return item.is_a("IfcSweptAreaSolid") or item.is_a("IfcHalfSpaceSolid") @classmethod def is_profile_based(cls, data: bpy.types.Mesh) -> bool: @@ -1571,12 +1571,10 @@ class Geometry(bonsai.core.tool.Geometry): has_changed = False for item_obj in props.item_objs: - if not (obj := item_obj.obj): + if not (obj := item_obj.obj) or not tool.Ifc.is_moved(obj): continue item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) if is_swept_area := item.is_a("IfcSweptAreaSolid"): - if not tool.Ifc.is_moved(obj): - continue has_changed = True old_position = item.Position @@ -1591,6 +1589,25 @@ class Geometry(bonsai.core.tool.Geometry): item.Position = builder.create_axis2_placement_3d_from_matrix(position) if old_position: ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), old_position) + if item.is_a("IfcHalfSpaceSolid"): + has_changed = True + surface = item.BaseSurface + if surface.is_a("IfcPlane"): + position = surface.Position + m = Matrix(ifcopenshell.util.placement.get_axis2placement(position).tolist()) + m.translation *= unit_scale + + new_m = rep_obj.matrix_world.inverted() @ obj.matrix_world + new_m.normalize() + new_m.translation /= unit_scale + new_m = np.array(new_m) + surface.Position = builder.create_axis2_placement_3d_from_matrix(new_m) + ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), position) + else: + self.report( + {"INFO"}, + f"Editing boolean using non-IfcPlane IfcSurface ({surface.is_a()}) is not supported.", + ) if has_changed: cls.reload_representation(rep_obj) @@ -1631,21 +1648,36 @@ class Geometry(bonsai.core.tool.Geometry): item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) obj.data.clear_geometry() - geometry = tool.Loader.create_generic_shape(item) - if (cartesian_point_offset := cls.get_cartesian_point_offset(rep_obj)) is not None: - verts_array = np.array(geometry.verts) - offset = np.array([-cartesian_point_offset[0], -cartesian_point_offset[1], -cartesian_point_offset[2]]) - offset_verts = verts_array + np.tile(offset, len(verts_array) // 3) - verts = offset_verts.tolist() + if item.is_a("IfcHalfSpaceSolid"): + bm = bmesh.new() + bmesh.ops.create_grid(bm, size=0.5) + bm.verts.ensure_lookup_table() + bm.edges.ensure_lookup_table() + bm.faces.ensure_lookup_table() + bm.to_mesh(obj.data) + bm.free() + + unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) + position = item.BaseSurface.Position + position = Matrix(ifcopenshell.util.placement.get_axis2placement(position).tolist()) + position.translation *= unit_scale + obj.matrix_world = rep_obj.matrix_world @ position else: - verts = geometry.verts - tool.Loader.convert_geometry_to_mesh(geometry, obj.data, verts=verts) + geometry = tool.Loader.create_generic_shape(item) + if (cartesian_point_offset := cls.get_cartesian_point_offset(rep_obj)) is not None: + verts_array = np.array(geometry.verts) + offset = np.array([-cartesian_point_offset[0], -cartesian_point_offset[1], -cartesian_point_offset[2]]) + offset_verts = verts_array + np.tile(offset, len(verts_array) // 3) + verts = offset_verts.tolist() + else: + verts = geometry.verts + tool.Loader.convert_geometry_to_mesh(geometry, obj.data, verts=verts) - if ios_materials := list(obj.data["ios_materials"]): - material = tool.Ifc.get_object(tool.Ifc.get().by_id(ios_materials[0])) - obj.data.materials.append(material) + if ios_materials := list(obj.data["ios_materials"]): + material = tool.Ifc.get_object(tool.Ifc.get().by_id(ios_materials[0])) + obj.data.materials.append(material) - obj.matrix_world = rep_obj.matrix_world.copy() + obj.matrix_world = rep_obj.matrix_world.copy() if is_swept_area := item.is_a("IfcSweptAreaSolid"): position = item.Position