Deprecate type_class in favour of new simpler type manager workflow

This commit is contained in:
Dion Moult
2024-09-30 14:03:56 +10:00
parent 2c22421889
commit af7bdf6b9f
9 changed files with 134 additions and 497 deletions
+41 -131
View File
@@ -59,10 +59,8 @@ class AuthoringData:
cls.data["relating_type_id"] = cls.relating_type_id() # only after .ifc_classes()
cls.data["relating_type_name"] = cls.relating_type_name() # only after .relating_type_id()
cls.data["predefined_type"] = cls.predefined_type() # only after .relating_type_id()
cls.data["type_class"] = cls.type_class()
# only after .type_class()
cls.data["type_predefined_type"] = cls.type_predefined_type()
# only after .ifc_classes()
cls.data["total_types"] = cls.total_types()
cls.data["total_pages"] = cls.total_pages()
cls.data["next_page"] = cls.next_page()
@@ -78,7 +76,6 @@ class AuthoringData:
cls.data["active_representation_type"] = cls.active_representation_type()
cls.data["boundary_class"] = cls.boundary_class()
cls.data["selected_material_usages"] = cls.selected_material_usages()
cls.data["type_template"] = cls.type_template()
@classmethod
def default_container(cls) -> str | None:
@@ -97,41 +94,6 @@ class AuthoringData:
version = tool.Ifc.get_schema()
return [(c, c, get_entity_doc(version, c).get("description", "")) for c in sorted(names)]
@classmethod
def type_class(cls):
declaration = tool.Ifc.schema().declaration_by_name("IfcElementType")
declarations = ifcopenshell.util.schema.get_subtypes(declaration)
names = [d.name() for d in declarations]
if tool.Ifc.get_schema() == "IFC2X3":
declaration = tool.Ifc.schema().declaration_by_name("IfcSpatialStructureElementType")
else:
declaration = tool.Ifc.schema().declaration_by_name("IfcSpatialElementType")
declarations = ifcopenshell.util.schema.get_subtypes(declaration)
names.extend([d.name() for d in declarations])
if tool.Ifc.get_schema() in ("IFC2X3", "IFC4"):
names.extend(("IfcDoorStyle", "IfcWindowStyle"))
version = tool.Ifc.get_schema()
return [(c, c, get_entity_doc(version, c).get("description", "")) for c in sorted(names)]
@classmethod
def type_predefined_type(cls):
results = []
declaration = tool.Ifc().schema().declaration_by_name(cls.props.type_class)
version = tool.Ifc.get_schema()
for attribute in declaration.attributes():
if attribute.name() == "PredefinedType":
results.extend(
[
(e, e, get_predefined_type_doc(version, cls.props.type_class, e))
for e in attribute.type_of_attribute().declared_type().enumeration_items()
]
)
break
return results
@classmethod
def type_thumbnail(cls):
if not cls.data["relating_type_id"]:
@@ -142,76 +104,15 @@ class AuthoringData:
element = tool.Ifc.get().by_id(int(relating_type_id))
return cls.type_thumbnails.get(element.id(), None) or 0
@classmethod
def type_template(cls):
type_class = cls.props.type_class
templates = [
(
"MESH",
"Custom Mesh",
"Use as a representation currently active object mesh or default cube if no object selected",
),
(
"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",
),
("EMPTY", "Non-Geometric Type", "Start with an empty object"),
]
if not type_class:
pass
elif type_class in ("IfcWindowType", "IfcWindowStyle"):
templates.append(("WINDOW", "Window", "Parametric window"))
elif type_class in ("IfcDoorType", "IfcDoorStyle"):
templates.append(("DOOR", "Door", "Parametric door"))
elif type_class in ("IfcStairType", "IfcStairFlightType"):
templates.append(("STAIR", "Stair", "Parametric stair"))
elif type_class == "IfcRailingType":
templates.append(("RAILING", "Railing", "Parametric railing"))
elif type_class == "IfcRoofType":
templates.append(("Roof", "Roof", "Parametric roof"))
templates.extend(
(
(
"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
def total_types(cls):
type_class = cls.props.type_class
return len(tool.Ifc.get().by_type(type_class)) if type_class else 0
ifc_class = cls.props.ifc_class
return len(tool.Ifc.get().by_type(ifc_class)) if ifc_class else 0
@classmethod
def total_pages(cls):
type_class = cls.props.type_class
total_types = len(tool.Ifc.get().by_type(type_class)) if type_class else 0
ifc_class = cls.props.ifc_class
total_types = len(tool.Ifc.get().by_type(ifc_class)) if ifc_class else 0
return math.ceil(total_types / cls.types_per_page)
@classmethod
@@ -226,11 +127,11 @@ class AuthoringData:
@classmethod
def paginated_relating_types(cls):
type_class = cls.props.type_class
if not type_class:
ifc_class = cls.props.ifc_class
if not ifc_class:
return []
results = []
elements = natsorted(tool.Ifc.get().by_type(type_class), key=lambda e: e.Name or "Unnamed")
elements = natsorted(tool.Ifc.get().by_type(ifc_class), key=lambda e: e.Name or "Unnamed")
elements = elements[(cls.props.type_page - 1) * cls.types_per_page : cls.props.type_page * cls.types_per_page]
for element in elements:
predefined_type = ifcopenshell.util.element.get_predefined_type(element)
@@ -250,45 +151,50 @@ class AuthoringData:
@classmethod
def is_voidable_element(cls):
element = tool.Ifc.get_entity(bpy.context.active_object)
return element and element.is_a("IfcElement") and not element.is_a("IfcOpeningElement")
if active_object := tool.Blender.get_active_object():
element = tool.Ifc.get_entity(active_object)
return element and element.is_a("IfcElement") and not element.is_a("IfcOpeningElement")
@classmethod
def has_visible_openings(cls):
element = tool.Ifc.get_entity(bpy.context.active_object)
if element and element.is_a("IfcElement") and not element.is_a("IfcOpeningElement"):
for opening in [r.RelatedOpeningElement for r in element.HasOpenings]:
if tool.Ifc.get_object(opening):
return True
if active_object := tool.Blender.get_active_object():
element = tool.Ifc.get_entity(active_object)
if element and element.is_a("IfcElement") and not element.is_a("IfcOpeningElement"):
for opening in [r.RelatedOpeningElement for r in element.HasOpenings]:
if tool.Ifc.get_object(opening):
return True
return False
@classmethod
def has_visible_boundaries(cls):
element = tool.Ifc.get_entity(bpy.context.active_object)
if element:
if element.is_a("IfcRelSpaceBoundary"):
return True
for boundary in getattr(element, "BoundedBy", []):
if tool.Ifc.get_object(boundary):
if active_object := tool.Blender.get_active_object():
element = tool.Ifc.get_entity(active_object)
if element:
if element.is_a("IfcRelSpaceBoundary"):
return True
for boundary in getattr(element, "BoundedBy", []):
if tool.Ifc.get_object(boundary):
return True
return False
@classmethod
def active_class(cls):
element = tool.Ifc.get_entity(bpy.context.active_object)
if element:
return element.is_a()
if active_object := tool.Blender.get_active_object():
element = tool.Ifc.get_entity(active_object)
if element:
return element.is_a()
@classmethod
def active_material_usage(cls):
element = tool.Ifc.get_entity(bpy.context.active_object)
if element:
return tool.Model.get_usage_type(element)
if active_object := tool.Blender.get_active_object():
element = tool.Ifc.get_entity(active_object)
if element:
return tool.Model.get_usage_type(element)
@classmethod
def active_representation_type(cls):
if bpy.context.active_object:
representation = tool.Geometry.get_active_representation(bpy.context.active_object)
if active_object := tool.Blender.get_active_object():
representation = tool.Geometry.get_active_representation(active_object)
if representation and representation.is_a("IfcShapeRepresentation"):
representation = tool.Geometry.resolve_mapped_representation(representation)
return representation.RepresentationType
@@ -317,7 +223,7 @@ class AuthoringData:
if not ifc_classes:
return []
results = []
ifc_class = cls.props.ifc_class
ifc_class = tool.Blender.get_enum_safe(cls.props, "ifc_class")
if not ifc_class and ifc_classes:
ifc_class = ifc_classes[0][0]
if ifc_class:
@@ -328,7 +234,11 @@ class AuthoringData:
@classmethod
def relating_type_name(cls):
if relating_type_id := cls.props.relating_type_id:
relating_type_id = tool.Blender.get_enum_safe(cls.props, "relating_type_id")
relating_type_id_data = cls.data["relating_type_id"]
if not relating_type_id and relating_type_id_data:
relating_type_id = relating_type_id_data[0][0]
if relating_type_id:
return tool.Ifc.get().by_id(int(relating_type_id)).Name or "Unnamed"
@classmethod
@@ -345,7 +255,7 @@ class AuthoringData:
@classmethod
def selected_material_usages(cls):
selected_usages = {}
for obj in bpy.context.selected_objects:
for obj in tool.Blender.get_selected_objects():
element = tool.Ifc.get_entity(obj)
if not element:
continue
+1 -1
View File
@@ -516,7 +516,7 @@ class MEPGenerator:
break
def create_obstruction_type(self, segment):
# code is very similar to "bim.add_type"
# code is very similar to "bim.add_element"
profile_set = ifcopenshell.util.element.get_material(segment, should_skip_usage=True)
material_profile = profile_set.MaterialProfiles[0]
profile = material_profile.Profile
+36 -28
View File
@@ -67,37 +67,37 @@ class AddDefaultType(bpy.types.Operator, tool.Ifc.Operator):
ifc_element_type: bpy.props.StringProperty()
def _execute(self, context):
props = context.scene.BIMModelProperties
ifc_file = tool.Ifc.get()
props.type_class = self.ifc_element_type
props = context.scene.BIMRootProperties
props.ifc_product = "IfcElementType"
props.ifc_class = self.ifc_element_type
if self.ifc_element_type == "IfcWallType":
if ifc_file.schema == "IFC2X3":
props.type_predefined_type = "STANDARD"
if tool.Ifc.get().schema == "IFC2X3":
props.ifc_predefined_type = "STANDARD"
else:
props.type_predefined_type = "SOLIDWALL"
props.type_template = "LAYERSET_AXIS2"
props.ifc_predefined_type = "SOLIDWALL"
props.representation_template = "LAYERSET_AXIS2"
elif self.ifc_element_type == "IfcSlabType":
props.type_predefined_type = "FLOOR"
props.type_template = "LAYERSET_AXIS3"
props.ifc_predefined_type = "FLOOR"
props.representation_template = "LAYERSET_AXIS3"
elif self.ifc_element_type == "IfcDoorType":
props.type_predefined_type = "DOOR"
props.type_template = "DOOR"
props.ifc_predefined_type = "DOOR"
props.representation_template = "DOOR"
elif self.ifc_element_type == "IfcWindowType":
props.type_predefined_type = "WINDOW"
props.type_template = "WINDOW"
props.ifc_predefined_type = "WINDOW"
props.representation_template = "WINDOW"
elif self.ifc_element_type == "IfcColumnType":
props.type_predefined_type = "COLUMN"
props.type_template = "PROFILESET"
props.ifc_predefined_type = "COLUMN"
props.representation_template = "PROFILESET"
elif self.ifc_element_type == "IfcBeamType":
props.type_predefined_type = "BEAM"
props.type_template = "PROFILESET"
props.ifc_predefined_type = "BEAM"
props.representation_template = "PROFILESET"
elif self.ifc_element_type == "IfcDuctSegmentType":
props.type_predefined_type = "RIGIDSEGMENT"
props.type_template = "FLOW_SEGMENT_RECTANGULAR"
props.ifc_predefined_type = "RIGIDSEGMENT"
props.representation_template = "FLOW_SEGMENT_RECTANGULAR"
elif self.ifc_element_type == "IfcPipeSegmentType":
props.type_predefined_type = "RIGIDSEGMENT"
props.type_template = "FLOW_SEGMENT_CIRCULAR"
bpy.ops.bim.add_type()
props.ifc_predefined_type = "RIGIDSEGMENT"
props.representation_template = "FLOW_SEGMENT_CIRCULAR"
bpy.ops.bim.add_element()
class AddOccurrence(bpy.types.Operator, PolylineOperator):
@@ -111,10 +111,6 @@ class AddOccurrence(bpy.types.Operator, PolylineOperator):
def __init__(self):
super().__init__()
self.relating_type = None
relating_type_id = bpy.context.scene.BIMModelProperties.relating_type_id
if relating_type_id:
self.relating_type = tool.Ifc.get().by_id(int(relating_type_id))
def create_occurrence(self, context, event):
if not self.relating_type:
@@ -151,8 +147,20 @@ class AddOccurrence(bpy.types.Operator, PolylineOperator):
snap_obj.select_set(False)
def modal(self, context, event):
# Ensure state of BIM tool props is valid
props = bpy.context.scene.BIMModelProperties
relating_type_id = tool.Blender.get_enum_safe(props, "relating_type_id")
relating_type_id_data = AuthoringData.data["relating_type_id"]
if not relating_type_id and relating_type_id_data:
props.relating_type_id = relating_type_id_data[0][0]
self.relating_type = None
relating_type_id = props.relating_type_id
if relating_type_id:
self.relating_type = tool.Ifc.get().by_id(int(relating_type_id))
if not self.relating_type:
self.report({"WARNING"}, "You need to select a wall type.")
self.report({"WARNING"}, "You need to select a type.")
PolylineDecorator.uninstall()
tool.Blender.update_viewport()
return {"FINISHED"}
@@ -463,7 +471,7 @@ class ChangeTypePage(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
props = context.scene.BIMModelProperties
bpy.ops.bim.load_type_thumbnails(ifc_class=props.type_class, offset=9 * (self.page - 1), limit=9)
bpy.ops.bim.load_type_thumbnails(ifc_class=props.ifc_class, offset=9 * (self.page - 1), limit=9)
props.type_page = self.page
return {"FINISHED"}
@@ -32,12 +32,6 @@ def get_ifc_class(self, context):
return AuthoringData.data["ifc_classes"]
def get_type_class(self, context):
if not AuthoringData.is_loaded:
AuthoringData.load()
return AuthoringData.data["type_class"]
def get_boundary_class(self, context):
if not AuthoringData.is_loaded:
AuthoringData.load()
@@ -50,18 +44,6 @@ def get_relating_type_id(self, context):
return AuthoringData.data["relating_type_id"]
def get_type_predefined_type(self, context):
if not AuthoringData.is_loaded:
AuthoringData.load()
return AuthoringData.data["type_predefined_type"]
def get_type_template(self, context):
if not AuthoringData.is_loaded:
AuthoringData.load()
return AuthoringData.data["type_template"]
def update_ifc_class(self, context):
bpy.ops.bim.load_type_thumbnails(ifc_class=self.ifc_class)
AuthoringData.data["relating_type_id"] = AuthoringData.relating_type_id()
@@ -69,19 +51,11 @@ def update_ifc_class(self, context):
if tool.Blender.get_enum_safe(self, "relating_type_id") is None:
self["relating_type_id"] = 0
def update_type_class(self, context):
AuthoringData.data["total_types"] = AuthoringData.total_types()
AuthoringData.data["total_pages"] = AuthoringData.total_pages()
AuthoringData.data["prev_page"] = AuthoringData.prev_page()
AuthoringData.data["next_page"] = AuthoringData.next_page()
AuthoringData.data["paginated_relating_types"] = AuthoringData.paginated_relating_types()
AuthoringData.data["type_predefined_type"] = AuthoringData.type_predefined_type()
AuthoringData.data["type_template"] = AuthoringData.type_template()
type_class = self.type_class
if (type_class, type_class, "") in get_ifc_class(self, context):
self.ifc_class = type_class
def update_relating_type_id(self, context):
@@ -173,9 +147,6 @@ class BIMModelProperties(PropertyGroup):
rl3: bpy.props.FloatProperty(name="RL", default=1, subtype="DISTANCE", description="Z offset for space calculation")
x_angle: bpy.props.FloatProperty(name="X Angle", default=0, subtype="ANGLE", min=-pi / 180 * 89, max=pi / 180 * 89)
type_page: bpy.props.IntProperty(name="Type Page", default=1, update=update_type_page)
type_template: bpy.props.EnumProperty(items=get_type_template, name="Type Template", default=0, options=set())
type_class: bpy.props.EnumProperty(items=get_type_class, name="IFC Class", update=update_type_class)
type_predefined_type: bpy.props.EnumProperty(items=get_type_predefined_type, name="Predefined Type", default=None)
type_name: bpy.props.StringProperty(name="Name", default="TYPEX")
boundary_class: bpy.props.EnumProperty(items=get_boundary_class, name="Boundary Class")
+1 -2
View File
@@ -100,7 +100,6 @@ class LaunchTypeManager(bpy.types.Operator):
# will be None if project has no types
if ifc_class is not None:
props.type_class = ifc_class
bpy.ops.bim.load_type_thumbnails(ifc_class=ifc_class, offset=0, limit=9)
return context.window_manager.invoke_props_dialog(
self, width=550, title="Type Manager", confirm_text="Add Type"
@@ -165,7 +164,7 @@ class LaunchTypeManager(bpy.types.Operator):
else:
row = box.row()
op = box.operator("bim.load_type_thumbnails", text="Load Thumbnails", icon="FILE_REFRESH")
op.ifc_class = props.type_class
op.ifc_class = props.ifc_class
class BIM_PT_authoring(Panel):
+50 -49
View File
@@ -20,6 +20,7 @@ import os
import sys
import bpy
import bpy.utils.previews
import bonsai.bim
import bonsai.tool as tool
import bonsai.core.model as core
from bonsai.bim.module.model.wall import DumbWallJoiner
@@ -339,10 +340,12 @@ class CreateObjectUI:
)
cls.layout.label(text=tool_name, icon="TOOL_SETTINGS")
cls.draw_type_manager_launcher(context)
cls.draw_add_object(context)
cls.draw_thumbnail() if context.region.type != "TOOL_HEADER" else cls
cls.draw_add_object_parameters(context)
if AuthoringData.data["ifc_classes"] and AuthoringData.data["relating_type_id"]:
cls.draw_add_object(context)
cls.draw_thumbnail() if context.region.type != "TOOL_HEADER" else cls
cls.draw_add_object_parameters(context)
else:
cls.draw_type_manager_launcher(context)
@classmethod
def draw_container_info(cls, context):
@@ -355,50 +358,46 @@ class CreateObjectUI:
@classmethod
def draw_type_manager_launcher(cls, context):
row = cls.layout.row(align=True)
if not AuthoringData.data["ifc_classes"]:
if AuthoringData.data["ifc_element_type"]:
row.label(text=f"No {AuthoringData.data['ifc_element_type']} Found", icon="ERROR")
row = cls.layout.row(align=True)
op = row.operator(
"bim.add_default_type",
icon_value=custom_icon_previews["ADD_TYPE"].icon_id,
text=f"Create default {AuthoringData.data['ifc_element_type']}",
)
op.ifc_element_type = AuthoringData.data["ifc_element_type"]
else:
row.label(text="No Element Types Found", icon="ERROR")
row.label(text="No Element Types Found", icon="ERROR")
row = cls.layout.row(align=True)
row.operator("bim.add_element", icon_value=bonsai.bim.icons["IFC"].icon_id, text="Add New Type")
if AuthoringData.data["ifc_element_type"]:
row = cls.layout.row(align=True)
row.operator("bim.launch_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, text="Launch Type Manager")
op = row.operator(
"bim.add_default_type",
icon_value=custom_icon_previews["ADD_TYPE"].icon_id,
text=f"Quick Create Default {AuthoringData.data['ifc_element_type']}",
)
op.ifc_element_type = AuthoringData.data["ifc_element_type"]
@classmethod
def draw_add_object(cls, context):
ui_context = str(context.region.type)
if AuthoringData.data["ifc_classes"]:
if not AuthoringData.data["ifc_element_type"]:
row = cls.layout.row(align=True)
prop_with_search(row, cls.props, "ifc_class", text="Type Class" if ui_context != "TOOL_HEADER" else "")
if AuthoringData.data["relating_type_id"]:
row = cls.layout.row(align=True)
box = row.box()
# This trick creates a fake dropdown
row2 = box.row(align=True)
row2.operator(
"bim.launch_type_manager",
icon="FILE_3D",
text=AuthoringData.data["relating_type_name"],
emboss=False,
)
row2.operator(
"bim.launch_type_manager",
icon="DOWNARROW_HLT",
text="",
emboss=False,
)
row = cls.layout.row(align=True) if ui_context != "TOOL_HEADER" else row
op = row.operator("bim.hotkey", text="Add", icon_value=custom_icon_previews["ADD"].icon_id)
op.hotkey = "S_A"
else:
row.label(text="No Construction Type", icon="FILE_3D")
if not AuthoringData.data["ifc_element_type"]:
row = cls.layout.row(align=True)
prop_with_search(row, cls.props, "ifc_class", text="Type Class" if ui_context != "TOOL_HEADER" else "")
if AuthoringData.data["relating_type_id"]:
row = cls.layout.row(align=True)
box = row.box()
# This trick creates a fake dropdown
row2 = box.row(align=True)
row2.operator(
"bim.launch_type_manager",
icon="FILE_3D",
text=AuthoringData.data["relating_type_name"],
emboss=False,
)
row2.operator(
"bim.launch_type_manager",
icon="DOWNARROW_HLT",
text="",
emboss=False,
)
row = cls.layout.row(align=True) if ui_context != "TOOL_HEADER" else row
op = row.operator("bim.hotkey", text="Add", icon_value=custom_icon_previews["ADD"].icon_id)
op.hotkey = "S_A"
else:
row.label(text="No Construction Type", icon="FILE_3D")
@classmethod
def draw_add_object_parameters(cls, context):
@@ -858,13 +857,15 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
props = bpy.context.scene.BIMModelProperties
if bpy.context.scene.BIMGeometryProperties.mode == "ITEM":
bpy.ops.wm.call_menu(name="BIM_MT_add_representation_item")
elif (
props.relating_type_id
and tool.Model.get_usage_type(tool.Ifc.get().by_id(int(props.relating_type_id))) == "LAYER2"
):
bpy.ops.bim.draw_polyline_wall("INVOKE_DEFAULT")
else:
bpy.ops.bim.add_occurrence("INVOKE_DEFAULT")
for obj in tool.Blender.get_selected_objects():
obj.select_set(False)
if (
relating_type_id := tool.Blender.get_enum_safe(props, "relating_type_id")
) and tool.Model.get_usage_type(tool.Ifc.get().by_id(int(relating_type_id))) == "LAYER2":
bpy.ops.bim.draw_polyline_wall("INVOKE_DEFAULT")
else:
bpy.ops.bim.add_occurrence("INVOKE_DEFAULT")
def hotkey_S_Q(self):
if not bpy.context.selected_objects:
@@ -20,7 +20,6 @@ import bpy
from . import ui, prop, operator
classes = (
operator.AddType,
operator.AssignType,
operator.AutoRenameOccurrences,
operator.DisableEditingType,
@@ -229,257 +229,6 @@ class SelectTypeObjects(bpy.types.Operator):
return {"FINISHED"}
class AddType(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_type"
bl_label = "Add Type"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMModelProperties
ifc_class = props.type_class
predefined_type = props.type_predefined_type
name = props.type_name
template = props.type_template
ifc_file = tool.Ifc.get()
body = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
if not body:
props.type_class = props.type_class
self.report({"ERROR"}, "No Model/Body/MODEL_VIEW context found.")
return {"FINISHED"}
if template == "MESH":
location = context.scene.cursor.location
if context.active_object and context.selected_objects and context.active_object.data:
obj = context.active_object
element = tool.Ifc.get_entity(obj)
if element:
mesh = obj.data.copy()
mesh.BIMMeshProperties.ifc_definition_id = 0
obj = bpy.data.objects.new(element.Name or name, mesh)
else:
mesh = bpy.data.meshes.new(name)
bm = bmesh.new()
bmesh.ops.create_cube(bm, size=1)
bm.to_mesh(mesh)
bm.free()
obj = bpy.data.objects.new(name, mesh)
obj.matrix_world.translation = location
bonsai.core.root.assign_class(
tool.Ifc,
tool.Collector,
tool.Root,
obj=obj,
ifc_class=ifc_class,
predefined_type=predefined_type,
should_add_representation=True,
context=body,
ifc_representation_class=None,
)
elif template in ("LAYERSET_AXIS2", "LAYERSET_AXIS3"):
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
obj = bpy.data.objects.new(name, None)
element = bonsai.core.root.assign_class(
tool.Ifc,
tool.Collector,
tool.Root,
obj=obj,
ifc_class=ifc_class,
predefined_type=predefined_type,
should_add_representation=True,
context=body,
ifc_representation_class=None,
)
materials = ifc_file.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", ifc_file, products=[element], type="IfcMaterialLayerSet"
)
layer_set = rel.RelatingMaterial
layer = ifcopenshell.api.run("material.add_layer", ifc_file, 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", ifc_file, product=element, name="EPset_Parametric")
if template == "LAYERSET_AXIS2":
axis = "AXIS2"
elif template == "LAYERSET_AXIS3":
axis = "AXIS3"
ifcopenshell.api.run("pset.edit_pset", ifc_file, pset=pset, properties={"LayerSetDirection": axis})
elif template == "PROFILESET" or template.startswith("FLOW_SEGMENT_"):
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
obj = bpy.data.objects.new(name, None)
element = bonsai.core.root.assign_class(
tool.Ifc,
tool.Collector,
tool.Root,
obj=obj,
ifc_class=ifc_class,
predefined_type=predefined_type,
should_add_representation=True,
context=body,
ifc_representation_class=None,
)
materials = ifc_file.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 template == "PROFILESET":
named_profiles = [p for p in ifc_file.by_type("IfcProfileDef") if p.ProfileName]
if named_profiles:
profile = named_profiles[0]
else:
size = 0.5 / unit_scale
profile = ifc_file.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 template == "FLOW_SEGMENT_RECTANGULAR":
default_x_dim = 0.4
default_y_dim = 0.2
profile_name = f"{ifc_class}-{default_x_dim*1000}x{default_y_dim*1000}"
profile = ifc_file.create_entity(
"IfcRectangleProfileDef",
ProfileName=profile_name,
ProfileType="AREA",
XDim=default_x_dim / unit_scale,
YDim=default_y_dim / unit_scale,
)
elif template == "FLOW_SEGMENT_CIRCULAR":
default_diameter = 0.1
profile_name = f"{ifc_class}-{default_diameter*1000}"
profile = ifc_file.create_entity(
"IfcCircleProfileDef",
ProfileName=profile_name,
ProfileType="AREA",
Radius=(default_diameter / 2) / unit_scale,
)
elif template == "FLOW_SEGMENT_CIRCULAR_HOLLOW":
default_diameter = 0.15
default_thickness = 0.005
profile_name = f"{ifc_class}-{default_diameter*1000}x{default_thickness*1000}"
profile = ifc_file.create_entity(
"IfcCircleHollowProfileDef",
ProfileName=profile_name,
ProfileType="AREA",
Radius=(default_diameter / 2) / unit_scale,
WallThickness=default_thickness,
)
rel = ifcopenshell.api.run(
"material.assign_material", ifc_file, products=[element], type="IfcMaterialProfileSet"
)
profile_set = rel.RelatingMaterial
material_profile = ifcopenshell.api.run(
"material.add_profile", ifc_file, profile_set=profile_set, material=material
)
ifcopenshell.api.run(
"material.assign_profile", ifc_file, material_profile=material_profile, profile=profile
)
elif template == "EMPTY":
obj = bpy.data.objects.new(name, None)
bonsai.core.root.assign_class(
tool.Ifc,
tool.Collector,
tool.Root,
obj=obj,
ifc_class=ifc_class,
predefined_type=predefined_type,
should_add_representation=True,
context=body,
ifc_representation_class=None,
)
elif template == "WINDOW":
mesh = bpy.data.meshes.new(name)
obj = bpy.data.objects.new(name, mesh)
element = bonsai.core.root.assign_class(
tool.Ifc,
tool.Collector,
tool.Root,
obj=obj,
predefined_type=predefined_type if tool.Ifc.get_schema() != "IFC2X3" else None,
ifc_class="IfcWindowType" if tool.Ifc.get_schema() != "IFC2X3" else "IfcWindowStyle",
should_add_representation=False,
)
with context.temp_override(active_object=obj):
bpy.ops.bim.add_window()
elif template == "DOOR":
mesh = bpy.data.meshes.new(name)
obj = bpy.data.objects.new(name, mesh)
element = bonsai.core.root.assign_class(
tool.Ifc,
tool.Collector,
tool.Root,
obj=obj,
predefined_type=predefined_type if tool.Ifc.get_schema() != "IFC2X3" else None,
ifc_class="IfcDoorType" if tool.Ifc.get_schema() != "IFC2X3" else "IfcDoorStyle",
should_add_representation=False,
)
with context.temp_override(active_object=obj):
bpy.ops.bim.add_door()
elif template == "STAIR":
mesh = bpy.data.meshes.new(name)
obj = bpy.data.objects.new(name, mesh)
element = bonsai.core.root.assign_class(
tool.Ifc,
tool.Collector,
tool.Root,
obj=obj,
predefined_type=predefined_type,
ifc_class=ifc_class,
should_add_representation=False,
)
with context.temp_override(active_object=obj):
bpy.ops.bim.add_stair()
elif template == "RAILING":
mesh = bpy.data.meshes.new(name)
obj = bpy.data.objects.new(name, mesh)
element = bonsai.core.root.assign_class(
tool.Ifc,
tool.Collector,
tool.Root,
obj=obj,
predefined_type=predefined_type,
ifc_class="IfcRailingType",
should_add_representation=True,
context=body,
)
with context.temp_override(active_object=obj):
bpy.ops.bim.add_railing()
elif template == "ROOF":
mesh = bpy.data.meshes.new(name)
obj = bpy.data.objects.new(name, mesh)
element = bonsai.core.root.assign_class(
tool.Ifc,
tool.Collector,
tool.Root,
obj=obj,
predefined_type=predefined_type,
ifc_class="IfcRoofType",
should_add_representation=True,
context=body,
)
with context.temp_override(active_object=obj):
bpy.ops.bim.add_roof()
bpy.ops.bim.load_type_thumbnails(ifc_class=ifc_class)
props.type_class = props.type_class
return {"FINISHED"}
class RemoveType(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_type"
bl_label = "Remove Type"
+5 -5
View File
@@ -144,11 +144,11 @@ class Blender(bonsai.core.tool.Blender):
@classmethod
def get_selected_objects(cls) -> set[bpy.types.Object]:
if bpy.context.selected_objects:
if active_obj := bpy.context.active_object:
return set(bpy.context.selected_objects + [active_obj])
return set(bpy.context.selected_objects)
if active_obj := bpy.context.active_object:
if selected_objects := getattr(bpy.context, "selected_objects", None):
if active_obj := cls.get_active_object():
return set(selected_objects + [active_obj])
return set(selected_objects)
if active_obj := cls.get_active_object():
return {active_obj}
return set()