diff --git a/src/bonsai/bonsai/bim/module/geometry/__init__.py b/src/bonsai/bonsai/bim/module/geometry/__init__.py index b7a6676991..6f43679375 100644 --- a/src/bonsai/bonsai/bim/module/geometry/__init__.py +++ b/src/bonsai/bonsai/bim/module/geometry/__init__.py @@ -22,6 +22,7 @@ from bpy.app.handlers import persistent classes = ( operator.AddCurvelikeItem, + operator.AddHalfSpaceSolidItem, operator.AddMeshlikeItem, operator.AddRepresentation, operator.AddSweptAreaSolidItem, diff --git a/src/bonsai/bonsai/bim/module/geometry/decorator.py b/src/bonsai/bonsai/bim/module/geometry/decorator.py index c397c37115..5d2efe5e83 100644 --- a/src/bonsai/bonsai/bim/module/geometry/decorator.py +++ b/src/bonsai/bonsai/bim/module/geometry/decorator.py @@ -57,6 +57,8 @@ class ItemDecorator: verts = [] edges = [] tris = [] + special_verts = [] + special_edges = [] if len(obj.data.loop_triangles) > 0: verts = [tuple(obj.matrix_world @ v.co) for v in obj.data.vertices] @@ -81,7 +83,26 @@ class ItemDecorator: ] edges = bbox_edges verts.extend(bbox_verts) - return {"verts": verts, "edges": edges, "tris": tris} + + if "HalfSpaceSolid" in obj.name: + # Arrow shape + special_verts = [ + tuple(obj.matrix_world @ Vector((0, 0, 0))), + tuple(obj.matrix_world @ Vector((0, 0, 0.5))), + tuple(obj.matrix_world @ Vector((0.05, 0, 0.45))), + tuple(obj.matrix_world @ Vector((-0.05, 0, 0.45))), + tuple(obj.matrix_world @ Vector((0, 0.05, 0.45))), + tuple(obj.matrix_world @ Vector((0, -0.05, 0.45))), + ] + special_edges = [(0, 1), (1, 2), (1, 3), (1, 4), (1, 5)] + + return { + "verts": verts, + "edges": edges, + "tris": tris, + "special_verts": special_verts, + "special_edges": special_edges, + } @classmethod def uninstall(cls): @@ -157,11 +178,14 @@ 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"]) + self.draw_batch("LINES", data["special_verts"], selected_elements_color, data["special_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"]) + self.draw_batch("LINES", data["special_verts"], special_elements_color, data["special_edges"]) 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"]) + self.draw_batch("LINES", data["special_verts"], special_elements_color, data["special_edges"]) diff --git a/src/bonsai/bonsai/bim/module/geometry/operator.py b/src/bonsai/bonsai/bim/module/geometry/operator.py index 7799924c14..124f2a7ad1 100644 --- a/src/bonsai/bonsai/bim/module/geometry/operator.py +++ b/src/bonsai/bonsai/bim/module/geometry/operator.py @@ -2933,6 +2933,64 @@ class AddCurvelikeItem(bpy.types.Operator, tool.Ifc.Operator): tool.Geometry.import_item_attributes(obj) +class AddHalfSpaceSolidItem(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.add_half_space_solid_item" + bl_label = "Add Half Space Solid Item" + bl_options = {"REGISTER", "UNDO"} + + def _execute(self, context): + if ( + not tool.Blender.get_selected_objects() + or not (active_obj := tool.Blender.get_active_object()) + or not tool.Geometry.is_representation_item(active_obj) + ): + # In theory we can have half space solid as a top level item but let's not go there today. + self.report({"ERROR"}, "Select an item to apply the half space solid to.") + return {"CANCELLED"} + + props = context.scene.BIMGeometryProperties + mesh = bpy.data.meshes.new("Tmp") + obj = bpy.data.objects.new("Tmp", mesh) + scene = bpy.context.scene + scene.collection.objects.link(obj) + bpy.context.view_layer.objects.active = obj + obj.select_set(True) + new = props.item_objs.add() + new.obj = obj + + matrix = props.representation_obj.matrix_world.copy() + matrix.translation = context.scene.cursor.location + local_matrix = props.representation_obj.matrix_world.inverted() @ matrix + + obj.show_in_front = True + obj.matrix_world = matrix + tool.Geometry.record_object_position(obj) + + ifc_file = tool.Ifc.get() + unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file) + local_z = ifc_file.createIfcDirection((0.0, 0.0, 1.0)) + local_x = ifc_file.createIfcDirection((1.0, 0.0, 0.0)) + point = ifc_file.createIfcCartesianPoint((np.array(local_matrix.translation) / unit_scale).tolist()) + placement = ifc_file.createIfcAxis2Placement3D(point, local_z, local_x) + plane = ifc_file.createIfcPlane(placement) + item = ifc_file.createIfcHalfSpaceSolid(plane, AgreementFlag=False) + + representation = tool.Geometry.get_active_representation(props.representation_obj) + representation = ifcopenshell.util.representation.resolve_representation(representation) + + representation.Items = list(representation.Items) + [item] + tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj) + + obj.name = obj.data.name = f"Item/{item.is_a()}/{item.id()}" + obj.data.BIMMeshProperties.ifc_definition_id = item.id() + tool.Geometry.import_item(obj) + + # TODO refactor to core and not rely on selection + tool.Blender.select_and_activate_single_object(context, active_obj) + tool.Blender.select_object(obj) + bpy.ops.bim.add_boolean() + + class OverrideMoveAggregateMacro(bpy.types.Macro): bl_idname = "bim.override_move_aggregate_macro" bl_label = "IFC Move Aggregate" diff --git a/src/bonsai/bonsai/bim/module/model/workspace.py b/src/bonsai/bonsai/bim/module/model/workspace.py index 1b6b06a449..1be2e91176 100644 --- a/src/bonsai/bonsai/bim/module/model/workspace.py +++ b/src/bonsai/bonsai/bim/module/model/workspace.py @@ -309,6 +309,8 @@ class BIM_MT_add_representation_item(Menu): "ICOSPHERE" ) self.layout.operator("bim.add_meshlike_item", icon="MESH_CYLINDER", text="Mesh Cylinder").shape = "CYLINDER" + self.layout.separator() + self.layout.operator("bim.add_half_space_solid_item", icon="ORIENTATION_NORMAL", text="Half Space Solid") if ItemData.data["representation_type"] in ("SolidModel", "SweptSolid"): self.layout.operator( @@ -317,6 +319,8 @@ class BIM_MT_add_representation_item(Menu): self.layout.operator( "bim.add_swept_area_solid_item", icon="MESH_CYLINDER", text="Extruded Area Solid Cylinder" ).shape = "CYLINDER" + self.layout.separator() + self.layout.operator("bim.add_half_space_solid_item", icon="ORIENTATION_NORMAL", text="Half Space Solid") if ItemData.data["representation_type"] in ("Annotation2D"): self.layout.operator("bim.add_curvelike_item", icon="IPO_CONSTANT", text="Polycurve").shape = "LINE"