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_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["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["predefined_type"] = cls.predefined_type() # only after .relating_type_id()
cls.data["type_class"] = cls.type_class()
# only after .type_class() # only after .ifc_classes()
cls.data["type_predefined_type"] = cls.type_predefined_type()
cls.data["total_types"] = cls.total_types() cls.data["total_types"] = cls.total_types()
cls.data["total_pages"] = cls.total_pages() cls.data["total_pages"] = cls.total_pages()
cls.data["next_page"] = cls.next_page() cls.data["next_page"] = cls.next_page()
@@ -78,7 +76,6 @@ class AuthoringData:
cls.data["active_representation_type"] = cls.active_representation_type() cls.data["active_representation_type"] = cls.active_representation_type()
cls.data["boundary_class"] = cls.boundary_class() cls.data["boundary_class"] = cls.boundary_class()
cls.data["selected_material_usages"] = cls.selected_material_usages() cls.data["selected_material_usages"] = cls.selected_material_usages()
cls.data["type_template"] = cls.type_template()
@classmethod @classmethod
def default_container(cls) -> str | None: def default_container(cls) -> str | None:
@@ -97,41 +94,6 @@ class AuthoringData:
version = tool.Ifc.get_schema() version = tool.Ifc.get_schema()
return [(c, c, get_entity_doc(version, c).get("description", "")) for c in sorted(names)] 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 @classmethod
def type_thumbnail(cls): def type_thumbnail(cls):
if not cls.data["relating_type_id"]: if not cls.data["relating_type_id"]:
@@ -142,76 +104,15 @@ class AuthoringData:
element = tool.Ifc.get().by_id(int(relating_type_id)) element = tool.Ifc.get().by_id(int(relating_type_id))
return cls.type_thumbnails.get(element.id(), None) or 0 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 @classmethod
def total_types(cls): def total_types(cls):
type_class = cls.props.type_class ifc_class = cls.props.ifc_class
return len(tool.Ifc.get().by_type(type_class)) if type_class else 0 return len(tool.Ifc.get().by_type(ifc_class)) if ifc_class else 0
@classmethod @classmethod
def total_pages(cls): def total_pages(cls):
type_class = cls.props.type_class ifc_class = cls.props.ifc_class
total_types = len(tool.Ifc.get().by_type(type_class)) if type_class else 0 total_types = len(tool.Ifc.get().by_type(ifc_class)) if ifc_class else 0
return math.ceil(total_types / cls.types_per_page) return math.ceil(total_types / cls.types_per_page)
@classmethod @classmethod
@@ -226,11 +127,11 @@ class AuthoringData:
@classmethod @classmethod
def paginated_relating_types(cls): def paginated_relating_types(cls):
type_class = cls.props.type_class ifc_class = cls.props.ifc_class
if not type_class: if not ifc_class:
return [] return []
results = [] 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] elements = elements[(cls.props.type_page - 1) * cls.types_per_page : cls.props.type_page * cls.types_per_page]
for element in elements: for element in elements:
predefined_type = ifcopenshell.util.element.get_predefined_type(element) predefined_type = ifcopenshell.util.element.get_predefined_type(element)
@@ -250,45 +151,50 @@ class AuthoringData:
@classmethod @classmethod
def is_voidable_element(cls): def is_voidable_element(cls):
element = tool.Ifc.get_entity(bpy.context.active_object) if active_object := tool.Blender.get_active_object():
return element and element.is_a("IfcElement") and not element.is_a("IfcOpeningElement") element = tool.Ifc.get_entity(active_object)
return element and element.is_a("IfcElement") and not element.is_a("IfcOpeningElement")
@classmethod @classmethod
def has_visible_openings(cls): def has_visible_openings(cls):
element = tool.Ifc.get_entity(bpy.context.active_object) if active_object := tool.Blender.get_active_object():
if element and element.is_a("IfcElement") and not element.is_a("IfcOpeningElement"): element = tool.Ifc.get_entity(active_object)
for opening in [r.RelatedOpeningElement for r in element.HasOpenings]: if element and element.is_a("IfcElement") and not element.is_a("IfcOpeningElement"):
if tool.Ifc.get_object(opening): for opening in [r.RelatedOpeningElement for r in element.HasOpenings]:
return True if tool.Ifc.get_object(opening):
return True
return False return False
@classmethod @classmethod
def has_visible_boundaries(cls): def has_visible_boundaries(cls):
element = tool.Ifc.get_entity(bpy.context.active_object) if active_object := tool.Blender.get_active_object():
if element: element = tool.Ifc.get_entity(active_object)
if element.is_a("IfcRelSpaceBoundary"): if element:
return True if element.is_a("IfcRelSpaceBoundary"):
for boundary in getattr(element, "BoundedBy", []):
if tool.Ifc.get_object(boundary):
return True return True
for boundary in getattr(element, "BoundedBy", []):
if tool.Ifc.get_object(boundary):
return True
return False return False
@classmethod @classmethod
def active_class(cls): def active_class(cls):
element = tool.Ifc.get_entity(bpy.context.active_object) if active_object := tool.Blender.get_active_object():
if element: element = tool.Ifc.get_entity(active_object)
return element.is_a() if element:
return element.is_a()
@classmethod @classmethod
def active_material_usage(cls): def active_material_usage(cls):
element = tool.Ifc.get_entity(bpy.context.active_object) if active_object := tool.Blender.get_active_object():
if element: element = tool.Ifc.get_entity(active_object)
return tool.Model.get_usage_type(element) if element:
return tool.Model.get_usage_type(element)
@classmethod @classmethod
def active_representation_type(cls): def active_representation_type(cls):
if bpy.context.active_object: if active_object := tool.Blender.get_active_object():
representation = tool.Geometry.get_active_representation(bpy.context.active_object) representation = tool.Geometry.get_active_representation(active_object)
if representation and representation.is_a("IfcShapeRepresentation"): if representation and representation.is_a("IfcShapeRepresentation"):
representation = tool.Geometry.resolve_mapped_representation(representation) representation = tool.Geometry.resolve_mapped_representation(representation)
return representation.RepresentationType return representation.RepresentationType
@@ -317,7 +223,7 @@ class AuthoringData:
if not ifc_classes: if not ifc_classes:
return [] return []
results = [] 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: if not ifc_class and ifc_classes:
ifc_class = ifc_classes[0][0] ifc_class = ifc_classes[0][0]
if ifc_class: if ifc_class:
@@ -328,7 +234,11 @@ class AuthoringData:
@classmethod @classmethod
def relating_type_name(cls): 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" return tool.Ifc.get().by_id(int(relating_type_id)).Name or "Unnamed"
@classmethod @classmethod
@@ -345,7 +255,7 @@ class AuthoringData:
@classmethod @classmethod
def selected_material_usages(cls): def selected_material_usages(cls):
selected_usages = {} selected_usages = {}
for obj in bpy.context.selected_objects: for obj in tool.Blender.get_selected_objects():
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
if not element: if not element:
continue continue
+1 -1
View File
@@ -516,7 +516,7 @@ class MEPGenerator:
break break
def create_obstruction_type(self, segment): 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) profile_set = ifcopenshell.util.element.get_material(segment, should_skip_usage=True)
material_profile = profile_set.MaterialProfiles[0] material_profile = profile_set.MaterialProfiles[0]
profile = material_profile.Profile 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() ifc_element_type: bpy.props.StringProperty()
def _execute(self, context): def _execute(self, context):
props = context.scene.BIMModelProperties props = context.scene.BIMRootProperties
ifc_file = tool.Ifc.get() props.ifc_product = "IfcElementType"
props.type_class = self.ifc_element_type props.ifc_class = self.ifc_element_type
if self.ifc_element_type == "IfcWallType": if self.ifc_element_type == "IfcWallType":
if ifc_file.schema == "IFC2X3": if tool.Ifc.get().schema == "IFC2X3":
props.type_predefined_type = "STANDARD" props.ifc_predefined_type = "STANDARD"
else: else:
props.type_predefined_type = "SOLIDWALL" props.ifc_predefined_type = "SOLIDWALL"
props.type_template = "LAYERSET_AXIS2" props.representation_template = "LAYERSET_AXIS2"
elif self.ifc_element_type == "IfcSlabType": elif self.ifc_element_type == "IfcSlabType":
props.type_predefined_type = "FLOOR" props.ifc_predefined_type = "FLOOR"
props.type_template = "LAYERSET_AXIS3" props.representation_template = "LAYERSET_AXIS3"
elif self.ifc_element_type == "IfcDoorType": elif self.ifc_element_type == "IfcDoorType":
props.type_predefined_type = "DOOR" props.ifc_predefined_type = "DOOR"
props.type_template = "DOOR" props.representation_template = "DOOR"
elif self.ifc_element_type == "IfcWindowType": elif self.ifc_element_type == "IfcWindowType":
props.type_predefined_type = "WINDOW" props.ifc_predefined_type = "WINDOW"
props.type_template = "WINDOW" props.representation_template = "WINDOW"
elif self.ifc_element_type == "IfcColumnType": elif self.ifc_element_type == "IfcColumnType":
props.type_predefined_type = "COLUMN" props.ifc_predefined_type = "COLUMN"
props.type_template = "PROFILESET" props.representation_template = "PROFILESET"
elif self.ifc_element_type == "IfcBeamType": elif self.ifc_element_type == "IfcBeamType":
props.type_predefined_type = "BEAM" props.ifc_predefined_type = "BEAM"
props.type_template = "PROFILESET" props.representation_template = "PROFILESET"
elif self.ifc_element_type == "IfcDuctSegmentType": elif self.ifc_element_type == "IfcDuctSegmentType":
props.type_predefined_type = "RIGIDSEGMENT" props.ifc_predefined_type = "RIGIDSEGMENT"
props.type_template = "FLOW_SEGMENT_RECTANGULAR" props.representation_template = "FLOW_SEGMENT_RECTANGULAR"
elif self.ifc_element_type == "IfcPipeSegmentType": elif self.ifc_element_type == "IfcPipeSegmentType":
props.type_predefined_type = "RIGIDSEGMENT" props.ifc_predefined_type = "RIGIDSEGMENT"
props.type_template = "FLOW_SEGMENT_CIRCULAR" props.representation_template = "FLOW_SEGMENT_CIRCULAR"
bpy.ops.bim.add_type() bpy.ops.bim.add_element()
class AddOccurrence(bpy.types.Operator, PolylineOperator): class AddOccurrence(bpy.types.Operator, PolylineOperator):
@@ -111,10 +111,6 @@ class AddOccurrence(bpy.types.Operator, PolylineOperator):
def __init__(self): def __init__(self):
super().__init__() 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): def create_occurrence(self, context, event):
if not self.relating_type: if not self.relating_type:
@@ -151,8 +147,20 @@ class AddOccurrence(bpy.types.Operator, PolylineOperator):
snap_obj.select_set(False) snap_obj.select_set(False)
def modal(self, context, event): 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: 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() PolylineDecorator.uninstall()
tool.Blender.update_viewport() tool.Blender.update_viewport()
return {"FINISHED"} return {"FINISHED"}
@@ -463,7 +471,7 @@ class ChangeTypePage(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context): def _execute(self, context):
props = context.scene.BIMModelProperties 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 props.type_page = self.page
return {"FINISHED"} return {"FINISHED"}
@@ -32,12 +32,6 @@ def get_ifc_class(self, context):
return AuthoringData.data["ifc_classes"] 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): def get_boundary_class(self, context):
if not AuthoringData.is_loaded: if not AuthoringData.is_loaded:
AuthoringData.load() AuthoringData.load()
@@ -50,18 +44,6 @@ def get_relating_type_id(self, context):
return AuthoringData.data["relating_type_id"] 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): def update_ifc_class(self, context):
bpy.ops.bim.load_type_thumbnails(ifc_class=self.ifc_class) bpy.ops.bim.load_type_thumbnails(ifc_class=self.ifc_class)
AuthoringData.data["relating_type_id"] = AuthoringData.relating_type_id() 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: if tool.Blender.get_enum_safe(self, "relating_type_id") is None:
self["relating_type_id"] = 0 self["relating_type_id"] = 0
def update_type_class(self, context):
AuthoringData.data["total_types"] = AuthoringData.total_types() AuthoringData.data["total_types"] = AuthoringData.total_types()
AuthoringData.data["total_pages"] = AuthoringData.total_pages() AuthoringData.data["total_pages"] = AuthoringData.total_pages()
AuthoringData.data["prev_page"] = AuthoringData.prev_page() AuthoringData.data["prev_page"] = AuthoringData.prev_page()
AuthoringData.data["next_page"] = AuthoringData.next_page() AuthoringData.data["next_page"] = AuthoringData.next_page()
AuthoringData.data["paginated_relating_types"] = AuthoringData.paginated_relating_types() 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): 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") 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) 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_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") type_name: bpy.props.StringProperty(name="Name", default="TYPEX")
boundary_class: bpy.props.EnumProperty(items=get_boundary_class, name="Boundary Class") 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 # will be None if project has no types
if ifc_class is not None: 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) bpy.ops.bim.load_type_thumbnails(ifc_class=ifc_class, offset=0, limit=9)
return context.window_manager.invoke_props_dialog( return context.window_manager.invoke_props_dialog(
self, width=550, title="Type Manager", confirm_text="Add Type" self, width=550, title="Type Manager", confirm_text="Add Type"
@@ -165,7 +164,7 @@ class LaunchTypeManager(bpy.types.Operator):
else: else:
row = box.row() row = box.row()
op = box.operator("bim.load_type_thumbnails", text="Load Thumbnails", icon="FILE_REFRESH") 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): class BIM_PT_authoring(Panel):
+50 -49
View File
@@ -20,6 +20,7 @@ import os
import sys import sys
import bpy import bpy
import bpy.utils.previews import bpy.utils.previews
import bonsai.bim
import bonsai.tool as tool import bonsai.tool as tool
import bonsai.core.model as core import bonsai.core.model as core
from bonsai.bim.module.model.wall import DumbWallJoiner from bonsai.bim.module.model.wall import DumbWallJoiner
@@ -339,10 +340,12 @@ class CreateObjectUI:
) )
cls.layout.label(text=tool_name, icon="TOOL_SETTINGS") cls.layout.label(text=tool_name, icon="TOOL_SETTINGS")
cls.draw_type_manager_launcher(context) if AuthoringData.data["ifc_classes"] and AuthoringData.data["relating_type_id"]:
cls.draw_add_object(context) cls.draw_add_object(context)
cls.draw_thumbnail() if context.region.type != "TOOL_HEADER" else cls cls.draw_thumbnail() if context.region.type != "TOOL_HEADER" else cls
cls.draw_add_object_parameters(context) cls.draw_add_object_parameters(context)
else:
cls.draw_type_manager_launcher(context)
@classmethod @classmethod
def draw_container_info(cls, context): def draw_container_info(cls, context):
@@ -355,50 +358,46 @@ class CreateObjectUI:
@classmethod @classmethod
def draw_type_manager_launcher(cls, context): def draw_type_manager_launcher(cls, context):
row = cls.layout.row(align=True) row = cls.layout.row(align=True)
if not AuthoringData.data["ifc_classes"]: row.label(text="No Element Types Found", icon="ERROR")
if AuthoringData.data["ifc_element_type"]: row = cls.layout.row(align=True)
row.label(text=f"No {AuthoringData.data['ifc_element_type']} Found", icon="ERROR") row.operator("bim.add_element", icon_value=bonsai.bim.icons["IFC"].icon_id, text="Add New Type")
row = cls.layout.row(align=True) if AuthoringData.data["ifc_element_type"]:
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 = cls.layout.row(align=True) 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 @classmethod
def draw_add_object(cls, context): def draw_add_object(cls, context):
ui_context = str(context.region.type) ui_context = str(context.region.type)
if AuthoringData.data["ifc_classes"]: if not AuthoringData.data["ifc_element_type"]:
if not AuthoringData.data["ifc_element_type"]: row = cls.layout.row(align=True)
row = cls.layout.row(align=True) prop_with_search(row, cls.props, "ifc_class", text="Type Class" if ui_context != "TOOL_HEADER" else "")
prop_with_search(row, cls.props, "ifc_class", text="Type Class" if ui_context != "TOOL_HEADER" else "") if AuthoringData.data["relating_type_id"]:
if AuthoringData.data["relating_type_id"]: row = cls.layout.row(align=True)
row = cls.layout.row(align=True) box = row.box()
box = row.box() # This trick creates a fake dropdown
# This trick creates a fake dropdown row2 = box.row(align=True)
row2 = box.row(align=True) row2.operator(
row2.operator( "bim.launch_type_manager",
"bim.launch_type_manager", icon="FILE_3D",
icon="FILE_3D", text=AuthoringData.data["relating_type_name"],
text=AuthoringData.data["relating_type_name"], emboss=False,
emboss=False, )
) row2.operator(
row2.operator( "bim.launch_type_manager",
"bim.launch_type_manager", icon="DOWNARROW_HLT",
icon="DOWNARROW_HLT", text="",
text="", emboss=False,
emboss=False, )
) row = cls.layout.row(align=True) if ui_context != "TOOL_HEADER" else row
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 = row.operator("bim.hotkey", text="Add", icon_value=custom_icon_previews["ADD"].icon_id) op.hotkey = "S_A"
op.hotkey = "S_A" else:
else: row.label(text="No Construction Type", icon="FILE_3D")
row.label(text="No Construction Type", icon="FILE_3D")
@classmethod @classmethod
def draw_add_object_parameters(cls, context): def draw_add_object_parameters(cls, context):
@@ -858,13 +857,15 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
props = bpy.context.scene.BIMModelProperties props = bpy.context.scene.BIMModelProperties
if bpy.context.scene.BIMGeometryProperties.mode == "ITEM": if bpy.context.scene.BIMGeometryProperties.mode == "ITEM":
bpy.ops.wm.call_menu(name="BIM_MT_add_representation_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: 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): def hotkey_S_Q(self):
if not bpy.context.selected_objects: if not bpy.context.selected_objects:
@@ -20,7 +20,6 @@ import bpy
from . import ui, prop, operator from . import ui, prop, operator
classes = ( classes = (
operator.AddType,
operator.AssignType, operator.AssignType,
operator.AutoRenameOccurrences, operator.AutoRenameOccurrences,
operator.DisableEditingType, operator.DisableEditingType,
@@ -229,257 +229,6 @@ class SelectTypeObjects(bpy.types.Operator):
return {"FINISHED"} 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): class RemoveType(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_type" bl_idname = "bim.remove_type"
bl_label = "Remove Type" bl_label = "Remove Type"
+5 -5
View File
@@ -144,11 +144,11 @@ class Blender(bonsai.core.tool.Blender):
@classmethod @classmethod
def get_selected_objects(cls) -> set[bpy.types.Object]: def get_selected_objects(cls) -> set[bpy.types.Object]:
if bpy.context.selected_objects: if selected_objects := getattr(bpy.context, "selected_objects", None):
if active_obj := bpy.context.active_object: if active_obj := cls.get_active_object():
return set(bpy.context.selected_objects + [active_obj]) return set(selected_objects + [active_obj])
return set(bpy.context.selected_objects) return set(selected_objects)
if active_obj := bpy.context.active_object: if active_obj := cls.get_active_object():
return {active_obj} return {active_obj}
return set() return set()