mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
Finally, a way to actually add an IFC element directly
Previously, the way to add an IFC element would be to 1) create a blender element then assign or 2) activate bim tool, launch type manager, click create type, fill out field, or 3) using various menus for parametric stuff like stairs. This is now the new proposed way to do things and everything will consolidate to here - and it allows you to directly create non-mesh geometry immediately too!
This commit is contained in:
@@ -90,6 +90,8 @@ class ViewportData:
|
|||||||
modes.append(item_mode)
|
modes.append(item_mode)
|
||||||
elif tool.Geometry.is_representation_item(obj):
|
elif tool.Geometry.is_representation_item(obj):
|
||||||
modes.append(edit_mode)
|
modes.append(edit_mode)
|
||||||
|
else: # A regular Blender object
|
||||||
|
modes.append(edit_mode)
|
||||||
|
|
||||||
return modes
|
return modes
|
||||||
|
|
||||||
|
|||||||
@@ -115,17 +115,13 @@ class ItemDecorator:
|
|||||||
continue
|
continue
|
||||||
edges = selected_edges
|
edges = selected_edges
|
||||||
verts = selected_verts
|
verts = selected_verts
|
||||||
offset = len(selected_verts)
|
tris = selected_tris
|
||||||
selected_verts.extend([tuple(obj.matrix_world @ v.co) for v in obj.data.vertices])
|
|
||||||
selected_tris.extend([tuple([i + offset for i in t.vertices]) for t in obj.data.loop_triangles])
|
|
||||||
else:
|
else:
|
||||||
offset = len(unselected_verts)
|
|
||||||
unselected_verts.extend([tuple(obj.matrix_world @ v.co) for v in obj.data.vertices])
|
|
||||||
unselected_tris.extend([tuple([i + offset for i in t.vertices]) for t in obj.data.loop_triangles])
|
|
||||||
edges = unselected_edges
|
edges = unselected_edges
|
||||||
verts = unselected_verts
|
verts = unselected_verts
|
||||||
i = len(verts)
|
tris = unselected_tris
|
||||||
|
|
||||||
|
i = len(verts)
|
||||||
matrix_world = obj.matrix_world
|
matrix_world = obj.matrix_world
|
||||||
bbox_verts = [matrix_world @ Vector(co) for co in obj.bound_box]
|
bbox_verts = [matrix_world @ Vector(co) for co in obj.bound_box]
|
||||||
bbox_edges = [
|
bbox_edges = [
|
||||||
@@ -145,6 +141,16 @@ class ItemDecorator:
|
|||||||
edges.extend(bbox_edges)
|
edges.extend(bbox_edges)
|
||||||
verts.extend(bbox_verts)
|
verts.extend(bbox_verts)
|
||||||
|
|
||||||
|
if (total_tris := len(obj.data.loop_triangles)) == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if total_tris > 1000: # For performance
|
||||||
|
continue
|
||||||
|
|
||||||
|
offset = len(verts)
|
||||||
|
verts.extend([tuple(obj.matrix_world @ v.co) for v in obj.data.vertices])
|
||||||
|
tris.extend([tuple([i + offset for i in t.vertices]) for t in obj.data.loop_triangles])
|
||||||
|
|
||||||
if unselected_verts:
|
if unselected_verts:
|
||||||
self.draw_batch("LINES", unselected_verts, transparent_color(unselected_elements_color), unselected_edges)
|
self.draw_batch("LINES", unselected_verts, transparent_color(unselected_elements_color), unselected_edges)
|
||||||
self.draw_batch("TRIS", unselected_verts, transparent_color(special_elements_color), unselected_tris)
|
self.draw_batch("TRIS", unselected_verts, transparent_color(special_elements_color), unselected_tris)
|
||||||
|
|||||||
@@ -1631,6 +1631,8 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
bpy.ops.bim.import_representation_items()
|
bpy.ops.bim.import_representation_items()
|
||||||
elif tool.Geometry.is_representation_item(obj):
|
elif tool.Geometry.is_representation_item(obj):
|
||||||
self.enable_editing_representation_item(context, obj)
|
self.enable_editing_representation_item(context, obj)
|
||||||
|
else: # A regular Blender object
|
||||||
|
self.enable_edit_mode(context)
|
||||||
|
|
||||||
def handle_multiple_selected_objects(self, context):
|
def handle_multiple_selected_objects(self, context):
|
||||||
obj = context.active_object
|
obj = context.active_object
|
||||||
@@ -2300,9 +2302,7 @@ class ImportRepresentationItems(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
context.scene.BIMGeometryProperties.mode = "ITEM"
|
context.scene.BIMGeometryProperties.mode = "ITEM"
|
||||||
context.scene.BIMGeometryProperties.is_changing_mode = False
|
context.scene.BIMGeometryProperties.is_changing_mode = False
|
||||||
|
|
||||||
tool.Loader.settings.contexts = ifcopenshell.util.representation.get_prioritised_contexts(tool.Ifc.get())
|
tool.Loader.load_settings()
|
||||||
tool.Loader.settings.context_settings = tool.Loader.create_settings()
|
|
||||||
tool.Loader.settings.gross_context_settings = tool.Loader.create_settings(is_gross=True)
|
|
||||||
|
|
||||||
for item_id in set(obj.data["ios_item_ids"]):
|
for item_id in set(obj.data["ios_item_ids"]):
|
||||||
item = tool.Ifc.get().by_id(item_id)
|
item = tool.Ifc.get().by_id(item_id)
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ classes = (
|
|||||||
ui.BIM_PT_roof,
|
ui.BIM_PT_roof,
|
||||||
ui.BIM_MT_type_manager_menu,
|
ui.BIM_MT_type_manager_menu,
|
||||||
ui.LaunchTypeManager,
|
ui.LaunchTypeManager,
|
||||||
ui.BIM_MT_model,
|
ui.BIM_MT_elements,
|
||||||
grid.BIM_OT_add_object,
|
grid.BIM_OT_add_object,
|
||||||
stair.BIM_OT_add_stair,
|
stair.BIM_OT_add_stair,
|
||||||
stair.AddStair,
|
stair.AddStair,
|
||||||
@@ -216,8 +216,7 @@ def register():
|
|||||||
bpy.types.Object.BIMRailingProperties = bpy.props.PointerProperty(type=prop.BIMRailingProperties)
|
bpy.types.Object.BIMRailingProperties = bpy.props.PointerProperty(type=prop.BIMRailingProperties)
|
||||||
bpy.types.Object.BIMRoofProperties = bpy.props.PointerProperty(type=prop.BIMRoofProperties)
|
bpy.types.Object.BIMRoofProperties = bpy.props.PointerProperty(type=prop.BIMRoofProperties)
|
||||||
|
|
||||||
bpy.types.VIEW3D_MT_mesh_add.append(ui.add_mesh_object_menu)
|
bpy.types.VIEW3D_MT_add.prepend(ui.add_menu)
|
||||||
bpy.types.VIEW3D_MT_add.append(ui.add_menu)
|
|
||||||
bpy.app.handlers.load_post.append(handler.load_post)
|
bpy.app.handlers.load_post.append(handler.load_post)
|
||||||
|
|
||||||
workspace.load_custom_icons()
|
workspace.load_custom_icons()
|
||||||
@@ -248,7 +247,6 @@ def unregister():
|
|||||||
del bpy.types.Object.BIMRoofProperties
|
del bpy.types.Object.BIMRoofProperties
|
||||||
|
|
||||||
bpy.app.handlers.load_post.remove(handler.load_post)
|
bpy.app.handlers.load_post.remove(handler.load_post)
|
||||||
bpy.types.VIEW3D_MT_mesh_add.remove(ui.add_mesh_object_menu)
|
|
||||||
bpy.types.VIEW3D_MT_add.remove(ui.add_menu)
|
bpy.types.VIEW3D_MT_add.remove(ui.add_menu)
|
||||||
|
|
||||||
workspace.unload_custom_icons()
|
workspace.unload_custom_icons()
|
||||||
|
|||||||
@@ -116,7 +116,8 @@ class LaunchTypeManager(bpy.types.Operator):
|
|||||||
row.operator("bim.disable_add_type", icon="CANCEL", text="")
|
row.operator("bim.disable_add_type", icon="CANCEL", text="")
|
||||||
else:
|
else:
|
||||||
row = self.layout.row()
|
row = self.layout.row()
|
||||||
row.operator("bim.enable_add_type", icon="ADD", text="Create New Type")
|
# row.operator("bim.enable_add_type", icon="ADD", text="Create New Type")
|
||||||
|
row.operator("bim.launch_add_element", icon="ADD", text="Create New Type")
|
||||||
|
|
||||||
flow = self.layout.grid_flow(row_major=True, columns=3, even_columns=True, even_rows=True, align=True)
|
flow = self.layout.grid_flow(row_major=True, columns=3, even_columns=True, even_rows=True, align=True)
|
||||||
|
|
||||||
@@ -656,26 +657,16 @@ class BIM_PT_roof(bpy.types.Panel):
|
|||||||
row.operator("bim.add_roof", icon="ADD", text="")
|
row.operator("bim.add_roof", icon="ADD", text="")
|
||||||
|
|
||||||
|
|
||||||
class BIM_MT_model(Menu):
|
class BIM_MT_elements(Menu):
|
||||||
bl_idname = "BIM_MT_model"
|
bl_idname = "BIM_MT_elements"
|
||||||
bl_label = "Objects"
|
bl_label = "IFC Elements"
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
# TODO consolidate in Item mode UI then remove
|
||||||
layout.operator("bim.add_empty_type", text="Empty Type", icon="EMPTY_AXIS")
|
self.layout.operator("bim.add_potential_half_space_solid", text="Half Space", icon="ORIENTATION_NORMAL")
|
||||||
layout.operator("bim.add_potential_half_space_solid", text="Half Space Proxy", icon="ORIENTATION_NORMAL")
|
self.layout.operator("bim.add_potential_opening", text="Opening", icon="CUBE")
|
||||||
layout.operator("bim.add_potential_opening", text="Opening Proxy", icon="CUBE")
|
|
||||||
|
|
||||||
|
|
||||||
def add_menu(self, context):
|
def add_menu(self, context):
|
||||||
self.layout.menu("BIM_MT_model", icon="FILE_3D")
|
self.layout.operator("bim.launch_add_element", icon_value=bonsai.bim.icons["IFC"].icon_id, text="IFC Element")
|
||||||
|
self.layout.separator()
|
||||||
|
|
||||||
def add_mesh_object_menu(self, context):
|
|
||||||
if context.mode == "OBJECT":
|
|
||||||
self.layout.separator()
|
|
||||||
self.layout.operator("mesh.add_stair", icon_value=bonsai.bim.icons["IFC"].icon_id, text="Stair (Untyped)")
|
|
||||||
self.layout.operator("mesh.add_window", icon_value=bonsai.bim.icons["IFC"].icon_id, text="Window (Untyped)")
|
|
||||||
self.layout.operator("mesh.add_door", icon_value=bonsai.bim.icons["IFC"].icon_id, text="Door (Untyped)")
|
|
||||||
self.layout.operator("mesh.add_railing", icon_value=bonsai.bim.icons["IFC"].icon_id, text="Railing (Untyped)")
|
|
||||||
self.layout.operator("mesh.add_roof", icon_value=bonsai.bim.icons["IFC"].icon_id, text="Roof (Untyped)")
|
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ class CreateProject(bpy.types.Operator):
|
|||||||
)
|
)
|
||||||
bonsai.bim.schema.reload(tool.Ifc.get().schema_identifier)
|
bonsai.bim.schema.reload(tool.Ifc.get().schema_identifier)
|
||||||
tool.Blender.register_toolbar()
|
tool.Blender.register_toolbar()
|
||||||
|
tool.Root.reload_item_decorator()
|
||||||
|
|
||||||
def rollback(self, data):
|
def rollback(self, data):
|
||||||
IfcStore.file = None
|
IfcStore.file = None
|
||||||
|
|||||||
@@ -20,10 +20,12 @@ import bpy
|
|||||||
from . import ui, prop, operator
|
from . import ui, prop, operator
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
|
operator.AddElement,
|
||||||
operator.AssignClass,
|
operator.AssignClass,
|
||||||
operator.CopyClass,
|
operator.CopyClass,
|
||||||
operator.DisableReassignClass,
|
operator.DisableReassignClass,
|
||||||
operator.EnableReassignClass,
|
operator.EnableReassignClass,
|
||||||
|
operator.LaunchAddElement,
|
||||||
operator.ReassignClass,
|
operator.ReassignClass,
|
||||||
operator.UnlinkObject,
|
operator.UnlinkObject,
|
||||||
prop.BIMRootProperties,
|
prop.BIMRootProperties,
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ class IfcClassData:
|
|||||||
cls.data["ifc_products"] = cls.ifc_products()
|
cls.data["ifc_products"] = cls.ifc_products()
|
||||||
cls.data["ifc_classes"] = cls.ifc_classes()
|
cls.data["ifc_classes"] = cls.ifc_classes()
|
||||||
cls.data["ifc_classes_suggestions"] = cls.ifc_classes_suggestions() # Call AFTER cls.ifc_classes()
|
cls.data["ifc_classes_suggestions"] = cls.ifc_classes_suggestions() # Call AFTER cls.ifc_classes()
|
||||||
|
cls.data["representation_template"] = cls.representation_template()
|
||||||
cls.data["contexts"] = cls.contexts()
|
cls.data["contexts"] = cls.contexts()
|
||||||
|
|
||||||
cls.data["has_entity"] = cls.has_entity()
|
cls.data["has_entity"] = cls.has_entity()
|
||||||
@@ -58,9 +59,7 @@ class IfcClassData:
|
|||||||
"IfcElement",
|
"IfcElement",
|
||||||
"IfcSpatialElement",
|
"IfcSpatialElement",
|
||||||
"IfcSpatialElementType",
|
"IfcSpatialElementType",
|
||||||
"IfcGroup",
|
|
||||||
"IfcStructuralItem",
|
"IfcStructuralItem",
|
||||||
"IfcContext",
|
|
||||||
"IfcAnnotation",
|
"IfcAnnotation",
|
||||||
"IfcRelSpaceBoundary",
|
"IfcRelSpaceBoundary",
|
||||||
]
|
]
|
||||||
@@ -70,7 +69,6 @@ class IfcClassData:
|
|||||||
"IfcElementType",
|
"IfcElementType",
|
||||||
"IfcElement",
|
"IfcElement",
|
||||||
"IfcSpatialStructureElement",
|
"IfcSpatialStructureElement",
|
||||||
"IfcGroup",
|
|
||||||
"IfcStructuralItem",
|
"IfcStructuralItem",
|
||||||
"IfcAnnotation",
|
"IfcAnnotation",
|
||||||
"IfcRelSpaceBoundary",
|
"IfcRelSpaceBoundary",
|
||||||
@@ -130,6 +128,95 @@ class IfcClassData:
|
|||||||
suggestions[ifc_class].append(suggestion_dict)
|
suggestions[ifc_class].append(suggestion_dict)
|
||||||
return suggestions
|
return suggestions
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def representation_template(cls):
|
||||||
|
ifc_class = bpy.context.scene.BIMRootProperties.ifc_class
|
||||||
|
templates = [
|
||||||
|
("EMPTY", "No Geometry", "Start with an empty object"),
|
||||||
|
None,
|
||||||
|
]
|
||||||
|
if (
|
||||||
|
hasattr(bpy.context, "selected_objects")
|
||||||
|
and len(bpy.context.selected_objects) > 0
|
||||||
|
and (obj := bpy.context.active_object)
|
||||||
|
and obj.type == "MESH"
|
||||||
|
):
|
||||||
|
templates.append(
|
||||||
|
(
|
||||||
|
"OBJ",
|
||||||
|
"Tessellation From Active Selection",
|
||||||
|
"Use the actively selected object as a template to create a new tessellation",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
templates.extend(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"MESH",
|
||||||
|
"Custom Tessellation",
|
||||||
|
"Create a basic tessellated or faceted cube",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"EXTRUSION",
|
||||||
|
"Custom Extruded Solid",
|
||||||
|
"An extrusion from an arbitrary profile",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
if ifc_class.endswith("Type") or ifc_class.endswith("Style"):
|
||||||
|
templates.extend(
|
||||||
|
[
|
||||||
|
None,
|
||||||
|
(
|
||||||
|
"LAYERSET_AXIS2",
|
||||||
|
"Vertical Layers",
|
||||||
|
"For objects similar to walls, will automatically add IfcMaterialLayerSet",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"LAYERSET_AXIS3",
|
||||||
|
"Horizontal Layers",
|
||||||
|
"For objects similar to slabs, will automatically add IfcMaterialLayerSet",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"PROFILESET",
|
||||||
|
"Extruded Profile",
|
||||||
|
"Create profile type object, automatically defines IfcMaterialProfileSet with the first profile from library",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
if ifc_class in ("IfcWindowType", "IfcWindowStyle", "IfcWindow"):
|
||||||
|
templates.extend([None, ("WINDOW", "Window", "Parametric window")])
|
||||||
|
elif ifc_class in ("IfcDoorType", "IfcDoorStyle", "IfcDoor"):
|
||||||
|
templates.extend([None, ("DOOR", "Door", "Parametric door")])
|
||||||
|
elif ifc_class in ("IfcStairType", "IfcStairFlightType", "IfcStair", "IfcStairFlight"):
|
||||||
|
templates.extend([None, ("STAIR", "Stair", "Parametric stair")])
|
||||||
|
elif ifc_class in ("IfcRailingType", "IfcRailing"):
|
||||||
|
templates.extend([None, ("RAILING", "Railing", "Parametric railing")])
|
||||||
|
elif ifc_class in ("IfcRoofType", "IfcRoof"):
|
||||||
|
templates.extend([None, ("ROOF", "Roof", "Parametric roof with a constant pitch")])
|
||||||
|
elif ifc_class and "Segment" in ifc_class:
|
||||||
|
templates.extend(
|
||||||
|
(
|
||||||
|
None,
|
||||||
|
(
|
||||||
|
"FLOW_SEGMENT_RECTANGULAR",
|
||||||
|
"Rectangular Distribution Segment",
|
||||||
|
"Works similarly to Profile, has distribution ports",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"FLOW_SEGMENT_CIRCULAR",
|
||||||
|
"Circular Distribution Segment",
|
||||||
|
"Works similarly to Profile, has distribution ports",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"FLOW_SEGMENT_CIRCULAR_HOLLOW",
|
||||||
|
"Circular Hollow Distribution Segment",
|
||||||
|
"Works similarly to Profile, has distribution ports",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return templates
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def contexts(cls):
|
def contexts(cls):
|
||||||
results = []
|
results = []
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
import bmesh
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import ifcopenshell.api
|
import ifcopenshell.api
|
||||||
import ifcopenshell.util.schema
|
import ifcopenshell.util.schema
|
||||||
@@ -24,9 +25,12 @@ import ifcopenshell.util.element
|
|||||||
import ifcopenshell.util.type
|
import ifcopenshell.util.type
|
||||||
import bonsai.bim.handler
|
import bonsai.bim.handler
|
||||||
import bonsai.core.root as core
|
import bonsai.core.root as core
|
||||||
|
import bonsai.core.geometry
|
||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
|
import bonsai.bim.module.root.prop as root_prop
|
||||||
from bonsai.bim.ifc import IfcStore
|
from bonsai.bim.ifc import IfcStore
|
||||||
from bonsai.bim.helper import get_enum_items
|
from bonsai.bim.helper import get_enum_items, prop_with_search
|
||||||
|
from mathutils import Vector
|
||||||
|
|
||||||
|
|
||||||
class EnableReassignClass(bpy.types.Operator):
|
class EnableReassignClass(bpy.types.Operator):
|
||||||
@@ -302,3 +306,241 @@ class CopyClass(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
for obj in objects:
|
for obj in objects:
|
||||||
core.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=obj)
|
core.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=obj)
|
||||||
bonsai.bim.handler.refresh_ui_data()
|
bonsai.bim.handler.refresh_ui_data()
|
||||||
|
|
||||||
|
|
||||||
|
class AddElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
|
bl_idname = "bim.add_element"
|
||||||
|
bl_label = "Add Element"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
bl_description = "Add an IFC physical product, construction type, and more"
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
return IfcStore.execute_ifc_operator(self, context, is_invoke=True)
|
||||||
|
|
||||||
|
def _invoke(self, context, event):
|
||||||
|
return context.window_manager.invoke_props_dialog(self)
|
||||||
|
|
||||||
|
def _execute(self, context):
|
||||||
|
props = context.scene.BIMRootProperties
|
||||||
|
predefined_type = (
|
||||||
|
props.userdefined_type if props.ifc_predefined_type == "USERDEFINED" else props.ifc_predefined_type
|
||||||
|
)
|
||||||
|
representation_template = props.representation_template
|
||||||
|
|
||||||
|
ifc_context = None
|
||||||
|
if get_enum_items(props, "contexts", context):
|
||||||
|
ifc_context = int(props.contexts or "0") or None
|
||||||
|
if ifc_context:
|
||||||
|
ifc_context = tool.Ifc.get().by_id(ifc_context)
|
||||||
|
|
||||||
|
if representation_template in (
|
||||||
|
"EMPTY",
|
||||||
|
"LAYERSET_AXIS2",
|
||||||
|
"LAYERSET_AXIS3",
|
||||||
|
"PROFILESET",
|
||||||
|
) or representation_template.startswith("FLOW_SEGMENT_"):
|
||||||
|
mesh = None
|
||||||
|
else:
|
||||||
|
mesh = bpy.data.meshes.new("Mesh")
|
||||||
|
|
||||||
|
obj = bpy.data.objects.new(props.ifc_class[3:], mesh)
|
||||||
|
obj.location = bpy.context.scene.cursor.location
|
||||||
|
element = core.assign_class(
|
||||||
|
tool.Ifc,
|
||||||
|
tool.Collector,
|
||||||
|
tool.Root,
|
||||||
|
obj=obj,
|
||||||
|
ifc_class=props.ifc_class,
|
||||||
|
predefined_type=predefined_type,
|
||||||
|
should_add_representation=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
if representation_template == "EMTPY" or not ifc_context:
|
||||||
|
pass
|
||||||
|
elif (
|
||||||
|
representation_template == "OBJ"
|
||||||
|
and (template_obj := context.active_object)
|
||||||
|
and template_obj.type == "MESH"
|
||||||
|
and len(template_obj.data.vertices)
|
||||||
|
):
|
||||||
|
obj.matrix_world = template_obj.matrix_world
|
||||||
|
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
|
||||||
|
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
|
bm = bmesh.new()
|
||||||
|
bmesh.ops.create_cube(bm, size=0.5)
|
||||||
|
verts = [v.co / unit_scale for v in template_obj.data.vertices]
|
||||||
|
faces = [p.vertices[:] for p in template_obj.data.polygons]
|
||||||
|
item = builder.mesh(verts, faces)
|
||||||
|
bm.free()
|
||||||
|
representation = builder.get_representation(ifc_context, [item])
|
||||||
|
ifcopenshell.api.geometry.assign_representation(tool.Ifc.get(), element, representation)
|
||||||
|
bonsai.core.geometry.switch_representation(
|
||||||
|
tool.Ifc,
|
||||||
|
tool.Geometry,
|
||||||
|
obj=obj,
|
||||||
|
representation=representation,
|
||||||
|
should_reload=True,
|
||||||
|
is_global=True,
|
||||||
|
should_sync_changes_first=False,
|
||||||
|
)
|
||||||
|
if not tool.Ifc.get_entity(template_obj):
|
||||||
|
bpy.data.objects.remove(template_obj)
|
||||||
|
elif representation_template == "MESH":
|
||||||
|
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
|
||||||
|
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
|
bm = bmesh.new()
|
||||||
|
bmesh.ops.create_cube(bm, size=0.5)
|
||||||
|
verts = [v.co / unit_scale for v in bm.verts]
|
||||||
|
faces = [[v.index for v in p.verts] for p in bm.faces]
|
||||||
|
item = builder.mesh(verts, faces)
|
||||||
|
bm.free()
|
||||||
|
representation = builder.get_representation(ifc_context, [item])
|
||||||
|
ifcopenshell.api.geometry.assign_representation(tool.Ifc.get(), element, representation)
|
||||||
|
bonsai.core.geometry.switch_representation(
|
||||||
|
tool.Ifc,
|
||||||
|
tool.Geometry,
|
||||||
|
obj=obj,
|
||||||
|
representation=representation,
|
||||||
|
should_reload=True,
|
||||||
|
is_global=True,
|
||||||
|
should_sync_changes_first=False,
|
||||||
|
)
|
||||||
|
elif representation_template == "EXTRUSION":
|
||||||
|
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
|
||||||
|
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
|
curve = builder.rectangle(size=Vector((0.5, 0.5)) / unit_scale)
|
||||||
|
item = builder.extrude(curve, magnitude=0.5 / unit_scale)
|
||||||
|
representation = builder.get_representation(ifc_context, [item])
|
||||||
|
ifcopenshell.api.geometry.assign_representation(tool.Ifc.get(), element, representation)
|
||||||
|
bonsai.core.geometry.switch_representation(
|
||||||
|
tool.Ifc,
|
||||||
|
tool.Geometry,
|
||||||
|
obj=obj,
|
||||||
|
representation=representation,
|
||||||
|
should_reload=True,
|
||||||
|
is_global=True,
|
||||||
|
should_sync_changes_first=False,
|
||||||
|
)
|
||||||
|
elif representation_template in ("LAYERSET_AXIS2", "LAYERSET_AXIS3"):
|
||||||
|
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
|
materials = tool.Ifc.get().by_type("IfcMaterial")
|
||||||
|
if materials:
|
||||||
|
material = materials[0] # Arbitrarily pick a material
|
||||||
|
else:
|
||||||
|
material = ifcopenshell.api.run("material.add_material", tool.Ifc.get(), name="Unknown")
|
||||||
|
rel = ifcopenshell.api.run(
|
||||||
|
"material.assign_material", tool.Ifc.get(), products=[element], type="IfcMaterialLayerSet"
|
||||||
|
)
|
||||||
|
layer_set = rel.RelatingMaterial
|
||||||
|
layer = ifcopenshell.api.run("material.add_layer", tool.Ifc.get(), layer_set=layer_set, material=material)
|
||||||
|
thickness = 0.1 # Arbitrary metric thickness for now
|
||||||
|
layer.LayerThickness = thickness / unit_scale
|
||||||
|
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="EPset_Parametric")
|
||||||
|
if representation_template == "LAYERSET_AXIS2":
|
||||||
|
axis = "AXIS2"
|
||||||
|
elif representation_template == "LAYERSET_AXIS3":
|
||||||
|
axis = "AXIS3"
|
||||||
|
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"LayerSetDirection": axis})
|
||||||
|
elif representation_template == "PROFILESET" or representation_template.startswith("FLOW_SEGMENT_"):
|
||||||
|
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
|
materials = tool.Ifc.get().by_type("IfcMaterial")
|
||||||
|
if materials:
|
||||||
|
material = materials[0] # Arbitrarily pick a material
|
||||||
|
else:
|
||||||
|
material = ifcopenshell.api.run("material.add_material", tool.Ifc.get(), name="Unknown")
|
||||||
|
if representation_template == "PROFILESET":
|
||||||
|
named_profiles = [p for p in tool.Ifc.get().by_type("IfcProfileDef") if p.ProfileName]
|
||||||
|
if named_profiles:
|
||||||
|
profile = named_profiles[0]
|
||||||
|
else:
|
||||||
|
size = 0.5 / unit_scale
|
||||||
|
profile = tool.Ifc.get().create_entity(
|
||||||
|
"IfcRectangleProfileDef", ProfileName="New Profile", ProfileType="AREA", XDim=size, YDim=size
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# NOTE: defaults dims are in meters / mm
|
||||||
|
# for now default names are hardcoded to mm
|
||||||
|
if representation_template == "FLOW_SEGMENT_RECTANGULAR":
|
||||||
|
default_x_dim = 0.4
|
||||||
|
default_y_dim = 0.2
|
||||||
|
profile_name = f"{props.ifc_class}-{default_x_dim*1000}x{default_y_dim*1000}"
|
||||||
|
profile = tool.Ifc.get().create_entity(
|
||||||
|
"IfcRectangleProfileDef",
|
||||||
|
ProfileName=profile_name,
|
||||||
|
ProfileType="AREA",
|
||||||
|
XDim=default_x_dim / unit_scale,
|
||||||
|
YDim=default_y_dim / unit_scale,
|
||||||
|
)
|
||||||
|
elif representation_template == "FLOW_SEGMENT_CIRCULAR":
|
||||||
|
default_diameter = 0.1
|
||||||
|
profile_name = f"{props.ifc_class}-{default_diameter*1000}"
|
||||||
|
profile = tool.Ifc.get().create_entity(
|
||||||
|
"IfcCircleProfileDef",
|
||||||
|
ProfileName=profile_name,
|
||||||
|
ProfileType="AREA",
|
||||||
|
Radius=(default_diameter / 2) / unit_scale,
|
||||||
|
)
|
||||||
|
elif representation_template == "FLOW_SEGMENT_CIRCULAR_HOLLOW":
|
||||||
|
default_diameter = 0.15
|
||||||
|
default_thickness = 0.005
|
||||||
|
profile_name = f"{props.ifc_class}-{default_diameter*1000}x{default_thickness*1000}"
|
||||||
|
profile = tool.Ifc.get().create_entity(
|
||||||
|
"IfcCircleHollowProfileDef",
|
||||||
|
ProfileName=profile_name,
|
||||||
|
ProfileType="AREA",
|
||||||
|
Radius=(default_diameter / 2) / unit_scale,
|
||||||
|
WallThickness=default_thickness,
|
||||||
|
)
|
||||||
|
|
||||||
|
rel = ifcopenshell.api.run(
|
||||||
|
"material.assign_material", tool.Ifc.get(), products=[element], type="IfcMaterialProfileSet"
|
||||||
|
)
|
||||||
|
profile_set = rel.RelatingMaterial
|
||||||
|
material_profile = ifcopenshell.api.run(
|
||||||
|
"material.add_profile", tool.Ifc.get(), profile_set=profile_set, material=material
|
||||||
|
)
|
||||||
|
ifcopenshell.api.run(
|
||||||
|
"material.assign_profile", tool.Ifc.get(), material_profile=material_profile, profile=profile
|
||||||
|
)
|
||||||
|
elif representation_template == "WINDOW":
|
||||||
|
with context.temp_override(active_object=obj):
|
||||||
|
bpy.ops.bim.add_window()
|
||||||
|
elif representation_template == "DOOR":
|
||||||
|
with context.temp_override(active_object=obj):
|
||||||
|
bpy.ops.bim.add_door()
|
||||||
|
elif representation_template == "STAIR":
|
||||||
|
with context.temp_override(active_object=obj):
|
||||||
|
bpy.ops.bim.add_stair()
|
||||||
|
elif representation_template == "RAILING":
|
||||||
|
with context.temp_override(active_object=obj):
|
||||||
|
bpy.ops.bim.add_railing()
|
||||||
|
elif representation_template == "ROOF":
|
||||||
|
with context.temp_override(active_object=obj):
|
||||||
|
bpy.ops.bim.add_roof()
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
props = context.scene.BIMRootProperties
|
||||||
|
self.layout.use_property_split = True
|
||||||
|
self.layout.use_property_decorate = False
|
||||||
|
prop_with_search(self.layout, props, "ifc_product", text="Definition")
|
||||||
|
prop_with_search(self.layout, props, "ifc_class", should_click_ok_to_validate=True)
|
||||||
|
ifc_predefined_types = root_prop.get_ifc_predefined_types(context.scene.BIMRootProperties, context)
|
||||||
|
if ifc_predefined_types:
|
||||||
|
prop_with_search(self.layout, props, "ifc_predefined_type")
|
||||||
|
if props.ifc_predefined_type == "USERDEFINED":
|
||||||
|
row = self.layout.row()
|
||||||
|
row.prop(props, "ifc_userdefined_type")
|
||||||
|
prop_with_search(self.layout, props, "representation_template", text="Representation")
|
||||||
|
if props.representation_template != "EMPTY":
|
||||||
|
prop_with_search(self.layout, props, "contexts")
|
||||||
|
|
||||||
|
|
||||||
|
class LaunchAddElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
|
bl_idname = "bim.launch_add_element"
|
||||||
|
bl_label = "Add Element"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
bl_description = "Add an IFC physical product, construction type, and more"
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
bpy.ops.bim.add_element("INVOKE_DEFAULT")
|
||||||
|
return {"FINISHED"}
|
||||||
|
|||||||
@@ -41,6 +41,12 @@ def get_ifc_predefined_types(self, context):
|
|||||||
return IfcClassData.data["ifc_predefined_types"]
|
return IfcClassData.data["ifc_predefined_types"]
|
||||||
|
|
||||||
|
|
||||||
|
def get_representation_template(self, context):
|
||||||
|
if not IfcClassData.is_loaded:
|
||||||
|
IfcClassData.load()
|
||||||
|
return IfcClassData.data["representation_template"]
|
||||||
|
|
||||||
|
|
||||||
def refresh_classes(self, context):
|
def refresh_classes(self, context):
|
||||||
enum = get_ifc_classes(self, context)
|
enum = get_ifc_classes(self, context)
|
||||||
context.scene.BIMRootProperties.ifc_class = enum[0][0]
|
context.scene.BIMRootProperties.ifc_class = enum[0][0]
|
||||||
@@ -110,11 +116,12 @@ def is_object_class_applicable(self, obj):
|
|||||||
|
|
||||||
|
|
||||||
class BIMRootProperties(PropertyGroup):
|
class BIMRootProperties(PropertyGroup):
|
||||||
contexts: EnumProperty(items=get_contexts, name="Contexts")
|
contexts: EnumProperty(items=get_contexts, name="Contexts", options=set())
|
||||||
ifc_product: EnumProperty(items=get_ifc_products, name="Products", update=refresh_classes)
|
ifc_product: EnumProperty(items=get_ifc_products, name="Products", update=refresh_classes)
|
||||||
ifc_class: EnumProperty(items=get_ifc_classes, name="Class", update=refresh_predefined_types)
|
ifc_class: EnumProperty(items=get_ifc_classes, name="Class", update=refresh_predefined_types)
|
||||||
ifc_predefined_type: EnumProperty(items=get_ifc_predefined_types, name="Predefined Type", default=None)
|
ifc_predefined_type: EnumProperty(items=get_ifc_predefined_types, name="Predefined Type", default=None)
|
||||||
ifc_userdefined_type: StringProperty(name="Userdefined Type")
|
ifc_userdefined_type: StringProperty(name="Userdefined Type")
|
||||||
|
representation_template: bpy.props.EnumProperty(items=get_representation_template, name="Representation Template", default=0)
|
||||||
relating_class_object: PointerProperty(
|
relating_class_object: PointerProperty(
|
||||||
type=bpy.types.Object,
|
type=bpy.types.Object,
|
||||||
name="Copy Class",
|
name="Copy Class",
|
||||||
|
|||||||
@@ -19,9 +19,10 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import math
|
|
||||||
import bpy
|
import bpy
|
||||||
|
import math
|
||||||
import bmesh
|
import bmesh
|
||||||
|
import logging
|
||||||
import ifcopenshell.geom
|
import ifcopenshell.geom
|
||||||
import ifcopenshell.util.element
|
import ifcopenshell.util.element
|
||||||
import ifcopenshell.util.geolocation
|
import ifcopenshell.util.geolocation
|
||||||
@@ -56,6 +57,14 @@ class Loader(bonsai.core.tool.Loader):
|
|||||||
def set_unit_scale(cls, unit_scale: float) -> None:
|
def set_unit_scale(cls, unit_scale: float) -> None:
|
||||||
cls.unit_scale = unit_scale
|
cls.unit_scale = unit_scale
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_settings(cls) -> None:
|
||||||
|
logger = logging.getLogger("ImportIFC")
|
||||||
|
cls.settings = bonsai.bim.import_ifc.IfcImportSettings.factory(bpy.context, None, logger)
|
||||||
|
cls.settings.contexts = ifcopenshell.util.representation.get_prioritised_contexts(tool.Ifc.get())
|
||||||
|
cls.settings.context_settings = cls.create_settings()
|
||||||
|
cls.settings.gross_context_settings = cls.create_settings(is_gross=True)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def set_settings(cls, settings: bonsai.bim.import_ifc.IfcImportSettings) -> None:
|
def set_settings(cls, settings: bonsai.bim.import_ifc.IfcImportSettings) -> None:
|
||||||
cls.settings = settings
|
cls.settings = settings
|
||||||
|
|||||||
Reference in New Issue
Block a user