This commit is contained in:
Andrej730
2025-02-25 11:43:29 +05:00
parent 5f4098e5d4
commit 2f6ae1745f
87 changed files with 739 additions and 448 deletions
+1 -1
View File
@@ -137,7 +137,7 @@ class IfcExporter:
return checksum != tool.Geometry.get_material_checksum(obj)
def sync_object_placement(self, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
element = self.file.by_id(tool.Blender.get_object_bim_props(obj).ifc_definition_id)
if tool.Geometry.is_scaled(obj):
bpy.ops.bim.update_representation(obj=obj.name)
# update_representation might not apply scale if the object has openings
+7 -6
View File
@@ -64,14 +64,15 @@ def name_callback(obj: Union[bpy.types.Object, bpy.types.Material], data: str) -
refresh_ui_data()
return
if not obj.BIMObjectProperties.ifc_definition_id:
props = tool.Blender.get_object_bim_props(obj)
if not props.ifc_definition_id:
return
if obj.BIMObjectProperties.is_renaming:
obj.BIMObjectProperties.is_renaming = False
if props.is_renaming:
props.is_renaming = False
return
element = tool.Ifc.get().by_id(obj.BIMObjectProperties.ifc_definition_id)
element = tool.Ifc.get().by_id(props.ifc_definition_id)
if "/" in obj.name:
object_name = obj.name
element_name = obj.name.split("/", 1)[1]
@@ -87,8 +88,8 @@ def name_callback(obj: Union[bpy.types.Object, bpy.types.Material], data: str) -
if not element.is_a("IfcRoot"):
return
element.Name = element_name
if obj.BIMObjectProperties.collection:
obj.BIMObjectProperties.collection.name = object_name
if props.collection:
props.collection.name = object_name
refresh_ui_data()
+4 -2
View File
@@ -267,7 +267,8 @@ class IfcStore:
if element.is_a("IfcSurfaceStyle"):
obj.BIMStyleProperties.ifc_definition_id = element.id()
else:
obj.BIMObjectProperties.ifc_definition_id = element.id()
props = tool.Blender.get_object_bim_props(obj)
props.ifc_definition_id = element.id()
tool.Ifc.setup_listeners(obj)
@@ -407,7 +408,8 @@ class IfcStore:
if isinstance(obj, bpy.types.Material):
obj.BIMStyleProperties.ifc_definition_id = 0
else: # bpy.types.Object
obj.BIMObjectProperties.ifc_definition_id = 0
props = tool.Blender.get_object_bim_props(obj)
props.ifc_definition_id = 0
@staticmethod
def execute_ifc_operator(
+4 -2
View File
@@ -933,7 +933,8 @@ class IfcImporter:
self.project = {"ifc": project}
obj = tool.Ifc.get_object(project)
if obj:
self.project["blender"] = obj.BIMObjectProperties.collection
props = tool.Blender.get_object_bim_props(obj)
self.project["blender"] = props.collection
self.has_existing_project = True
return
self.project["blender"] = bpy.data.collections.new(
@@ -943,7 +944,8 @@ class IfcImporter:
obj.hide_select = True
self.project["blender"].objects.link(obj)
self.project["blender"].BIMCollectionProperties.obj = obj
obj.BIMObjectProperties.collection = self.collections[project.GlobalId] = self.project["blender"]
props = tool.Blender.get_object_bim_props(obj)
props.collection = self.collections[project.GlobalId] = self.project["blender"]
def create_styles(self) -> None:
for style in self.file.by_type("IfcSurfaceStyle"):
+6 -6
View File
@@ -34,9 +34,9 @@ class BIM_PT_aggregate(Panel):
@classmethod
def poll(cls, context):
if not context.active_object:
if not (obj := context.active_object):
return False
props = context.active_object.BIMObjectProperties
props = tool.Blender.get_object_bim_props(obj)
if not props.ifc_definition_id:
return False
if not tool.Ifc.get_object_by_identifier(props.ifc_definition_id):
@@ -66,9 +66,9 @@ class BIM_PT_aggregate(Panel):
col.enabled = False
op = col.operator("bim.aggregate_assign_object", icon="CHECKMARK")
if props.relating_object:
op.relating_object = props.relating_object.BIMObjectProperties.ifc_definition_id
op.relating_object = tool.Blender.get_object_bim_props(props.relating_object).ifc_definition_id
elif props.related_object:
op.related_object = props.related_object.BIMObjectProperties.ifc_definition_id
op.related_object = tool.Blender.get_object_bim_props(props.related_object).ifc_definition_id
row.operator("bim.disable_editing_aggregate", icon="CANCEL", text="")
return
else:
@@ -115,9 +115,9 @@ class BIM_PT_linked_aggregate(Panel):
@classmethod
def poll(cls, context):
if not context.active_object:
if not (obj := context.active_object):
return False
props = context.active_object.BIMObjectProperties
props = tool.Blender.get_object_bim_props(obj)
if not props.ifc_definition_id:
return False
if not tool.Ifc.get_object_by_identifier(props.ifc_definition_id):
@@ -316,7 +316,8 @@ class ColourByRelatedBuildingElement(bpy.types.Operator):
def _execute(self, context):
for obj in context.visible_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
props = tool.Blender.get_object_bim_props(obj)
if not props.ifc_definition_id:
continue
element = tool.Ifc.get_entity(obj)
if not element.is_a("IfcRelSpaceBoundary"):
+8 -6
View File
@@ -52,9 +52,9 @@ class BIM_PT_Boundary(Panel):
@classmethod
def poll(cls, context):
if not context.active_object:
if not (obj := context.active_object):
return False
props = context.active_object.BIMObjectProperties
props = tool.Blender.get_object_bim_props(obj)
if not props.ifc_definition_id:
return False
if not tool.Ifc.get_object_by_identifier(props.ifc_definition_id):
@@ -65,7 +65,7 @@ class BIM_PT_Boundary(Panel):
def draw(self, context):
obj = context.active_object
assert obj
props = obj.BIMObjectProperties
props = tool.Blender.get_object_bim_props(obj)
ifc_file = tool.Ifc.get()
boundary = ifc_file.by_id(props.ifc_definition_id)
self.bprops = tool.Boundary.get_object_boundary_props(obj)
@@ -128,9 +128,9 @@ class BIM_PT_SpaceBoundaries(Panel):
@classmethod
def poll(cls, context):
if not context.active_object:
if not (obj := context.active_object):
return False
props = context.active_object.BIMObjectProperties
props = tool.Blender.get_object_bim_props(obj)
if not props.ifc_definition_id:
return False
if not tool.Ifc.get_object_by_identifier(props.ifc_definition_id):
@@ -145,7 +145,9 @@ class BIM_PT_SpaceBoundaries(Panel):
if not SpaceBoundariesData.is_loaded:
SpaceBoundariesData.load()
self.props = context.active_object.BIMObjectProperties
obj = context.active_object
assert obj
self.props = tool.Blender.get_object_bim_props(obj)
self.ifc_file = tool.Ifc.get()
row = self.layout.row()
row.operator("bim.load_space_boundaries")
+9
View File
@@ -20,6 +20,7 @@ import bpy
from bonsai.bim.module.model.data import AuthoringData
from bpy.types import PropertyGroup
from math import pi
from typing import TYPE_CHECKING
class BIMCadProperties(PropertyGroup):
@@ -31,3 +32,11 @@ class BIMCadProperties(PropertyGroup):
gable_roof_edge_angle: bpy.props.FloatProperty(
name="Gable Roof Edge Angle", default=pi / 2, soft_min=0, soft_max=pi / 2, subtype="ANGLE"
)
if TYPE_CHECKING:
resolution: int
radius: float
distance: float
x: float
y: float
gable_roof_edge_angle: float
+12 -6
View File
@@ -146,7 +146,7 @@ class CadTool(WorkSpaceTool):
if (
(RailingData.is_loaded or not RailingData.load())
and RailingData.data["pset_data"]
and obj.BIMRailingProperties.is_editing_path
and tool.Model.get_railing_props(obj).is_editing_path
):
add_header_apply_button(
layout,
@@ -159,7 +159,7 @@ class CadTool(WorkSpaceTool):
elif (
(RoofData.is_loaded or not RoofData.load())
and RoofData.data["pset_data"]
and obj.BIMRoofProperties.is_editing_path
and tool.Model.get_roof_props(obj).is_editing_path
):
add_header_apply_button(
layout, "Edit Roof Path", "bim.finish_editing_roof_path", "bim.cancel_editing_roof_path", ui_context
@@ -195,12 +195,14 @@ class CadHotkey(bpy.types.Operator):
return operator.description or ""
def execute(self, context):
self.props = context.scene.BIMCadProperties
self.props = tool.Cad.get_cad_props()
getattr(self, f"hotkey_{self.hotkey}")()
return {"FINISHED"}
def draw(self, context):
props = context.scene.BIMCadProperties
props = tool.Cad.get_cad_props()
obj = context.active_object
if self.hotkey == "S_C":
if tool.Geometry.is_profile_object_active():
row = self.layout.row()
@@ -226,7 +228,7 @@ class CadHotkey(bpy.types.Operator):
elif (
(RoofData.is_loaded or not RoofData.load())
and RoofData.data["pset_data"]
and bpy.context.active_object.BIMRoofProperties.is_editing_path
and tool.Model.get_roof_props(obj).is_editing_path
):
self.layout.row().prop(props, "gable_roof_edge_angle")
@@ -281,13 +283,17 @@ class CadHotkey(bpy.types.Operator):
bpy.ops.bim.edit_extrusion_axis()
def hotkey_S_R(self):
obj = bpy.context.active_object
if not obj:
return
if tool.Geometry.is_profile_object_active():
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
bpy.ops.bim.add_rectangle(x=self.props.x / si_conversion, y=self.props.y / si_conversion)
elif (
(RoofData.is_loaded or not RoofData.load())
and RoofData.data["pset_data"]
and bpy.context.active_object.BIMRoofProperties.is_editing_path
and tool.Model.get_roof_props(obj).is_editing_path
):
bpy.ops.bim.set_gable_roof_edge_angle(angle=self.props.gable_roof_edge_angle)
@@ -316,7 +316,8 @@ class SelectIfcClashResults(bpy.types.Operator):
global_ids.extend([clash["a_global_id"], clash["b_global_id"]])
for obj in context.visible_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
props = tool.Blender.get_object_bim_props(obj)
if not props.ifc_definition_id:
continue
ifc_file = ""
@@ -335,7 +336,7 @@ class SelectIfcClashResults(bpy.types.Operator):
element_file = self.file
try:
element = element_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
element = element_file.by_id(props.ifc_definition_id)
except:
continue
@@ -161,8 +161,12 @@ class AssignConstraint(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
self.file = tool.Ifc.get()
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
products = [self.file.by_id(obj_id) for obj in objs if (obj_id := obj.BIMObjectProperties.ifc_definition_id)]
objs = [bpy.data.objects[self.obj]] if self.obj else context.selected_objects
products = [
self.file.by_id(obj_id)
for obj in objs
if (obj_id := tool.Blender.get_object_bim_props(obj).ifc_definition_id)
]
if products:
ifcopenshell.api.run(
"constraint.assign_constraint",
@@ -184,8 +188,12 @@ class UnassignConstraint(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
self.file = tool.Ifc.get()
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
products = [self.file.by_id(obj_id) for obj in objs if (obj_id := obj.BIMObjectProperties.ifc_definition_id)]
objs = [bpy.data.objects[self.obj]] if self.obj else context.selected_objects
products = [
self.file.by_id(obj_id)
for obj in objs
if (obj_id := tool.Blender.get_object_bim_props(obj).ifc_definition_id)
]
if products:
ifcopenshell.api.run(
"constraint.unassign_constraint",
@@ -79,18 +79,18 @@ class BIM_PT_object_constraints(Panel):
@classmethod
def poll(cls, context):
if not context.active_object:
if not (obj := context.active_object):
return False
if not tool.Ifc.get_object_by_identifier(context.active_object.BIMObjectProperties.ifc_definition_id):
return False
return bool(context.active_object.BIMObjectProperties.ifc_definition_id)
props = tool.Blender.get_object_bim_props(obj)
return bool(tool.Ifc.get_object_by_identifier(props.ifc_definition_id))
def draw(self, context):
if not ObjectConstraintsData.is_loaded:
ObjectConstraintsData.load()
obj = context.active_object
self.oprops = obj.BIMObjectProperties
assert obj
self.oprops = tool.Blender.get_object_bim_props(obj)
self.sprops = context.scene.BIMConstraintProperties
self.props = obj.BIMObjectConstraintProperties
self.file = tool.Ifc.get()
@@ -740,15 +740,14 @@ class LoadProductCostItems(bpy.types.Operator):
@classmethod
def poll(cls, context):
if not tool.Ifc.get() or not (obj := context.active_object) or not (obj.BIMObjectProperties.ifc_definition_id):
if not tool.Ifc.get() or not (obj := context.active_object) or not (tool.Blender.get_ifc_definition_id(obj)):
cls.poll_message_set("No IFC object is active.")
return False
return True
def execute(self, context):
core.load_product_cost_items(
tool.Cost, product=tool.Ifc.get().by_id(context.active_object.BIMObjectProperties.ifc_definition_id)
)
obj = context.active_object
core.load_product_cost_items(tool.Cost, product=tool.Ifc.get_entity(obj))
return {"FINISHED"}
@@ -18,9 +18,13 @@
import bpy
from bpy.types import PropertyGroup
from typing import TYPE_CHECKING
class BIMCoveringProperties(PropertyGroup):
ceiling_height: bpy.props.FloatProperty(
name="ceiling_height", default=2.7, subtype="DISTANCE", description="Ceiling height"
)
if TYPE_CHECKING:
ceiling_height: float
@@ -55,7 +55,7 @@ class CoveringToolUI:
def draw(cls, context, layout, ifc_element_type=None):
cls.layout = layout
cls.props = tool.Model.get_model_props()
cls.covering_props = context.scene.BIMCoveringProperties
cls.covering_props = tool.Covering.get_covering_props()
row = cls.layout.row(align=True)
if not tool.Ifc.get():
@@ -159,12 +159,11 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
return operator.description or ""
def _execute(self, context):
# self.props = context.scene.BIMCoveringProperties
self.props = tool.Covering.get_covering_props()
getattr(self, f"hotkey_{self.hotkey}")()
def invoke(self, context, event):
# https://blender.stackexchange.com/questions/276035/how-do-i-make-operators-remember-their-property-values-when-called-from-a-hotkey
# self.props = context.scene.BIMSpatialProperties
return self.execute(context)
def draw(self, context):
@@ -181,12 +181,12 @@ class RunAnalysis(bpy.types.Operator):
if modifier.type == "TRIANGULATE":
return True
def get_covetool_category(self, obj):
def get_covetool_category(self, obj: bpy.types.Object):
if not hasattr(obj, "data") or not isinstance(obj.data, bpy.types.Mesh):
return
if not obj.BIMObjectProperties.ifc_definition_id:
if not (ifc_id := tool.Blender.get_ifc_definition_id(obj)):
return
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
element = self.file.by_id(ifc_id)
ifc_class = element.is_a()
if "IfcSlab" in ifc_class:
return "floors"
@@ -382,7 +382,7 @@ class InspectFromObject(bpy.types.Operator):
def get_active_object_ifc_definition(cls, context: bpy.types.Context) -> Union[int, None]:
obj = context.active_object
assert obj
if ifc_id := obj.BIMObjectProperties.ifc_definition_id:
if ifc_id := tool.Blender.get_ifc_definition_id(obj):
return ifc_id
if (
(data := obj.data)
@@ -65,7 +65,7 @@ class VisualiseDiff(bpy.types.Operator):
obj.color = (0.0, 0.0, 0.7, 1.0)
continue
if not obj.BIMObjectProperties.ifc_definition_id:
if not (ifc_id := tool.Blender.get_ifc_definition_id(obj)):
continue
ifc_file = ""
@@ -84,7 +84,7 @@ class VisualiseDiff(bpy.types.Operator):
element_file = ifc_file
try:
element = element_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
element = element_file.by_id(ifc_id)
except:
continue
global_id = getattr(element, "GlobalId", None)
@@ -253,7 +253,7 @@ class SelectDiffObjects(bpy.types.Operator):
obj.select_set(True)
continue
if not obj.BIMObjectProperties.ifc_definition_id:
if not (ifc_id := tool.Blender.get_ifc_definition_id(obj)):
continue
ifc_file = ""
@@ -272,7 +272,7 @@ class SelectDiffObjects(bpy.types.Operator):
element_file = ifc_file
try:
element = element_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
element = element_file.by_id(ifc_id)
except:
continue
global_id = getattr(element, "GlobalId", None)
+6 -4
View File
@@ -90,18 +90,20 @@ class BIM_PT_object_documents(Panel):
@classmethod
def poll(cls, context):
if not context.active_object:
if not (obj := context.active_object):
return False
if not tool.Ifc.get_object_by_identifier(context.active_object.BIMObjectProperties.ifc_definition_id):
if not (ifc_id := tool.Blender.get_ifc_definition_id(obj)):
return False
return bool(context.active_object.BIMObjectProperties.ifc_definition_id)
if not tool.Ifc.get_object_by_identifier(ifc_id):
return False
return True
def draw(self, context):
if not ObjectDocumentData.is_loaded:
ObjectDocumentData.load()
obj = context.active_object
self.oprops = obj.BIMObjectProperties
self.oprops = tool.Blender.get_object_bim_props(obj)
self.props = tool.Document.get_document_props()
self.file = tool.Ifc.get()
@@ -51,7 +51,7 @@ class Annotator:
curve.font = font
props = tool.Drawing.get_text_props(obj)
props.font_size = "2.5"
collection = bpy.context.scene.camera.BIMObjectProperties.collection
collection = tool.Blender.get_object_bim_props(bpy.context.scene.camera).collection
collection.objects.link(obj)
Annotator.resize_text(obj)
return obj
@@ -133,7 +133,7 @@ class Annotator:
co1, _, _, _ = Annotator.get_placeholder_coords(camera)
matrix_world = tool.Drawing.get_camera_matrix(camera)
matrix_world.translation = co1
collection = camera.BIMObjectProperties.collection
collection = tool.Blender.get_object_bim_props(camera).collection
if object_type == "TEXT":
obj = bpy.data.objects.new(object_type, None)
@@ -343,7 +343,7 @@ def get_active_drawing(
props = tool.Drawing.get_document_props()
try:
camera = tool.Ifc.get_object(tool.Ifc.get().by_id(props.active_drawing_id))
return camera.BIMObjectProperties.collection, camera
return tool.Blender.get_object_bim_props(camera).collection, camera
except:
return None, None
@@ -246,7 +246,7 @@ class CreateDrawing(bpy.types.Operator):
def execute(self, context):
self.props = tool.Drawing.get_document_props()
active_drawing_id = context.scene.camera.BIMObjectProperties.ifc_definition_id
active_drawing_id = tool.Blender.get_ifc_definition_id(context.scene.camera)
if self.print_all:
original_drawing_id = active_drawing_id
drawings_to_print = [d.ifc_definition_id for d in self.props.drawings if d.is_selected and d.is_drawing]
@@ -391,7 +391,7 @@ class CreateDrawing(bpy.types.Operator):
bpy.ops.render.render(write_still=True)
else:
previous_visibility = {}
for obj in self.camera.BIMObjectProperties.collection.objects:
for obj in tool.Blender.get_object_bim_props(self.camera).collection.objects:
if bpy.context.view_layer.objects.get(obj.name):
previous_visibility[obj.name] = obj.hide_get()
obj.hide_set(True)
@@ -2095,7 +2095,7 @@ class ResizeText(bpy.types.Operator):
# TODO: check undo redo
def execute(self, context):
for obj in context.scene.camera.BIMObjectProperties.collection.objects:
for obj in tool.Blender.get_object_bim_props(context.scene.camera).collection.objects:
if isinstance(obj.data, bpy.types.TextCurve):
annotation.Annotator.resize_text(obj)
return {"FINISHED"}
+1 -1
View File
@@ -266,7 +266,7 @@ def update_titleblocks(self, context):
def update_should_draw_decorations(self, context: bpy.types.Context) -> None:
if self.should_draw_decorations:
# TODO: design a proper text variable templating renderer
collection = context.scene.camera.BIMObjectProperties.collection
collection = tool.Blender.get_object_bim_props(context.scene.camera).collection
for obj in collection.objects:
element = tool.Ifc.get_entity(obj)
if not element or not tool.Drawing.is_annotation_object_type(element, ["TEXT", "TEXT_LEADER"]):
@@ -102,7 +102,7 @@ def block_scale(scene: bpy.types.Scene) -> None:
import bonsai.tool as tool
if obj := (getattr(bpy.context, "active_object", None) or bpy.context.view_layer.objects.active):
if isinstance(obj, bpy.types.Object) and obj.BIMObjectProperties.ifc_definition_id:
if isinstance(obj, bpy.types.Object) and tool.Blender.get_ifc_definition_id(obj):
if obj.scale != (1, 1, 1):
obj.scale = (1, 1, 1)
elif isinstance(obj, bpy.types.Mesh) and tool.Geometry.get_mesh_props(obj).ifc_definition_id:
@@ -464,7 +464,8 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator):
assert tool.Geometry.is_data_supported_for_adding_representation(data)
mprops = tool.Geometry.get_mesh_props(data)
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
product = tool.Ifc.get_entity(obj)
assert product
material = ifcopenshell.util.element.get_material(product, should_skip_usage=True)
# NOTE: Currently iterator doesn't detect whether opening is actually affected the representation
@@ -903,7 +904,7 @@ class OverrideOutlinerDelete(bpy.types.Operator):
if element := tool.Ifc.get_entity(obj):
if tool.Geometry.is_locked(element):
self.report({"ERROR"}, lock_error_message(obj.name))
if collection := obj.BIMObjectProperties.collection:
if collection := tool.Blender.get_object_bim_props(obj).collection:
collections_to_delete.discard(collection)
continue
tool.Geometry.delete_ifc_object(obj)
@@ -1067,7 +1068,7 @@ class OverrideDuplicateMove(bpy.types.Operator):
continue
# clear object's collection so it will be able to have it's own
new_obj.BIMObjectProperties.collection = None
tool.Blender.get_object_bim_props(new_obj).collection = None
# copy the actual class
new = bonsai.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj)
+10 -7
View File
@@ -334,9 +334,9 @@ class BIM_PT_connections(Panel):
@classmethod
def poll(cls, context):
if not context.active_object:
if not (obj := context.active_object):
return False
if not tool.Ifc.get_object_by_identifier(context.active_object.BIMObjectProperties.ifc_definition_id):
if not tool.Ifc.get_object_by_identifier(tool.Blender.get_ifc_definition_id(obj)):
return False
return tool.Ifc.get()
@@ -345,7 +345,6 @@ class BIM_PT_connections(Panel):
ConnectionsData.load()
layout = self.layout
props = context.active_object.BIMObjectProperties
if not ConnectionsData.data["connections"] and not ConnectionsData.data["is_connection_realization"]:
layout.label(text="No connections found")
@@ -480,12 +479,16 @@ class BIM_PT_placement(Panel):
@classmethod
def poll(cls, context):
return (obj := context.active_object) and obj.BIMObjectProperties.ifc_definition_id
return (obj := context.active_object) and tool.Blender.get_ifc_definition_id(obj)
def draw(self, context):
if not PlacementData.is_loaded:
PlacementData.load()
obj = context.active_object
assert obj
props = tool.Blender.get_object_bim_props(obj)
if not PlacementData.data["has_placement"]:
row = self.layout.row()
row.label(text="No Object Placement Found")
@@ -496,12 +499,12 @@ class BIM_PT_placement(Panel):
row = self.layout.row()
row.prop(context.active_object, "rotation_euler", text="Rotation")
if context.active_object.BIMObjectProperties.blender_offset_type != "NONE":
if props.blender_offset_type != "NONE":
row = self.layout.row(align=True)
row.label(text="Blender Offset", icon="TRACKING_REFINE_FORWARDS")
row.label(text=context.active_object.BIMObjectProperties.blender_offset_type)
row.label(text=props.blender_offset_type)
if context.active_object.BIMObjectProperties.blender_offset_type != "NOT_APPLICABLE":
if props.blender_offset_type != "NOT_APPLICABLE":
row = self.layout.row(align=True)
row.label(text=PlacementData.data["original_x"], icon="EMPTY_AXIS")
row.label(text=PlacementData.data["original_y"])
+3 -2
View File
@@ -91,9 +91,10 @@ class BIM_PT_object_groups(Panel):
@classmethod
def poll(cls, context):
if not context.active_object:
if not (obj := context.active_object):
return False
return tool.Ifc.get() and context.active_object.BIMObjectProperties.ifc_definition_id
props = tool.Blender.get_object_bim_props(obj)
return tool.Ifc.get() and props.ifc_definition_id
def draw(self, context):
if not ObjectGroupsData.is_loaded:
@@ -374,16 +374,17 @@ class ObjectLog(bpy.types.Operator):
@classmethod
def poll(cls, context):
if not context.active_object:
if not (obj := context.active_object):
cls.poll_message_set("No Active Object")
elif not context.active_object.BIMObjectProperties.ifc_definition_id:
elif not tool.Blender.get_ifc_definition_id(obj):
cls.poll_message_set("Active Object doesn't have an IFC definition")
else:
return True
def execute(self, context):
step_id = context.active_object.BIMObjectProperties.ifc_definition_id
obj = context.active_object
assert obj
step_id = tool.Blender.get_ifc_definition_id(obj)
core.entity_log(tool.IfcGit, tool.Ifc, step_id, self)
return {"FINISHED"}
+9 -7
View File
@@ -137,14 +137,14 @@ class BIM_PT_object_material(Panel):
def poll(cls, context):
if not tool.Blender.is_tab(context, "GEOMETRY"):
return False
if not context.active_object:
if not (obj := context.active_object):
return False
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
ifc_id = tool.Blender.get_ifc_definition_id(obj)
if not ifc_id:
return False
if not tool.Ifc.get_object_by_identifier(props.ifc_definition_id):
if not tool.Ifc.get_object_by_identifier(ifc_id):
return False
if not hasattr(tool.Ifc.get().by_id(props.ifc_definition_id), "HasAssociations"):
if not hasattr(tool.Ifc.get().by_id(ifc_id), "HasAssociations"):
return False
return True
@@ -152,9 +152,11 @@ class BIM_PT_object_material(Panel):
if not ObjectMaterialData.is_loaded:
ObjectMaterialData.load()
obj = context.active_object
assert obj
self.file = tool.Ifc.get()
self.oprops = context.active_object.BIMObjectProperties
self.props = context.active_object.BIMObjectMaterialProperties
self.oprops = tool.Blender.get_object_bim_props(obj)
self.props = obj.BIMObjectMaterialProperties
self.mprops = tool.Material.get_material_props()
if not ObjectMaterialData.data["materials"]:
@@ -257,7 +257,7 @@ class DrawSystemArrows(bpy.types.Operator, tool.Ifc.Operator):
sources = []
for obj in bpy.context.selected_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
if not tool.Blender.get_ifc_definition_id(obj):
continue
element = tool.Ifc.get_entity(obj)
+13 -7
View File
@@ -72,7 +72,9 @@ class DisableEditingArray(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
context.active_object.BIMArrayProperties.is_editing = -1
obj = context.active_object
assert obj
tool.Model.get_array_props(obj).is_editing = -1
return {"FINISHED"}
@@ -84,8 +86,9 @@ class EnableEditingArray(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
assert obj
element = tool.Ifc.get_entity(obj)
props = obj.BIMArrayProperties
props = tool.Model.get_array_props(obj)
relating_obj = props.relating_array_object
@@ -119,7 +122,7 @@ class EditArray(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
element = tool.Ifc.get_entity(obj)
props = obj.BIMArrayProperties
props = tool.Model.get_array_props(obj)
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
@@ -182,7 +185,7 @@ class RemoveArray(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
element = tool.Ifc.get_entity(obj)
props = obj.BIMArrayProperties
props = tool.Model.get_array_props(obj)
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
data = json.loads(pset["Data"])
@@ -302,7 +305,8 @@ class Input3DCursorXArray(bpy.types.Operator):
def execute(self, context):
obj = context.active_object
props = obj.BIMArrayProperties
assert obj
props = tool.Model.get_array_props(obj)
cursor = context.scene.cursor
if props.use_local_space:
props.x = (Matrix.inverted(obj.matrix_world) @ cursor.matrix.translation).x
@@ -318,7 +322,8 @@ class Input3DCursorYArray(bpy.types.Operator):
def execute(self, context):
obj = context.active_object
props = obj.BIMArrayProperties
assert obj
props = tool.Model.get_array_props(obj)
cursor = context.scene.cursor
if props.use_local_space:
props.y = (Matrix.inverted(obj.matrix_world) @ cursor.matrix.translation).y
@@ -334,7 +339,8 @@ class Input3DCursorZArray(bpy.types.Operator):
def execute(self, context):
obj = context.active_object
props = obj.BIMArrayProperties
assert obj
props = tool.Model.get_array_props(obj)
cursor = context.scene.cursor
if props.use_local_space:
props.z = (Matrix.inverted(obj.matrix_world) @ cursor.matrix.translation).z
+18 -6
View File
@@ -385,7 +385,9 @@ class StairData:
@classmethod
def general_params(cls):
props = bpy.context.active_object.BIMStairProperties
obj = bpy.context.active_object
assert obj
props = tool.Model.get_stair_props(obj)
data = cls.data["pset_data"]["data_dict"]
general_params = {}
general_props = props.get_props_kwargs(stair_type=data["stair_type"])
@@ -451,7 +453,9 @@ class WindowData:
@classmethod
def general_params(cls):
props = bpy.context.active_object.BIMWindowProperties
obj = bpy.context.active_object
assert obj
props = tool.Model.get_window_props(obj)
data = cls.data["pset_data"]["data_dict"]
general_params = {}
general_props = props.get_general_kwargs()
@@ -462,7 +466,9 @@ class WindowData:
@classmethod
def lining_params(cls):
props = bpy.context.active_object.BIMWindowProperties
obj = bpy.context.active_object
assert obj
props = tool.Model.get_window_props(obj)
data = cls.data["pset_data"]["data_dict"]
lining_data = data["lining_properties"]
lining_params = {}
@@ -474,7 +480,9 @@ class WindowData:
@classmethod
def panel_params(cls):
props = bpy.context.active_object.BIMWindowProperties
obj = bpy.context.active_object
assert obj
props = tool.Model.get_window_props(obj)
panel_data = cls.data["pset_data"]["data_dict"]["panel_properties"]
panel_params = {}
panel_props = props.get_panel_kwargs()
@@ -565,7 +573,9 @@ class RailingData:
@classmethod
def general_params(cls):
props = bpy.context.active_object.BIMRailingProperties
obj = bpy.context.active_object
assert obj
props = tool.Model.get_railing_props(obj)
data = cls.data["pset_data"]["data_dict"]
general_params = {}
general_props = props.get_general_kwargs(railing_type=data["railing_type"])
@@ -599,7 +609,9 @@ class RoofData:
@classmethod
def general_params(cls):
props = bpy.context.active_object.BIMRoofProperties
obj = bpy.context.active_object
assert obj
props = tool.Model.get_roof_props(obj)
data = cls.data["pset_data"]["data_dict"]
general_params = {}
general_props = props.get_general_kwargs(generation_method=data["generation_method"])
@@ -718,14 +718,15 @@ class MirrorElements(bpy.types.Operator, tool.Ifc.Operator):
obj.matrix_world = newmat
def generate_box(usecase_path, ifc_file, settings):
def generate_box(usecase_path: str, ifc_file: ifcopenshell.file, settings: dict[str, Any]) -> None:
box_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Box", "MODEL_VIEW")
if not box_context:
return
obj = settings["blender_object"]
if 0 in list(obj.dimensions):
return
product = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
product = tool.Ifc.get_entity(obj)
assert product
old_box = ifcopenshell.util.representation.get_representation(product, "Model", "Box", "MODEL_VIEW")
if settings["context"].ContextType == "Model" and getattr(settings["context"], "ContextIdentifier") == "Body":
if old_box:
+145 -62
View File
@@ -28,34 +28,36 @@ from math import pi, radians
from bonsai.bim.module.model.decorator import WallAxisDecorator, SlabDirectionDecorator
from bonsai.bim.module.model.door import update_door_modifier_bmesh
from bonsai.bim.module.model.window import update_window_modifier_bmesh
from typing import TYPE_CHECKING, Literal, get_args
from typing import TYPE_CHECKING, Literal, get_args, Union, get_args
def get_ifc_class(self, context):
def get_ifc_class(self: "BIMModelProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
if not AuthoringData.is_loaded:
AuthoringData.load()
return AuthoringData.data["ifc_classes"]
def get_boundary_class(self, context):
def get_boundary_class(self: "BIMModelProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
if not AuthoringData.is_loaded:
AuthoringData.load()
return AuthoringData.data["boundary_class"]
def get_relating_type_id(self, context):
def get_relating_type_id(self: "BIMModelProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
if not AuthoringData.is_loaded:
AuthoringData.load()
return AuthoringData.data["relating_type_id"]
def get_materials(self, context):
def get_materials(
self: Union["BIMWindowProperties", "BIMDoorProperties"], context: bpy.types.Context
) -> list[tuple[str, str, str]]:
if not AuthoringData.is_loaded:
AuthoringData.load()
return AuthoringData.data["materials"]
def update_ifc_class(self, context):
def update_ifc_class(self: "BIMModelProperties", context: bpy.types.Context) -> None:
bpy.ops.bim.load_type_thumbnails(ifc_class=self.ifc_class)
AuthoringData.data["ifc_class_current"] = self.ifc_class
AuthoringData.data["type_elements"] = AuthoringData.type_elements()
@@ -72,46 +74,46 @@ def update_ifc_class(self, context):
AuthoringData.data["paginated_relating_types"] = AuthoringData.paginated_relating_types()
def update_relating_type_id(self, context):
def update_relating_type_id(self: "BIMModelProperties", context: bpy.types.Context) -> None:
AuthoringData.data["relating_type_id"] = AuthoringData.relating_type_id()
AuthoringData.data["relating_type_data"] = AuthoringData.relating_type_data()
self.type_page = [e[0] for e in AuthoringData.data["relating_type_id"]].index(self.relating_type_id) // 9 + 1
def update_type_page(self, context):
def update_type_page(self: "BIMModelProperties", context: bpy.types.Context) -> None:
AuthoringData.data["paginated_relating_types"] = AuthoringData.paginated_relating_types()
bpy.ops.bim.load_type_thumbnails(ifc_class=self.ifc_class, offset=9 * (self.type_page - 1), limit=9)
self["type_page"] = min(self["type_page"], AuthoringData.data["total_pages"])
self["type_page"] = max(self["type_page"], 1)
def update_relating_array_from_object(self, context):
def update_relating_array_from_object(self: "BIMArrayProperties", context: bpy.types.Context) -> None:
bpy.ops.bim.enable_editing_array(item=self.is_editing)
return
def is_object_array_applicable(self, obj):
def is_object_array_applicable(self: "BIMArrayProperties", obj: bpy.types.Object) -> bool:
element = tool.Ifc.get_entity(obj)
if not element:
return False
return ifcopenshell.util.element.get_pset(element, "BBIM_Array")
def update_wall_axis_decorator(self, context):
def update_wall_axis_decorator(self: "BIMModelProperties", context: bpy.types.Context) -> None:
if self.show_wall_axis:
WallAxisDecorator.install(bpy.context)
else:
WallAxisDecorator.uninstall()
def update_slab_direction_decorator(self, context):
def update_slab_direction_decorator(self: "BIMModelProperties", context: bpy.types.Context) -> None:
if self.show_slab_direction:
SlabDirectionDecorator.install(bpy.context)
else:
SlabDirectionDecorator.uninstall()
def update_search_name(self, context):
def update_search_name(self: "BIMModelProperties", context: bpy.types.Context) -> None:
AuthoringData.load()
# Total number of pages may decrease when using the search bar :
if self.type_page > AuthoringData.data["total_pages"]:
@@ -119,17 +121,17 @@ def update_search_name(self, context):
bpy.ops.bim.load_type_thumbnails(ifc_class=self.ifc_class)
def update_x_angle(self, context):
def update_x_angle(self: "BIMModelProperties", context: bpy.types.Context) -> None:
angle_deg = math.degrees(self.x_angle)
if tool.Cad.is_x(angle_deg, -90, 0.5) or tool.Cad.is_x(angle_deg, 90, 0.5):
self.x_angle = 0
def update_door(self, context):
def update_door(self: "BIMDoorProperties", context: bpy.types.Context) -> None:
update_door_modifier_bmesh(context)
def update_window(self, context):
def update_window(self: "BIMWindowProperties", context: bpy.types.Context) -> None:
update_window_modifier_bmesh(context)
@@ -303,36 +305,45 @@ class BIMArrayProperties(PropertyGroup):
poll=is_object_array_applicable,
)
if TYPE_CHECKING:
is_editing: int
count: int
x: float
y: float
z: float
use_local_space: bool
method: Literal["OFFSET", "DISTRIBUTE"]
sync_children: bool
relating_array_object: Union[bpy.types.Object, None]
def update_total_length_target(self, context):
def update_total_length_target(self: "BIMStairProperties", context: bpy.types.Context) -> None:
self["tread_run"] = self.total_length_target / (self.number_of_treads + 1)
def update_tread_run(self, context):
def update_tread_run(self: "BIMStairProperties", context: bpy.types.Context) -> None:
if self.total_length_lock:
self["number_of_treads"] = int((self.total_length_target / self.tread_run) - 1)
else:
self["total_length_target"] = (self.number_of_treads + 1) * self.tread_run
def update_number_of_treads(self, context):
def update_number_of_treads(self: "BIMStairProperties", context: bpy.types.Context) -> None:
if self.total_length_lock:
self["tread_run"] = self.total_length_target / (self.number_of_treads + 1)
else:
self["total_length_target"] = (self.number_of_treads + 1) * self.tread_run
StairType = Literal["CONCRETE", "WOOD/STEEL", "GENERIC"]
class BIMStairProperties(PropertyGroup):
def validate_nosing_value(self, context):
def validate_nosing_value(self, context: bpy.types.Context) -> None:
if self.stair_type != "WOOD/STEEL" and self.nosing_length < 0:
self["nosing_length"] = 0
non_si_units_props = ("is_editing", "number_of_treads", "has_top_nib", "stair_type")
stair_types = (
("CONCRETE", "Concrete", ""),
("WOOD/STEEL", "Wood / Steel", ""),
("GENERIC", "Generic", ""),
)
is_editing: bpy.props.BoolProperty(default=False)
width: bpy.props.FloatProperty(name="Width", default=1.2, soft_min=0.01, subtype="DISTANCE")
@@ -361,7 +372,10 @@ class BIMStairProperties(PropertyGroup):
top_slab_depth: bpy.props.FloatProperty(name="Top Slab Depth", default=0.25, soft_min=0, subtype="DISTANCE")
has_top_nib: bpy.props.BoolProperty(name="Has Top Nib", default=True)
stair_type: bpy.props.EnumProperty(
name="Stair Type", items=stair_types, default="CONCRETE", update=validate_nosing_value
name="Stair Type",
items=[(i, i.replace("/", " / ").title(), "") for i in get_args(StairType)],
default="CONCRETE",
update=validate_nosing_value,
)
custom_first_last_tread_run: bpy.props.FloatVectorProperty(
name="Custom First / Last Treads Widths",
@@ -385,6 +399,23 @@ class BIMStairProperties(PropertyGroup):
name="Nosing Depth", description="Depth of the tread's nosing", min=0, default=0, unit="LENGTH"
)
if TYPE_CHECKING:
is_editing: bool
width: float
height: float
number_of_treads: int
total_length_target: float
total_length_lock: bool
tread_depth: float
tread_run: float
base_slab_depth: float
top_slab_depth: float
has_top_nib: bool
stair_type: str
custom_first_last_tread_run: tuple[float, float]
nosing_length: float
nosing_depth: float
def get_props_kwargs(self, convert_to_project_units=False, stair_type=None):
if not stair_type:
stair_type = self.stair_type
@@ -445,20 +476,22 @@ def window_type_prop_update(self, context):
update_window(self, context)
WindowType = Literal[
"SINGLE_PANEL",
"DOUBLE_PANEL_HORIZONTAL",
"DOUBLE_PANEL_VERTICAL",
"TRIPLE_PANEL_BOTTOM",
"TRIPLE_PANEL_TOP",
"TRIPLE_PANEL_LEFT",
"TRIPLE_PANEL_RIGHT",
"TRIPLE_PANEL_HORIZONTAL",
"TRIPLE_PANEL_VERTICAL",
]
# default prop values are in mm and converted later
class BIMWindowProperties(PropertyGroup):
non_si_units_props = ("is_editing", "window_type")
window_types = (
("SINGLE_PANEL", "SINGLE_PANEL", ""),
("DOUBLE_PANEL_HORIZONTAL", "DOUBLE_PANEL_HORIZONTAL", ""),
("DOUBLE_PANEL_VERTICAL", "DOUBLE_PANEL_VERTICAL", ""),
("TRIPLE_PANEL_BOTTOM", "TRIPLE_PANEL_BOTTOM", ""),
("TRIPLE_PANEL_TOP", "TRIPLE_PANEL_TOP", ""),
("TRIPLE_PANEL_LEFT", "TRIPLE_PANEL_LEFT", ""),
("TRIPLE_PANEL_RIGHT", "TRIPLE_PANEL_RIGHT", ""),
("TRIPLE_PANEL_HORIZONTAL", "TRIPLE_PANEL_HORIZONTAL", ""),
("TRIPLE_PANEL_VERTICAL", "TRIPLE_PANEL_VERTICAL", ""),
)
# number of panels and default mullion/transom values
# fmt: off
@@ -477,7 +510,10 @@ class BIMWindowProperties(PropertyGroup):
is_editing: bpy.props.BoolProperty(default=False)
window_type: bpy.props.EnumProperty(
name="Window Type", items=window_types, default="SINGLE_PANEL", update=window_type_prop_update
name="Window Type",
items=[(i, i, "") for i in get_args(WindowType)],
default="SINGLE_PANEL",
update=window_type_prop_update,
)
overall_height: bpy.props.FloatProperty(
name="Overall Height", default=0.9, subtype="DISTANCE", update=update_window
@@ -546,6 +582,31 @@ class BIMWindowProperties(PropertyGroup):
framing_material: bpy.props.EnumProperty(name="Framing Material", items=get_materials, options=set())
glazing_material: bpy.props.EnumProperty(name="Glazing Material", items=get_materials, options=set())
if TYPE_CHECKING:
is_editing: bool
window_type: WindowType
overall_height: float
overall_width: float
lining_depth: float
lining_thickness: float
lining_offset: float
lining_to_panel_offset_x: float
lining_to_panel_offset_y: float
mullion_thickness: float
first_mullion_offset: float
second_mullion_offset: float
first_transom_offset: float
second_transom_offset: float
# Panel properties.
frame_depth: tuple[float, float, float]
frame_thickness: tuple[float, float, float]
# Material properties.
lining_material: str
framing_material: str
glazing_material: str
def get_general_kwargs(self, convert_to_project_units=False):
kwargs = {
"window_type": self.window_type,
@@ -824,6 +885,10 @@ class BIMDoorProperties(PropertyGroup):
setattr(self, prop_name, kwargs[prop_name])
RailingType = Literal["FRAMELESS_PANEL", "WALL_MOUNTED_HANDRAIL"]
CapType = Literal["TO_END_POST_AND_FLOOR", "TO_END_POST", "TO_FLOOR", "TO_WALL", "180", "NONE"]
class BIMRailingProperties(PropertyGroup):
non_si_units_props = (
"is_editing",
@@ -833,23 +898,12 @@ class BIMRailingProperties(PropertyGroup):
"path_data",
)
railing_types = (
("FRAMELESS_PANEL", "FRAMELESS_PANEL", ""),
("WALL_MOUNTED_HANDRAIL", "WALL_MOUNTED_HANDRAIL", ""),
)
cap_types = (
("TO_END_POST_AND_FLOOR", "TO_END_POST_AND_FLOOR", ""),
("TO_END_POST", "TO_END_POST", ""),
("TO_FLOOR", "TO_FLOOR", ""),
("TO_WALL", "TO_WALL", ""),
("180", "180", ""),
("NONE", "NONE", ""),
)
is_editing: bpy.props.BoolProperty(default=False)
is_editing_path: bpy.props.BoolProperty(default=False)
railing_type: bpy.props.EnumProperty(name="Railing Type", items=railing_types, default="FRAMELESS_PANEL")
railing_type: bpy.props.EnumProperty(
name="Railing Type", items=[(i, i, "") for i in get_args(RailingType)], default="FRAMELESS_PANEL"
)
height: bpy.props.FloatProperty(name="Height", default=1.0, subtype="DISTANCE")
thickness: bpy.props.FloatProperty(name="Thickness", default=0.050, subtype="DISTANCE")
spacing: bpy.props.FloatProperty(name="Spacing", default=0.050, subtype="DISTANCE")
@@ -875,7 +929,24 @@ class BIMRailingProperties(PropertyGroup):
description="Clear width between the railing and the wall",
subtype="DISTANCE",
)
terminal_type: bpy.props.EnumProperty(name="Terminal Type", items=cap_types, default="180")
terminal_type: bpy.props.EnumProperty(
name="Terminal Type", items=[(i, i, "") for i in get_args(CapType)], default="180"
)
if TYPE_CHECKING:
is_editing: bool
is_editing_path: bool
railing_type: RailingType
height: float
thickness: float
spacing: float
use_manual_supports: bool
support_spacing: float
railing_diameter: float
clear_width: float
terminal_type: CapType
def get_general_kwargs(self, railing_type=None, convert_to_project_units=False):
if railing_type is None:
@@ -921,11 +992,15 @@ def to_percentage(angle: float) -> float:
return math.tan(angle) * 100
RoofType = Literal["HIP/GABLE ROOF"]
RoofGenerationMethod = Literal["HEIGHT", "ANGLE"]
class BIMRoofProperties(PropertyGroup):
def update_angle(self, context) -> None:
def update_angle(self, context: bpy.types.Context) -> None:
self["angle"] = to_angle(self.percentage)
def update_percentage(self, context) -> None:
def update_percentage(self, context: bpy.types.Context) -> None:
self["percentage"] = to_percentage(self.angle)
non_si_units_props = (
@@ -937,18 +1012,15 @@ class BIMRoofProperties(PropertyGroup):
"percentage",
"rafter_edge_angle",
)
roof_types = (("HIP/GABLE ROOF", "HIP/GABLE ROOF", ""),)
roof_generation_methods = (
("HEIGHT", "HEIGHT", ""),
("ANGLE", "ANGLE", ""),
)
is_editing: bpy.props.BoolProperty(default=False)
is_editing_path: bpy.props.BoolProperty(default=False)
roof_type: bpy.props.EnumProperty(name="Roof Type", items=roof_types, default="HIP/GABLE ROOF")
roof_type: bpy.props.EnumProperty(
name="Roof Type", items=[(i, i, "") for i in get_args(RoofType)], default="HIP/GABLE ROOF"
)
generation_method: bpy.props.EnumProperty(
name="Roof Generation Method", items=roof_generation_methods, default="ANGLE"
name="Roof Generation Method", items=[(i, i, "") for i in get_args(RoofGenerationMethod)], default="ANGLE"
)
height: bpy.props.FloatProperty(
name="Height", default=1.0, description="Maximum height of the roof to be generated.", subtype="DISTANCE"
@@ -978,6 +1050,17 @@ class BIMRoofProperties(PropertyGroup):
name="Rafter Edge Angle", min=0, max=pi / 2, default=pi / 2, subtype="ANGLE"
)
if TYPE_CHECKING:
is_editing: bool
is_editing_path: bool
roof_type: Literal["HIP/GABLE ROOF"]
generation_method: Literal["HEIGHT", "ANGLE"]
height: float
angle: float
percentage: float
roof_thickness: float
rafter_edge_angle: float
def get_general_kwargs(self, generation_method=None, convert_to_project_units=False):
if generation_method is None:
generation_method = self.generation_method
+33 -13
View File
@@ -55,8 +55,10 @@ def update_railing_modifier_ifc_data(context: bpy.types.Context) -> None:
since it's going to update ifc representation
"""
obj = context.active_object
props = obj.BIMRailingProperties
assert obj
props = tool.Model.get_railing_props(obj)
element = tool.Ifc.get_entity(obj)
assert element
ifc_file = tool.Ifc.get()
# type attributes
@@ -122,7 +124,8 @@ def update_railing_modifier_bmesh(context: bpy.types.Context) -> None:
If BBIM Pset just changed should call refresh() before updating bmesh
"""
obj = context.active_object
props = obj.BIMRailingProperties
assert obj
props = tool.Model.get_railing_props(obj)
V_ = tool.Blender.V_
# NOTE: using Data since bmesh update will hapen very often
@@ -327,8 +330,10 @@ class AddRailing(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
assert obj
element = tool.Ifc.get_entity(obj)
props = obj.BIMRailingProperties
assert element
props = tool.Model.get_railing_props(obj)
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
railing_data = props.get_general_kwargs(convert_to_project_units=True)
@@ -367,7 +372,8 @@ class CopyRailingParameters(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
source_obj = context.active_object
source_props = source_obj.BIMRailingProperties
assert source_obj
source_props = tool.Model.get_railing_props(source_obj)
railing_data = source_props.get_general_kwargs(convert_to_project_units=True)
for target_obj in context.selected_objects:
@@ -379,7 +385,8 @@ class CopyRailingParameters(bpy.types.Operator, tool.Ifc.Operator):
continue
railing_data["path_data"] = RailingData.data["path_data"]
target_element = tool.Ifc.get_entity(target_obj)
target_props = target_obj.BIMRailingProperties
assert target_element
target_props = tool.Model.get_railing_props(target_obj)
target_props.set_props_kwargs_from_ifc_data(railing_data)
update_bbim_railing_pset(target_element, railing_data)
@@ -398,7 +405,8 @@ class EnableEditingRailing(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
props = obj.BIMRailingProperties
assert obj
props = tool.Model.get_railing_props(obj)
data = tool.Model.get_modeling_bbim_pset_data(obj, "BBIM_Railing")["data_dict"]
data["path_data"] = json.dumps(data["path_data"])
@@ -416,8 +424,9 @@ class CancelEditingRailing(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
assert obj
data = tool.Model.get_modeling_bbim_pset_data(obj, "BBIM_Railing")["data_dict"]
props = obj.BIMRailingProperties
props = tool.Model.get_railing_props(obj)
# restore previous settings since editing was canceled
props.set_props_kwargs_from_ifc_data(data)
@@ -434,8 +443,10 @@ class FinishEditingRailing(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
assert obj
element = tool.Ifc.get_entity(obj)
props = obj.BIMRailingProperties
assert element
props = tool.Model.get_railing_props(obj)
pset_data = tool.Model.get_modeling_bbim_pset_data(bpy.context.active_object, "BBIM_Railing")
path_data = pset_data["data_dict"]["path_data"]
@@ -457,8 +468,10 @@ class FlipRailingPathOrder(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
assert obj
element = tool.Ifc.get_entity(obj)
props = obj.BIMRailingProperties
assert element
props = tool.Model.get_railing_props(obj)
pset_data = tool.Model.get_modeling_bbim_pset_data(bpy.context.active_object, "BBIM_Railing")
path_data = pset_data["data_dict"]["path_data"]
@@ -488,7 +501,8 @@ class EnableEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
[o.select_set(False) for o in context.selected_objects if o != obj]
props = obj.BIMRailingProperties
assert obj
props = tool.Model.get_railing_props(obj)
data = tool.Model.get_modeling_bbim_pset_data(obj, "BBIM_Railing")["data_dict"]
# required since we could load pset from .ifc and BIMRoofProperties won't be set
props.set_props_kwargs_from_ifc_data(data)
@@ -505,7 +519,8 @@ class EnableEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator):
def cancel_editing_railing_path(context: bpy.types.Context) -> set[str]:
obj = context.active_object
props = obj.BIMRailingProperties
assert obj
props = tool.Model.get_railing_props(obj)
ProfileDecorator.uninstall()
props.is_editing_path = False
@@ -517,6 +532,7 @@ def cancel_editing_railing_path(context: bpy.types.Context) -> set[str]:
update_railing_modifier_bmesh(context)
else:
element = tool.Ifc.get_entity(obj)
assert element
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
bonsai.core.geometry.switch_representation(
tool.Ifc,
@@ -547,8 +563,9 @@ class FinishEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
assert obj
element = tool.Ifc.get_entity(obj)
props = obj.BIMRailingProperties
props = tool.Model.get_railing_props(obj)
railing_data = props.get_general_kwargs(convert_to_project_units=True)
path_data = get_path_data(obj)
@@ -574,8 +591,11 @@ class RemoveRailing(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
assert obj
element = tool.Ifc.get_entity(obj)
obj.BIMRailingProperties.is_editing = False
assert element
props = tool.Model.get_railing_props(obj)
props.is_editing = False
pset = tool.Pset.get_element_pset(element, "BBIM_Railing")
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
+18 -12
View File
@@ -409,7 +409,8 @@ def update_roof_modifier_ifc_data(context: bpy.types.Context) -> None:
since it's going to update ifc representation
"""
obj = context.active_object
props = obj.BIMRoofProperties
assert obj
props = tool.Model.get_roof_props(obj)
element = tool.Ifc.get_entity(obj)
def roof_is_gabled() -> bool:
@@ -442,7 +443,7 @@ def update_roof_modifier_bmesh(obj: bpy.types.Object) -> None:
"""before using should make sure that Data contains up-to-date information.
If BBIM Pset just changed should call refresh() before updating bmesh
"""
props = obj.BIMRoofProperties
props = tool.Model.get_roof_props(obj)
assert isinstance(obj.data, bpy.types.Mesh)
# NOTE: using Data since bmesh update will hapen very often
@@ -559,7 +560,7 @@ class AddRoof(bpy.types.Operator, tool.Ifc.Operator):
obj = context.active_object
assert obj
element = tool.Ifc.get_entity(obj)
props = obj.BIMRoofProperties
props = tool.Model.get_roof_props(obj)
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
# rejecting original roof shape to be safe
@@ -607,7 +608,8 @@ class EnableEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
props = obj.BIMRoofProperties
assert obj
props = tool.Model.get_roof_props(obj)
data = tool.Model.get_modeling_bbim_pset_data(obj, "BBIM_Roof")["data_dict"]
# required since we could load pset from .ifc and BIMRoofProperties won't be set
props.set_props_kwargs_from_ifc_data(data)
@@ -624,7 +626,7 @@ class CancelEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
obj = context.active_object
assert obj
data = tool.Model.get_modeling_bbim_pset_data(obj, "BBIM_Roof")["data_dict"]
props = obj.BIMRoofProperties
props = tool.Model.get_roof_props(obj)
# restore previous settings since editing was canceled
props.set_props_kwargs_from_ifc_data(data)
@@ -642,7 +644,7 @@ class FinishEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
element = tool.Ifc.get_entity(obj)
props = obj.BIMRoofProperties
props = tool.Model.get_roof_props(obj)
pset_data = tool.Model.get_modeling_bbim_pset_data(obj, "BBIM_Roof")
path_data = pset_data["data_dict"]["path_data"]
@@ -666,7 +668,7 @@ class EnableEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
obj = context.active_object
assert obj
[o.select_set(False) for o in context.selected_objects if o != obj]
props = obj.BIMRoofProperties
props = tool.Model.get_roof_props(obj)
data = tool.Model.get_modeling_bbim_pset_data(obj, "BBIM_Roof")["data_dict"]
# required since we could load pset from .ifc and BIMRoofProperties won't be set
props.set_props_kwargs_from_ifc_data(data)
@@ -720,7 +722,7 @@ class EnableEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
def cancel_editing_roof_path(context: bpy.types.Context) -> set[str]:
obj = context.active_object
assert obj
props = obj.BIMRoofProperties
props = tool.Model.get_roof_props(obj)
ProfileDecorator.uninstall()
props.is_editing_path = False
@@ -751,7 +753,8 @@ class CopyRoofParameters(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
source_obj = context.active_object
source_props = source_obj.BIMRoofProperties
assert source_obj
source_props = tool.Model.get_roof_props(source_obj)
data = source_props.get_general_kwargs(convert_to_project_units=True)
for target_obj in context.selected_objects:
@@ -763,7 +766,7 @@ class CopyRoofParameters(bpy.types.Operator, tool.Ifc.Operator):
continue
data["path_data"] = RoofData.data["path_data"]
target_element = tool.Ifc.get_entity(target_obj)
target_props = target_obj.BIMRoofProperties
target_props = tool.Model.get_roof_props(target_obj)
target_props.set_props_kwargs_from_ifc_data(data)
update_bbim_roof_pset(target_element, data)
@@ -783,7 +786,7 @@ class FinishEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
element = tool.Ifc.get_entity(obj)
props = obj.BIMRoofProperties
props = tool.Model.get_roof_props(obj)
bm = tool.Blender.get_bmesh_for_mesh(obj.data)
op_status, error_message = is_valid_roof_footprint(bm)
@@ -815,9 +818,12 @@ class RemoveRoof(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
assert obj
element = tool.Ifc.get_entity(obj)
obj.BIMRoofProperties.is_editing = False
props = tool.Model.get_roof_props(obj)
props.is_editing = False
assert element
pset = tool.Pset.get_element_pset(element, "BBIM_Roof")
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
return {"FINISHED"}
+18 -8
View File
@@ -34,7 +34,8 @@ from bpy_extras.object_utils import AddObjectHelper, object_data_add
def regenerate_stair_mesh(obj: bpy.types.Object) -> None:
props_kwargs = obj.BIMStairProperties.get_props_kwargs()
props = tool.Model.get_stair_props(obj)
props_kwargs = props.get_props_kwargs()
vertices, edges, faces = tool.Model.generate_stair_2d_profile(**props_kwargs)
bm = bmesh.new()
@@ -69,7 +70,8 @@ def update_ifc_stair_props(obj: bpy.types.Object) -> None:
since it's going to update ifc representation
"""
element = tool.Ifc.get_entity(obj)
props = obj.BIMStairProperties
assert element
props = tool.Model.get_stair_props(obj)
ifc_file = tool.Ifc.get()
if tool.Ifc.get_schema() != "IFC2X3" and element.is_a("IfcStairFlight"):
@@ -183,7 +185,8 @@ class AddStair(bpy.types.Operator, tool.Ifc.Operator):
obj = context.active_object
assert obj
element = tool.Ifc.get_entity(obj)
props = obj.BIMStairProperties
assert element
props = tool.Model.get_stair_props(obj)
ifc_file = tool.Ifc.get()
stair_data = props.get_props_kwargs(convert_to_project_units=True)
@@ -214,9 +217,11 @@ class CancelEditingStair(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
assert obj
element = tool.Ifc.get_entity(obj)
assert element
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Stair", "Data"))
props = obj.BIMStairProperties
props = tool.Model.get_stair_props(obj)
# restore previous settings since editing was canceled
props.set_props_kwargs_from_ifc_data(data)
regenerate_stair_mesh(obj)
@@ -233,8 +238,10 @@ class FinishEditingStair(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
assert obj
element = tool.Ifc.get_entity(obj)
props = obj.BIMStairProperties
assert element
props = tool.Model.get_stair_props(obj)
data = props.get_props_kwargs(convert_to_project_units=True)
props.is_editing = False
@@ -257,7 +264,8 @@ class EnableEditingStair(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
props = obj.BIMStairProperties
assert obj
props = tool.Model.get_stair_props(obj)
element = tool.Ifc.get_entity(obj)
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Stair", "Data"))
# required since we could load pset from .ifc and BIMStairProperties won't be set
@@ -273,9 +281,11 @@ class RemoveStair(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
props = obj.BIMStairProperties
assert obj
props = tool.Model.get_stair_props(obj)
element = tool.Ifc.get_entity(obj)
obj.BIMStairProperties.is_editing = False
assert element
props.is_editing = False
pset = tool.Pset.get_element_pset(element, "BBIM_Stair")
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
+11 -5
View File
@@ -217,7 +217,9 @@ class BIM_PT_array(bpy.types.Panel):
if not ArrayData.is_loaded:
ArrayData.load()
props = context.active_object.BIMArrayProperties
obj = context.active_object
assert obj
props = tool.Model.get_array_props(obj)
if ArrayData.data["parameters"]:
row = self.layout.row(align=True)
@@ -291,7 +293,7 @@ class BIM_PT_stair(bpy.types.Panel):
obj = context.active_object
assert obj
props = obj.BIMStairProperties
props = tool.Model.get_stair_props(obj)
if StairData.data["pset_data"]:
row = self.layout.row(align=True)
@@ -404,7 +406,9 @@ class BIM_PT_window(bpy.types.Panel):
if not WindowData.is_loaded:
WindowData.load()
props = context.active_object.BIMWindowProperties
obj = context.active_object
assert obj
props = tool.Model.get_window_props(obj)
if WindowData.data["pset_data"]:
row = self.layout.row(align=True)
@@ -594,7 +598,9 @@ class BIM_PT_railing(bpy.types.Panel):
if not RailingData.is_loaded:
RailingData.load()
props = context.active_object.BIMRailingProperties
obj = context.active_object
assert obj
props = tool.Model.get_railing_props(obj)
if RailingData.data["pset_data"]:
row = self.layout.row(align=True)
@@ -659,7 +665,7 @@ class BIM_PT_roof(bpy.types.Panel):
obj = context.active_object
assert obj
props = obj.BIMRoofProperties
props = tool.Model.get_roof_props(obj)
if RoofData.data["pset_data"]:
row = self.layout.row(align=True)
+21 -8
View File
@@ -41,8 +41,10 @@ V_ = tool.Blender.V_
def update_window_modifier_representation(context: bpy.types.Context) -> None:
obj = context.active_object
assert obj
element = tool.Ifc.get_entity(obj)
props = obj.BIMWindowProperties
assert element
props = tool.Model.get_window_props(obj)
ifc_file = tool.Ifc.get()
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
@@ -258,7 +260,8 @@ def create_bm_window(
def update_window_modifier_bmesh(context: bpy.types.Context) -> None:
obj = context.active_object
props = obj.BIMWindowProperties
assert obj
props = tool.Model.get_window_props(obj)
panel_schema = DEFAULT_PANEL_SCHEMAS[props.window_type]
accumulated_height = [0] * len(panel_schema[0])
built_panels = []
@@ -447,8 +450,10 @@ class AddWindow(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
assert obj
element = tool.Ifc.get_entity(obj)
props = obj.BIMWindowProperties
assert element
props = tool.Model.get_window_props(obj)
window_data = props.get_general_kwargs(convert_to_project_units=True)
lining_props = props.get_lining_kwargs(convert_to_project_units=True)
@@ -477,11 +482,13 @@ class CancelEditingWindow(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
assert obj
element = tool.Ifc.get_entity(obj)
assert element
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Window", "Data"))
data.update(data.pop("lining_properties"))
data.update(data.pop("panel_properties"))
props = obj.BIMWindowProperties
props = tool.Model.get_window_props(obj)
props.set_props_kwargs_from_ifc_data(data)
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
@@ -506,8 +513,10 @@ class FinishEditingWindow(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
assert obj
element = tool.Ifc.get_entity(obj)
props = obj.BIMWindowProperties
assert element
props = tool.Model.get_window_props(obj)
window_data = props.get_general_kwargs(convert_to_project_units=True)
lining_props = props.get_lining_kwargs(convert_to_project_units=True)
@@ -533,8 +542,10 @@ class EnableEditingWindow(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
props = obj.BIMWindowProperties
assert obj
props = tool.Model.get_window_props(obj)
element = tool.Ifc.get_entity(obj)
assert element
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Window", "Data"))
data.update(data.pop("lining_properties"))
data.update(data.pop("panel_properties"))
@@ -553,9 +564,11 @@ class RemoveWindow(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
props = obj.BIMWindowProperties
assert obj
element = tool.Ifc.get_entity(obj)
obj.BIMWindowProperties.is_editing = False
assert element
props = tool.Model.get_window_props(obj)
props.is_editing = False
pset = tool.Pset.get_element_pset(element, "BBIM_Window")
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
@@ -1046,6 +1046,7 @@ class EditObjectUI:
@classmethod
def draw_modes(cls, context: bpy.types.Context) -> None:
obj = context.active_object
ui_context = str(context.region.type)
row = cls.layout.row(align=True)
row.separator()
@@ -1055,9 +1056,7 @@ class EditObjectUI:
if len(context.selected_objects) == 1 and AuthoringData.data["has_extrusion"]:
row = cls.layout.row(align=True) if ui_context != "TOOL_HEADER" else row
add_layout_hotkey_operator(row, "Edit Profile", "S_E", "", ui_context)
elif (
tool.Model.is_parametric_railing_active() and not context.active_object.BIMRailingProperties.is_editing_path
):
elif tool.Model.is_parametric_railing_active() and not tool.Model.get_railing_props(obj).is_editing_path:
row = cls.layout.row(align=True) if ui_context != "TOOL_HEADER" else row
row.operator(
"bim.enable_editing_railing_path",
@@ -1208,12 +1207,12 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
# and it might conflict with one of the conditions below
if (
tool.Model.is_parametric_railing_active()
and not bpy.context.active_object.BIMRailingProperties.is_editing_path
and not tool.Model.get_railing_props(active_object).is_editing_path
):
bpy.ops.bim.enable_editing_railing_path()
return
elif tool.Model.is_parametric_roof_active() and not bpy.context.active_object.BIMRoofProperties.is_editing_path:
elif tool.Model.is_parametric_roof_active() and not tool.Model.get_roof_props(active_object).is_editing_path:
# undo the unselection done above because roof has no usage type
bpy.ops.bim.enable_editing_roof_path()
return
+1 -1
View File
@@ -40,7 +40,7 @@ def update_relating_object(self, context):
if self.relating_object is None:
return
if not self.relating_object.BIMObjectProperties.ifc_definition_id:
if not tool.Blender.get_ifc_definition_id(self.relating_object):
context.window_manager.popup_menu(message, title="Invalid Element Selected", icon="INFO")
self.relating_object = None
+6 -6
View File
@@ -32,14 +32,14 @@ class BIM_PT_nest(Panel):
@classmethod
def poll(cls, context):
if not context.active_object:
if not (obj := context.active_object):
return False
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
ifc_id = tool.Blender.get_ifc_definition_id(obj)
if not ifc_id:
return False
if not tool.Ifc.get_object_by_identifier(props.ifc_definition_id):
if not tool.Ifc.get_object_by_identifier(ifc_id):
return False
if not tool.Ifc.get().by_id(props.ifc_definition_id).is_a("IfcObjectDefinition"):
if not tool.Ifc.get().by_id(ifc_id).is_a("IfcObjectDefinition"):
return False
return True
@@ -55,7 +55,7 @@ class BIM_PT_nest(Panel):
row.prop(props, "relating_object", text="")
if props.relating_object:
op = row.operator("bim.nest_assign_object", icon="CHECKMARK", text="")
op.relating_object = props.relating_object.BIMObjectProperties.ifc_definition_id
op.relating_object = tool.Blender.get_ifc_definition_id(props.relating_object)
row.operator("bim.disable_editing_nest", icon="CANCEL", text="")
else:
row = layout.row(align=True)
@@ -2327,11 +2327,12 @@ class RefreshClippingPlanes(bpy.types.Operator):
else:
break
def is_moved(self, obj):
if not obj.BIMObjectProperties.location_checksum:
def is_moved(self, obj: bpy.types.Object) -> bool:
props = tool.Blender.get_object_bim_props(obj)
if not props.location_checksum:
return True # Let's be conservative
loc_check = np.frombuffer(eval(obj.BIMObjectProperties.location_checksum))
rot_check = np.frombuffer(eval(obj.BIMObjectProperties.rotation_checksum))
loc_check = np.frombuffer(eval(props.location_checksum))
rot_check = np.frombuffer(eval(props.rotation_checksum))
loc_real = np.array(obj.matrix_world.translation).flatten()
rot_real = np.array(obj.matrix_world.to_3x3()).flatten()
if np.allclose(loc_check, loc_real, atol=1e-4) and np.allclose(rot_check, rot_real, atol=1e-2):
+7 -12
View File
@@ -351,10 +351,9 @@ class BIM_OT_add_edit_custom_property(bpy.types.Operator, tool.Ifc.Operator):
props = context.scene.AddEditProperties
for obj in tool.Blender.get_selected_objects():
ifc_definition_id = obj.BIMObjectProperties.ifc_definition_id
if not ifc_definition_id:
ifc_element = tool.Ifc.get_entity(obj)
if not ifc_element:
continue
ifc_element = tool.Ifc.get().by_id(ifc_definition_id)
for prop in props:
value = getattr(prop, prop.get_value_name())
@@ -406,23 +405,19 @@ class BIM_OT_bulk_remove_psets(bpy.types.Operator, tool.Ifc.Operator):
props = context.scene.DeletePsets
for obj in tool.Blender.get_selected_objects():
ifc_definition_id = obj.BIMObjectProperties.ifc_definition_id
if not ifc_definition_id:
ifc_element = tool.Ifc.get_entity(obj)
if not ifc_element:
continue
ifc_element = tool.Ifc.get().by_id(ifc_definition_id)
psets = ifcopenshell.util.element.get_psets(ifc_element)
for prop in props:
pset = prop.pset_name
if pset in psets:
try:
ifcopenshell.api.run(
"pset.remove_pset",
ifcopenshell.api.pset.remove_pset(
self.file,
**{
"product": self.file.by_id(ifc_definition_id),
"pset": self.file.by_id(psets[pset]["id"]),
},
product=ifc_element,
pset=self.file.by_id(psets[pset]["id"]),
)
except KeyError:
pass # Sometimes the pset id is not found, I'm not sure why this happens though. - vulevukusej
+8 -8
View File
@@ -241,12 +241,12 @@ class BIM_PT_object_psets(Panel):
@classmethod
def poll(cls, context):
if not context.active_object:
if not (obj := context.active_object):
return False
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
ifc_id = tool.Blender.get_ifc_definition_id(obj)
if not ifc_id:
return False
if not tool.Ifc.get_object_by_identifier(props.ifc_definition_id):
if not tool.Ifc.get_object_by_identifier(ifc_id):
return False
return True
@@ -319,12 +319,12 @@ class BIM_PT_object_qtos(Panel):
@classmethod
def poll(cls, context):
if not context.active_object:
if not (obj := context.active_object):
return False
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
ifc_id = tool.Blender.get_ifc_definition_id(obj)
if not ifc_id:
return False
if not tool.Ifc.get_object_by_identifier(props.ifc_definition_id):
if not tool.Ifc.get_object_by_identifier(ifc_id):
return False
return True
@@ -648,7 +648,8 @@ def get_opening_area(
"""
total_opening_area = 0
ifc = tool.Ifc.get()
ifc_element = ifc.by_id(obj.BIMObjectProperties.ifc_definition_id)
ifc_element = tool.Ifc.get_entity(obj)
assert ifc_element
if len(openings := ifc_element.HasOpenings) != 0:
for opening in openings:
opening_id = opening.RelatedOpeningElement.GlobalId
@@ -887,7 +888,7 @@ def get_OBB_object(obj: bpy.types.Object) -> bpy.types.Object:
:param blender-object obj: Blender Object
:return blender-object: OBB of the Object
"""
ifc_id = obj.BIMObjectProperties.ifc_definition_id
ifc_id = tool.Blender.get_ifc_definition_id(obj)
bbox = obj.bound_box
# matrix transformation to go from obj coordinates to world coordinates:
obb = [Vector(v) for v in bbox]
@@ -929,7 +930,7 @@ def get_AABB_object(obj: bpy.types.Object) -> bpy.types.Object:
:param blender-object obj: Blender Object
:return blender-object: AABB of the Object
"""
ifc_id = obj.BIMObjectProperties.ifc_definition_id
ifc_id = tool.Blender.get_ifc_definition_id(obj)
aabb_mesh = bpy.data.meshes.new(f"OBB_{ifc_id}")
x = [v.co.x for v in obj.data.vertices]
@@ -994,7 +995,7 @@ def get_bisected_obj(
:param tuple(x,y,z) plane_no_neg: Tuple describing the normal vector of the lower bisection plane. Example: (0,0,-1)
:return _type_: _description_
"""
ifc_id = obj.BIMObjectProperties.ifc_definition_id
ifc_id = tool.Blender.get_ifc_definition_id(obj)
bis_obj = obj.copy()
bis_obj.data = obj.data.copy()
+2 -1
View File
@@ -18,6 +18,7 @@
import bpy
import bmesh
import bonsai.tool as tool
from typing import Callable
@@ -86,7 +87,7 @@ def calculate_formwork_area(objs: list[bpy.types.Object], context: bpy.types.Con
bpy.ops.object.modifier_apply(modifier="Boolean")
copied_obj.name = "Formwork"
copied_obj.BIMObjectProperties.ifc_definition_id = 0
tool.Blender.get_object_bim_props(copied_obj).ifc_definition_id = 0
modifier = copied_obj.modifiers.new("Formwork", "REMESH")
assert isinstance(modifier, bpy.types.RemeshModifier)
modifier.mode = "SHARP"
@@ -48,7 +48,8 @@ class EnableReassignClass(bpy.types.Operator):
element = tool.Ifc.get_entity(obj)
assert element
ifc_class = element.is_a()
context.active_object.BIMObjectProperties.is_reassigning_class = True
props = tool.Blender.get_object_bim_props(obj)
props.is_reassigning_class = True
ifc_products = tool.Root.get_ifc_products()
schema = tool.Ifc.schema()
declaration = schema.declaration_by_name(ifc_class)
@@ -58,10 +59,10 @@ class EnableReassignClass(bpy.types.Operator):
break
else:
self.report({"ERROR"}, f"Couldn't find matching IFC product for the selected object: '{element}'.")
obj.BIMObjectProperties.is_reassigning_class = False
props.is_reassigning_class = False
return {"CANCELLED"}
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
element = self.file.by_id(tool.Blender.get_ifc_definition_id(obj))
rprops.ifc_class = element.is_a()
rprops.relating_class_object = None
if hasattr(element, "PredefinedType"):
@@ -78,7 +79,8 @@ class DisableReassignClass(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.active_object.BIMObjectProperties.is_reassigning_class = False
props = tool.Blender.get_object_bim_props(context.active_object)
props.is_reassigning_class = False
return {"FINISHED"}
@@ -127,7 +129,8 @@ class ReassignClass(bpy.types.Operator, tool.Ifc.Operator):
)
return {"CANCELLED"}
obj.BIMObjectProperties.is_reassigning_class = False
props = tool.Blender.get_object_bim_props(obj)
props.is_reassigning_class = False
if element.is_a("IfcTypeObject"):
elements_to_reassign[element] = ifc_class
elements_to_update.update(ifcopenshell.util.element.get_types(element))
+3 -1
View File
@@ -43,7 +43,9 @@ class BIM_PT_class(Panel):
def draw(self, context):
if not IfcClassData.is_loaded:
IfcClassData.load()
props = context.active_object.BIMObjectProperties
obj = context.active_object
assert obj
props = tool.Blender.get_object_bim_props(obj)
rprops = tool.Root.get_root_props()
if props.ifc_definition_id:
if not IfcClassData.data["has_entity"]:
@@ -1449,15 +1449,17 @@ class LoadProductTasks(bpy.types.Operator):
@classmethod
def poll(cls, context):
if not tool.Ifc.get() or not (obj := context.active_object) or not (obj.BIMObjectProperties.ifc_definition_id):
if not tool.Ifc.get() or not (obj := context.active_object) or not (tool.Blender.get_ifc_definition_id(obj)):
cls.poll_message_set("No IFC object is active.")
return False
return True
def execute(self, context):
result = core.load_product_related_tasks(
tool.Sequence, product=tool.Ifc.get().by_id(context.active_object.BIMObjectProperties.ifc_definition_id)
)
obj = context.active_object
assert obj
product = tool.Ifc.get_entity(obj)
assert product
result = core.load_product_related_tasks(tool.Sequence, product=product)
if isinstance(result, str):
self.report({"INFO"}, result)
else:
@@ -271,10 +271,10 @@ def update_sort_reversed(self, context):
def update_filter_by_active_schedule(self, context):
if context.active_object:
core.load_product_related_tasks(
tool.Sequence, product=tool.Ifc.get().by_id(context.active_object.BIMObjectProperties.ifc_definition_id)
)
if obj := context.active_object:
product = tool.Ifc.get_entity(obj)
assert product
core.load_product_related_tasks(tool.Sequence, product=product)
def switch_options(self, context):
@@ -75,11 +75,13 @@ class AddStructuralMemberConnection(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
oprops = obj.BIMObjectProperties
assert obj
oprops = tool.Blender.get_object_bim_props(obj)
props = obj.BIMStructuralProperties
file = tool.Ifc.get()
related_structural_connection = file.by_id(oprops.ifc_definition_id)
relating_structural_member = file.by_id(props.relating_structural_member.BIMObjectProperties.ifc_definition_id)
relating_structural_member = tool.Ifc.get_entity(props.relating_structural_member)
assert relating_structural_member
if not relating_structural_member.is_a("IfcStructuralMember"):
return {"FINISHED"}
ifcopenshell.api.structural.add_structural_member_connection(
@@ -99,7 +101,6 @@ class EnableEditingStructuralConnectionCondition(bpy.types.Operator):
def execute(self, context):
obj = context.active_object
oprops = obj.BIMObjectProperties
props = obj.BIMStructuralProperties
props.active_connects_structural_member = self.connects_structural_member
return {"FINISHED"}
@@ -350,7 +351,8 @@ class EnableEditingStructuralItemAxis(bpy.types.Operator):
def execute(self, context):
obj = context.active_object
oprops = obj.BIMObjectProperties
assert obj
oprops = tool.Blender.get_object_bim_props(obj)
props = obj.BIMStructuralProperties
self.file = tool.Ifc.get()
@@ -403,7 +405,8 @@ class EditStructuralItemAxis(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
oprops = obj.BIMObjectProperties
assert obj
oprops = tool.Blender.get_object_bim_props(obj)
props = obj.BIMStructuralProperties
relative_matrix = props.axis_empty.matrix_world @ obj.matrix_world.inverted()
z_axis = relative_matrix.col[2][0:3]
@@ -425,11 +428,12 @@ class EnableEditingStructuralConnectionCS(bpy.types.Operator):
def execute(self, context):
obj = context.active_object
oprops = obj.BIMObjectProperties
assert obj
props = obj.BIMStructuralProperties
self.file = tool.Ifc.get()
item = self.file.by_id(oprops.ifc_definition_id)
item = tool.Ifc.get_entity(obj)
assert item
location = obj.data.vertices[0].co
empty = bpy.data.objects.new("Item Connection CS", None)
@@ -491,16 +495,17 @@ class EditStructuralConnectionCS(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
oprops = obj.BIMObjectProperties
assert obj
item = tool.Ifc.get_entity(obj)
assert item
props = obj.BIMStructuralProperties
relative_matrix = props.ccs_empty.matrix_world @ obj.matrix_world.inverted()
x_axis = relative_matrix.col[0][0:3]
z_axis = relative_matrix.col[2][0:3]
self.file = tool.Ifc.get()
ifcopenshell.api.run(
"structural.edit_structural_connection_cs",
ifcopenshell.api.structural.edit_structural_connection_cs(
self.file,
structural_item=self.file.by_id(oprops.ifc_definition_id),
structural_item=item,
axis=z_axis,
ref_direction=x_axis,
)
@@ -695,9 +700,9 @@ class AddStructuralActivity(bpy.types.Operator, tool.Ifc.Operator):
self.props = context.scene.BIMStructuralProperties
self.file = tool.Ifc.get()
for obj in context.selected_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
element = tool.Ifc.get_entity(obj)
if not element:
continue
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
applied_load_class = self.props.applicable_structural_load_types
allowed_load_classes = {
+20 -21
View File
@@ -85,14 +85,14 @@ class BIM_PT_structural_boundary_conditions(Panel):
@classmethod
def poll(cls, context):
if not context.active_object:
if not (obj := context.active_object):
return False
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
ifc_id = tool.Blender.get_ifc_definition_id(obj)
if not ifc_id:
return False
if not tool.Ifc.get_object_by_identifier(props.ifc_definition_id):
if not (tool.Ifc.get_object_by_identifier(ifc_id)):
return False
if not tool.Ifc.get().by_id(props.ifc_definition_id).is_a("IfcStructuralConnection"):
if not tool.Ifc.get().by_id(ifc_id).is_a("IfcStructuralConnection"):
return False
return True
@@ -120,14 +120,14 @@ class BIM_PT_connected_structural_members(Panel):
@classmethod
def poll(cls, context):
if not context.active_object:
if not (obj := context.active_object):
return False
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
ifc_id = tool.Blender.get_ifc_definition_id(obj)
if not ifc_id:
return False
if not tool.Ifc.get_object_by_identifier(props.ifc_definition_id):
if not (tool.Ifc.get_object_by_identifier(ifc_id)):
return False
if not tool.Ifc.get().by_id(props.ifc_definition_id).is_a("IfcStructuralConnection"):
if not tool.Ifc.get().by_id(ifc_id).is_a("IfcStructuralConnection"):
return False
return True
@@ -173,14 +173,14 @@ class BIM_PT_structural_member(Panel):
@classmethod
def poll(cls, context):
if not context.active_object:
if not (obj := context.active_object):
return False
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
ifc_id = tool.Blender.get_ifc_definition_id(obj)
if not ifc_id:
return False
if not tool.Ifc.get_object_by_identifier(props.ifc_definition_id):
if not tool.Ifc.get_object_by_identifier(ifc_id):
return False
if not tool.Ifc.get().by_id(props.ifc_definition_id).is_a("IfcStructuralMember"):
if not tool.Ifc.get().by_id(ifc_id).is_a("IfcStructuralMember"):
return False
return True
@@ -216,14 +216,14 @@ class BIM_PT_structural_connection(Panel):
@classmethod
def poll(cls, context):
if not context.active_object:
if not (obj := context.active_object):
return False
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
ifc_id = tool.Blender.get_ifc_definition_id(obj)
if not ifc_id:
return False
if not tool.Ifc.get_object_by_identifier(props.ifc_definition_id):
if not tool.Ifc.get_object_by_identifier(ifc_id):
return False
if not tool.Ifc.get().by_id(props.ifc_definition_id).is_a("IfcStructuralConnection"):
if not tool.Ifc.get().by_id(ifc_id).is_a("IfcStructuralConnection"):
return False
return True
@@ -315,7 +315,6 @@ class BIM_UL_structural_analysis_models(UIList):
row.label(text=item.name)
if context.active_object:
oprops = context.active_object.BIMObjectProperties
if item.ifc_definition_id in StructuralAnalysisModelsData.data["active_model_ids"]:
op = row.operator(
"bim.unassign_structural_analysis_model", text="", icon="KEYFRAME_HLT", emboss=False
+8 -12
View File
@@ -187,9 +187,8 @@ class BIM_PT_ports(Panel):
if connected_obj_name:
connected_obj = bpy.data.objects[connected_obj_name]
cols[3].operator("bim.disconnect_port", text="", icon="UNLINKED").element_id = port.id()
cols[4].operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = (
connected_obj.BIMObjectProperties.ifc_definition_id
)
ifc_id = tool.Blender.get_ifc_definition_id(connected_obj)
cols[4].operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = ifc_id
cols[5].label(text=connected_obj_name)
else:
cols[3].label(text="", icon="UNLINKED")
@@ -244,9 +243,8 @@ class BIM_PT_port(Panel):
relating_object = bpy.data.objects[relating_object_name]
row.label(text="Port located on:")
row.label(text=relating_object_name)
row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = (
relating_object.BIMObjectProperties.ifc_definition_id
)
ifc_id = tool.Blender.get_ifc_definition_id(relating_object)
row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = ifc_id
# object connected to the port
row = layout.row(align=True)
@@ -255,9 +253,8 @@ class BIM_PT_port(Panel):
connected_object = bpy.data.objects[connected_object_name]
row.label(text="Port connected to:")
row.label(text=connected_object_name)
row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = (
connected_object.BIMObjectProperties.ifc_definition_id
)
ifc_id = tool.Blender.get_ifc_definition_id(connected_object)
row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = ifc_id
else:
row.label(text="Port is not connected to any element")
@@ -304,9 +301,8 @@ class BIM_PT_flow_controls(Panel):
op.flow_control = control_id
op.flow_element = flow_element_id
op.assign = False
row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = (
displayed_object.BIMObjectProperties.ifc_definition_id
)
ifc_id = tool.Blender.get_ifc_definition_id(displayed_object)
row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = ifc_id
row.label(text=f"{displayed_object_name}")
element = tool.Ifc.get_entity(context.active_object)
@@ -150,7 +150,8 @@ class SelectRequirement(bpy.types.Operator):
area.spaces[0].shading.show_xray = True
failed_ids = [e["id"] for e in failed_entities]
for obj in context.scene.objects:
if obj.BIMObjectProperties.ifc_definition_id in failed_ids:
ifc_id = tool.Blender.get_ifc_definition_id(obj)
if ifc_id in failed_ids:
obj.color = (1, 0, 0, 1)
else:
obj.color = (1, 1, 1, 1)
@@ -175,7 +176,8 @@ class SelectFailedEntities(bpy.types.Operator):
failed_ids = [e["id"] for e in failed_entities]
for obj in context.scene.objects:
if obj.BIMObjectProperties.ifc_definition_id in failed_ids:
ifc_id = tool.Blender.get_ifc_definition_id(obj)
if ifc_id in failed_ids:
obj.select_set(True)
else:
obj.select_set(False)
@@ -337,5 +337,5 @@ class DuplicateType(bpy.types.Operator, tool.Ifc.Operator):
# Set duplicated type as active in current tool.
if ifc_class in (i[0] for i in (bonsai.bim.helper.get_enum_items(props, "ifc_class", context) or ()) if i):
props.ifc_class = new.is_a()
props.relating_type_id = str(new_obj.BIMObjectProperties.ifc_definition_id)
props.relating_type_id = str(tool.Blender.get_ifc_definition_id(new_obj))
return {"FINISHED"}
+5 -5
View File
@@ -46,7 +46,9 @@ class BIM_PT_type(Panel):
if not TypeData.is_loaded:
TypeData.load()
oprops = context.active_object.BIMObjectProperties
obj = context.active_object
assert obj
oprops = tool.Blender.get_object_bim_props(obj)
if TypeData.data["is_product"]:
self.draw_product_ui(context)
@@ -54,21 +56,19 @@ class BIM_PT_type(Panel):
self.draw_type_ui(context)
def draw_type_ui(self, context):
props = context.active_object.BIMTypeProperties
oprops = context.active_object.BIMObjectProperties
oprops = tool.Blender.get_object_bim_props(context.active_object)
row = self.layout.row(align=True)
row.label(text=f"{TypeData.data['total_instances']} Typed Objects")
select_type_objects_row = row.row(align=True)
select_type_objects_row.operator("bim.select_type_objects", icon="RESTRICT_SELECT_OFF", text="")
select_type_objects_row.enabled = int(TypeData.data["total_instances"]) > 0
op = row.operator("bim.duplicate_type", icon="DUPLICATE", text="")
op.element = context.active_object.BIMObjectProperties.ifc_definition_id
op.element = oprops.ifc_definition_id
row.operator("bim.auto_rename_occurrences", icon="ITALIC", text="")
def draw_product_ui(self, context):
layout = self.layout
props = context.active_object.BIMTypeProperties
oprops = context.active_object.BIMObjectProperties
if props.is_editing_type:
row = layout.row(align=True)
@@ -215,8 +215,8 @@ class AddFilling(bpy.types.Operator, tool.Ifc.Operator):
if opening is None:
return {"FINISHED"}
self.file = tool.Ifc.get()
element_id = obj.BIMObjectProperties.ifc_definition_id
opening_id = opening.BIMObjectProperties.ifc_definition_id
element_id = tool.Blender.get_object_bim_props(obj).ifc_definition_id
opening_id = tool.Blender.get_object_bim_props(opening).ifc_definition_id
if not element_id or not opening_id or element_id == opening_id:
return {"FINISHED"}
ifcopenshell.api.run(
-2
View File
@@ -52,8 +52,6 @@ class BIM_PT_voids(Panel):
if not VoidsData.is_loaded:
VoidsData.load()
props = context.active_object.BIMObjectProperties
if len(context.selected_objects) >= 2:
row = self.layout.row(align=True)
op = row.operator("bim.add_opening", icon="ADD", text="Add Opening")
+3 -1
View File
@@ -857,7 +857,9 @@ class FetchObjectPassport(bpy.types.Operator):
def execute(self, context):
# TODO: this is dead code, awaiting reimplementation. See #1222.
for reference in context.active_object.BIMObjectProperties.document_references:
obj = context.active_object
props = tool.Blender.get_object_bim_props(obj)
for reference in props.document_references:
bim_props = tool.Blender.get_bim_props()
reference = bim_props.document_references[reference.name]
if reference.location[-6:] == ".blend":
+26 -10
View File
@@ -41,7 +41,7 @@ from typing import Any, Optional, Union, Literal, Iterable, Callable, TypeVar, G
from typing_extensions import assert_never
if TYPE_CHECKING:
from bonsai.bim.prop import BIMProperties
from bonsai.bim.prop import BIMProperties, BIMObjectProperties
VIEWPORT_ATTRIBUTES = [
@@ -192,7 +192,8 @@ class Blender(bonsai.core.tool.Blender):
if context is None:
context = bpy.context
if obj_type == "Object":
return bpy.data.objects.get(obj).BIMObjectProperties.ifc_definition_id
props = tool.Blender.get_object_bim_props(bpy.data.objects[obj])
return props.ifc_definition_id
elif obj_type == "Material":
props = tool.Material.get_material_props()
return props.materials[props.active_material_index].ifc_definition_id
@@ -221,7 +222,8 @@ class Blender(bonsai.core.tool.Blender):
@classmethod
def is_ifc_object(cls, obj: bpy.types.Object) -> bool:
return bool(obj.BIMObjectProperties.ifc_definition_id)
props = tool.Blender.get_object_bim_props(obj)
return bool(props.ifc_definition_id)
@classmethod
def is_ifc_class_active(cls, ifc_class: str) -> bool:
@@ -880,7 +882,7 @@ class Blender(bonsai.core.tool.Blender):
@classmethod
def get_layer_collection(cls, collection: bpy.types.Collection) -> Union[bpy.types.LayerCollection, None]:
project = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0])
project_collection = project.BIMObjectProperties.collection
project_collection = tool.Blender.get_object_bim_props(project).collection
for layer_collection in bpy.context.view_layer.layer_collection.children:
if layer_collection.collection == project_collection:
for layer_collection2 in layer_collection.children:
@@ -1019,23 +1021,28 @@ class Blender(bonsai.core.tool.Blender):
@classmethod
def is_editing_railing_path(cls, obj: bpy.types.Object):
return obj.BIMRailingProperties.is_editing_path
props = tool.Model.get_railing_props(obj)
return props.is_editing_path
@classmethod
def is_editing_roof_path(cls, obj: bpy.types.Object) -> bool:
return obj.BIMRoofProperties.is_editing_path
props = tool.Model.get_roof_props(obj)
return props.is_editing_path
@classmethod
def is_editing_railing_parameters(cls, obj: bpy.types.Object) -> bool:
return obj.BIMRailingProperties.is_editing
props = tool.Model.get_railing_props(obj)
return props.is_editing
@classmethod
def is_editing_roof_parameters(cls, obj: bpy.types.Object) -> bool:
return obj.BIMRoofProperties.is_editing
props = tool.Model.get_roof_props(obj)
return props.is_editing
@classmethod
def is_editing_window_parameters(cls, obj: bpy.types.Object) -> bool:
return obj.BIMWindowProperties.is_editing
props = tool.Model.get_window_props(obj)
return props.is_editing
@classmethod
def is_editing_door_parameters(cls, obj: bpy.types.Object) -> bool:
@@ -1044,7 +1051,8 @@ class Blender(bonsai.core.tool.Blender):
@classmethod
def is_editing_stair_parameters(cls, obj: bpy.types.Object) -> bool:
return obj.BIMStairProperties.is_editing
props = tool.Model.get_stair_props(obj)
return props.is_editing
@classmethod
def is_modifier_with_non_editable_path(cls, element: entity_instance) -> bool:
@@ -1545,3 +1553,11 @@ class Blender(bonsai.core.tool.Blender):
if scene is None:
scene = bpy.context.scene
return scene.BIMProperties
@classmethod
def get_object_bim_props(cls, obj: bpy.types.Object) -> BIMObjectProperties:
return obj.BIMObjectProperties
@classmethod
def get_ifc_definition_id(cls, obj: bpy.types.Object) -> int:
return tool.Blender.get_object_bim_props(obj).ifc_definition_id
+9 -1
View File
@@ -28,7 +28,7 @@
# - An arc is reconstructed from 3 points instead of a full circle
# - You can now derive the center from an arc without generating geometry
from __future__ import annotations
import sys
import bpy
import math
@@ -36,12 +36,20 @@ import bmesh
import mathutils.geometry
from mathutils import Vector, Matrix, geometry
import itertools
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from bonsai.bim.module.cad.prop import BIMCadProperties
VTX_PRECISION = 1.0e-5
class Cad:
@classmethod
def get_cad_props(cls) -> BIMCadProperties:
return bpy.context.scene.BIMCadProperties
@classmethod
def is_point_on_edge(cls, p, edge):
"""
+14 -12
View File
@@ -29,7 +29,7 @@ class Collector(bonsai.core.tool.Collector):
"""Links an object to an appropriate Blender collection."""
if should_clean_users_collection:
for users_collection in obj.users_collection:
if obj.BIMObjectProperties.collection == users_collection:
if tool.Blender.get_object_bim_props(obj).collection == users_collection:
continue
# Users are free to use extra collections for their own
# purposes except for the reserved keyword "Ifc" and
@@ -87,7 +87,7 @@ class Collector(bonsai.core.tool.Collector):
if collection := cls._create_own_collection(obj):
cls.link_collection_object_safe(collection, obj)
project_obj = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0])
cls.link_collection_child_safe(project_obj.BIMObjectProperties.collection, collection)
cls.link_collection_child_safe(tool.Blender.get_object_bim_props(project_obj).collection, collection)
elif (
tool.Ifc.get_schema() != "IFC2X3"
and element.is_a("IfcSpatialElement")
@@ -98,21 +98,21 @@ class Collector(bonsai.core.tool.Collector):
if collection := cls._create_own_collection(obj):
cls.link_collection_object_safe(collection, obj)
project_obj = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0])
cls.link_collection_child_safe(project_obj.BIMObjectProperties.collection, collection)
cls.link_collection_child_safe(tool.Blender.get_object_bim_props(project_obj).collection, collection)
elif element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
if collection := cls._create_own_collection(obj):
cls.link_collection_object_safe(collection, obj)
project_obj = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0])
cls.link_collection_child_safe(project_obj.BIMObjectProperties.collection, collection)
cls.link_collection_child_safe(tool.Blender.get_object_bim_props(project_obj).collection, collection)
elif element.is_a("IfcAnnotation") and (drawing_obj := cls.get_annotation_drawing_obj(element)):
cls.link_collection_object_safe(drawing_obj.BIMObjectProperties.collection, obj)
cls.link_collection_object_safe(tool.Blender.get_object_bim_props(drawing_obj).collection, obj)
elif container := ifcopenshell.util.element.get_container(element):
while container.is_a("IfcSpace"):
container = ifcopenshell.util.element.get_aggregate(container)
container_obj = tool.Ifc.get_object(container)
if not (collection := container_obj.BIMObjectProperties.collection):
if not (collection := tool.Blender.get_object_bim_props(container_obj).collection):
cls.assign(container_obj)
collection = container_obj.BIMObjectProperties.collection
collection = tool.Blender.get_object_bim_props(container_obj).collection
cls.link_collection_object_safe(collection, obj)
else:
collection = cls._create_project_child_collection("Unsorted")
@@ -128,7 +128,7 @@ class Collector(bonsai.core.tool.Collector):
return collection
collection = bpy.data.collections.new(name)
project_obj = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0])
project_obj.BIMObjectProperties.collection.children.link(collection)
tool.Blender.get_object_bim_props(project_obj).collection.children.link(collection)
if layer_collection := tool.Blender.get_layer_collection(collection):
cls.set_layer_collection_visibility(layer_collection)
return collection
@@ -136,11 +136,12 @@ class Collector(bonsai.core.tool.Collector):
@classmethod
def _create_own_collection(cls, obj: bpy.types.Object) -> bpy.types.Collection:
"""get or create own collection for the element"""
if obj.BIMObjectProperties.collection:
obj.BIMObjectProperties.collection.name = obj.name
props = tool.Blender.get_object_bim_props(obj)
if props.collection:
props.collection.name = obj.name
return
collection = bpy.data.collections.new(obj.name)
obj.BIMObjectProperties.collection = collection
props.collection = collection
collection.BIMCollectionProperties.obj = obj
return collection
@@ -184,7 +185,8 @@ class Collector(bonsai.core.tool.Collector):
@classmethod
def reset_default_visibility(cls) -> None:
project = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0])
project_collection = project.BIMObjectProperties.collection
assert project
project_collection = tool.Blender.get_object_bim_props(project).collection
for layer_collection in bpy.context.view_layer.layer_collection.children:
if layer_collection.collection == project_collection:
for layer_collection2 in layer_collection.children:
+10 -1
View File
@@ -16,17 +16,26 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import bpy
import ifcopenshell
import ifcopenshell.util.element
import bonsai.core.tool
import bonsai.tool as tool
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from bonsai.bim.module.covering.prop import BIMCoveringProperties
class Covering(bonsai.core.tool.Covering):
@classmethod
def get_covering_props(cls) -> BIMCoveringProperties:
return bpy.context.scene.BIMCoveringProperties
@classmethod
def get_z_from_ceiling_height(cls) -> float:
props = bpy.context.scene.BIMCoveringProperties
props = cls.get_covering_props()
return props.ceiling_height
# def toggle_spaces_visibility_wired_and_textured(cls, spaces):
+8 -7
View File
@@ -524,7 +524,7 @@ class Drawing(bonsai.core.tool.Drawing):
def get_drawing_collection(cls, drawing: ifcopenshell.entity_instance) -> Union[bpy.types.Collection, None]:
obj = tool.Ifc.get_object(drawing)
if obj:
return obj.BIMObjectProperties.collection
return tool.Blender.get_object_bim_props(obj).collection
@classmethod
def get_drawing_group(cls, drawing: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
@@ -1516,8 +1516,8 @@ class Drawing(bonsai.core.tool.Drawing):
dst = src.copy()
dst.data = dst.data.copy()
dst.name = dst.name.replace("IfcGridAxis/", "")
dst.BIMObjectProperties.ifc_definition_id = 0
tool.Geometry.get_geometry_props(dst).ifc_definition_id = 0
tool.Blender.get_object_bim_props(dst).ifc_definition_id = 0
tool.Geometry.get_geometry_props(dst.data).ifc_definition_id = 0
return dst
def disassemble(obj: bpy.types.Object) -> tuple[bpy.types.Object, bmesh.types.BMesh]:
@@ -1888,7 +1888,7 @@ class Drawing(bonsai.core.tool.Drawing):
return bool(
camera is not None
and camera.type == "CAMERA"
and camera.BIMObjectProperties.ifc_definition_id
and tool.Blender.get_ifc_definition_id(camera)
and area is not None
)
@@ -1910,11 +1910,12 @@ class Drawing(bonsai.core.tool.Drawing):
def isolate_camera_collection(cls, camera: bpy.types.Object) -> None:
drawings = [e for e in tool.Ifc.get().by_type("IfcAnnotation") if e.ObjectType == "DRAWING"]
drawing_collections = []
camera_collection = camera.BIMObjectProperties.collection
camera_collection = tool.Blender.get_object_bim_props(camera).collection
for drawing in drawings:
if not (drawing_obj := tool.Ifc.get_object(drawing)):
continue
if not (drawing_collection := drawing_obj.BIMObjectProperties.collection):
oprops = tool.Blender.get_object_bim_props(drawing_obj)
if not (drawing_collection := oprops.collection):
continue
if drawing_obj == camera:
drawing_collection.hide_render = False
@@ -1922,7 +1923,7 @@ class Drawing(bonsai.core.tool.Drawing):
drawing_collection.hide_render = True
project = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0])
project_collection = project.BIMObjectProperties.collection
project_collection = tool.Blender.get_object_bim_props(project).collection
for layer_collection in bpy.context.view_layer.layer_collection.children:
if layer_collection.collection == project_collection:
for layer_collection2 in layer_collection.children:
+11 -11
View File
@@ -235,7 +235,7 @@ class Geometry(bonsai.core.tool.Geometry):
bpy.data.objects.remove(axis_obj)
ifcopenshell.api.grid.remove_grid_axis(tool.Ifc.get(), axis=axis)
collection = obj.BIMObjectProperties.collection
collection = tool.Blender.get_object_bim_props(obj).collection
if collection:
parent = ifcopenshell.util.element.get_aggregate(element)
if not parent:
@@ -243,7 +243,7 @@ class Geometry(bonsai.core.tool.Geometry):
if parent:
parent_obj = tool.Ifc.get_object(parent)
if parent_obj:
parent_collection = parent_obj.BIMObjectProperties.collection
parent_collection = tool.Blender.get_object_bim_props(parent_obj).collection
for child in collection.children:
parent_collection.children.link(child)
for child_object in collection.objects:
@@ -557,11 +557,9 @@ class Geometry(bonsai.core.tool.Geometry):
@classmethod
def get_cartesian_point_offset(cls, obj: bpy.types.Object) -> npt.NDArray[np.float64] | None:
if (
obj.BIMObjectProperties.blender_offset_type == "CARTESIAN_POINT"
and obj.BIMObjectProperties.cartesian_point_offset
):
return np.array(tuple(map(float, obj.BIMObjectProperties.cartesian_point_offset.split(","))))
props = tool.Blender.get_object_bim_props(obj)
if props.blender_offset_type == "CARTESIAN_POINT" and props.cartesian_point_offset:
return np.array(tuple(map(float, props.cartesian_point_offset.split(","))))
@classmethod
def get_element_type(cls, element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
@@ -1084,8 +1082,9 @@ class Geometry(bonsai.core.tool.Geometry):
@classmethod
def record_object_position(cls, obj: bpy.types.Object) -> None:
# These are recorded separately because they have different numerical tolerances
obj.BIMObjectProperties.location_checksum = repr(np.array(obj.matrix_world.translation).tobytes())
obj.BIMObjectProperties.rotation_checksum = repr(np.array(obj.matrix_world.to_3x3()).tobytes())
props = tool.Blender.get_object_bim_props(obj)
props.location_checksum = repr(np.array(obj.matrix_world.translation).tobytes())
props.rotation_checksum = repr(np.array(obj.matrix_world.to_3x3()).tobytes())
@classmethod
def remove_connection(cls, connection: ifcopenshell.entity_instance) -> None:
@@ -1570,8 +1569,9 @@ class Geometry(bonsai.core.tool.Geometry):
def get_blender_offset_type(cls, obj: bpy.types.Object) -> Optional[str]:
props = tool.Georeference.get_georeference_props()
if props.has_blender_offset:
if (result := obj.BIMObjectProperties.blender_offset_type) == "NONE":
result = obj.BIMObjectProperties.blender_offset_type = "OBJECT_PLACEMENT"
props = tool.Blender.get_object_bim_props(obj)
if (result := props.blender_offset_type) == "NONE":
result = props.blender_offset_type = "OBJECT_PLACEMENT"
return result
@classmethod
+5 -4
View File
@@ -77,13 +77,14 @@ class Ifc(bonsai.core.tool.Ifc):
return False
if element and (element.is_a("IfcTypeProduct") or element.is_a("IfcProject")):
return False
if not obj.BIMObjectProperties.location_checksum:
oprops = tool.Blender.get_object_bim_props(obj)
if not oprops.location_checksum:
return True # Let's be conservative
loc_check = np.frombuffer(eval(obj.BIMObjectProperties.location_checksum))
loc_check = np.frombuffer(eval(oprops.location_checksum))
loc_real = np.array(obj.matrix_world.translation).flatten()
if not np.allclose(loc_check, loc_real, atol=1e-4): # 0.1 mm
return True
rot_check = np.frombuffer(eval(obj.BIMObjectProperties.rotation_checksum)).reshape(3, 3)
rot_check = np.frombuffer(eval(oprops.rotation_checksum)).reshape(3, 3)
rot_real = np.array(obj.matrix_world.to_3x3())
rot_dot = np.dot(rot_check, rot_real.T)
angle_rad = np.arccos(np.clip((np.trace(rot_dot) - 1) / 2, -1, 1))
@@ -107,7 +108,7 @@ class Ifc(bonsai.core.tool.Ifc):
props = None
if isinstance(obj, bpy.types.Object):
props = obj.BIMObjectProperties
props = tool.Blender.get_object_bim_props(obj)
elif isinstance(obj, bpy.types.Material):
props = obj.BIMStyleProperties
else:
+2 -2
View File
@@ -397,9 +397,9 @@ class IfcGit:
bpy.ops.object.select_all(action="DESELECT")
for obj in bpy.context.visible_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
props = tool.Blender.get_object_bim_props(obj)
if not (step_id := props.ifc_definition_id):
continue
step_id = obj.BIMObjectProperties.ifc_definition_id
if step_id in step_ids["modified"]:
obj.color = (0.3, 0.3, 1.0, 1)
obj.select_set(True)
+6 -5
View File
@@ -929,6 +929,7 @@ class Loader(bonsai.core.tool.Loader):
@classmethod
def apply_blender_offset_to_matrix_world(cls, obj: bpy.types.Object, matrix: np.ndarray) -> Matrix:
oprops = tool.Blender.get_object_bim_props(obj)
if (
not obj.data
and tool.Cad.is_x(matrix[0][3], 0)
@@ -939,13 +940,13 @@ class Loader(bonsai.core.tool.Loader):
# positionally significant and is left alone. This handles
# scenarios where often spatial elements are left at 0,0,0 and
# everything else is at map coordinates.
obj.BIMObjectProperties.blender_offset_type = "NOT_APPLICABLE"
oprops.blender_offset_type = "NOT_APPLICABLE"
return Matrix(matrix.tolist())
if obj.data and obj.data.get("has_cartesian_point_offset", None):
obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT"
oprops.blender_offset_type = "CARTESIAN_POINT"
if cartesian_point_offset := obj.data.get("cartesian_point_offset", None):
obj.BIMObjectProperties.cartesian_point_offset = cartesian_point_offset
oprops.cartesian_point_offset = cartesian_point_offset
offset_xyz = list(map(float, cartesian_point_offset.split(","))) + [1.0]
offset_xyz = matrix @ offset_xyz
matrix[0][3] = offset_xyz[0]
@@ -954,8 +955,8 @@ class Loader(bonsai.core.tool.Loader):
props = tool.Georeference.get_georeference_props()
if props.has_blender_offset:
if obj.BIMObjectProperties.blender_offset_type == "NONE":
obj.BIMObjectProperties.blender_offset_type = "OBJECT_PLACEMENT"
if oprops.blender_offset_type == "NONE":
oprops.blender_offset_type = "OBJECT_PLACEMENT"
matrix = ifcopenshell.util.geolocation.global2local(
matrix,
float(props.blender_offset_x) * cls.unit_scale,
+31 -3
View File
@@ -55,7 +55,15 @@ T = TypeVar("T")
V_ = tool.Blender.V_
if TYPE_CHECKING:
from bonsai.bim.module.model.prop import BIMModelProperties, BIMDoorProperties
from bonsai.bim.module.model.prop import (
BIMModelProperties,
BIMDoorProperties,
BIMArrayProperties,
BIMRoofProperties,
BIMWindowProperties,
BIMStairProperties,
BIMRailingProperties,
)
class Model(bonsai.core.tool.Model):
@@ -67,6 +75,26 @@ class Model(bonsai.core.tool.Model):
def get_door_props(cls, obj: bpy.types.Object) -> BIMDoorProperties:
return obj.BIMDoorProperties
@classmethod
def get_window_props(cls, obj: bpy.types.Object) -> BIMWindowProperties:
return obj.BIMWindowProperties
@classmethod
def get_stair_props(cls, obj: bpy.types.Object) -> BIMStairProperties:
return obj.BIMStairProperties
@classmethod
def get_roof_props(cls, obj: bpy.types.Object) -> BIMRoofProperties:
return obj.BIMRoofProperties
@classmethod
def get_railing_props(cls, obj: bpy.types.Object) -> BIMRailingProperties:
return obj.BIMRailingProperties
@classmethod
def get_array_props(cls, obj: bpy.types.Object) -> BIMArrayProperties:
return obj.BIMArrayProperties
@classmethod
def convert_si_to_unit(cls, value: T) -> T:
if isinstance(value, (tuple, list)):
@@ -1989,9 +2017,9 @@ class Model(bonsai.core.tool.Model):
if z < 0 and y < 0:
y = abs(y)
z = abs(z)
if z < 0 and y >=0:
if z < 0 and y >= 0:
vector = Vector((0, -1))
x_angle = vector.angle_signed(Vector((y, z)))
return x_angle
+2 -1
View File
@@ -409,7 +409,8 @@ class Root(bonsai.core.tool.Root):
def set_object_name(cls, obj: bpy.types.Object, element: ifcopenshell.entity_instance) -> None:
name = tool.Loader.get_name(element)
if obj.name != name:
obj.BIMObjectProperties.is_renaming = True
props = tool.Blender.get_object_bim_props(obj)
props.is_renaming = True
obj.name = name # The handler will trigger, and reset is_renaming to False
@classmethod
+6 -5
View File
@@ -1158,7 +1158,7 @@ class Sequence(bonsai.core.tool.Sequence):
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 2
for obj in bpy.data.objects:
if not obj.BIMObjectProperties.ifc_definition_id:
if not (ifc_id := tool.Blender.get_ifc_definition_id(obj)):
continue
obj.color = (1.0, 1.0, 1.0, 1)
obj.hide_viewport = False
@@ -1317,7 +1317,7 @@ class Sequence(bonsai.core.tool.Sequence):
@classmethod
def clear_objects_animation(cls, include_blender_objects=True):
for obj in bpy.data.objects:
if not include_blender_objects and not obj.BIMObjectProperties.ifc_definition_id:
if not include_blender_objects and not (ifc_id := tool.Blender.get_ifc_definition_id(obj)):
continue
cls.clear_object_animation(obj)
cls.clear_object_color(obj)
@@ -1326,13 +1326,14 @@ class Sequence(bonsai.core.tool.Sequence):
@classmethod
def animate_objects(cls, settings, frames, animation_type=""):
for obj in bpy.data.objects:
if not obj.BIMObjectProperties.ifc_definition_id:
element = tool.Ifc.get_entity(obj)
if not element:
continue
if tool.Ifc.get().by_id(obj.BIMObjectProperties.ifc_definition_id).is_a("IfcSpace"):
if element.is_a("IfcSpace"):
cls.hide_object(obj)
continue
cls.earliest_frame = None
product_frames = frames.get(obj.BIMObjectProperties.ifc_definition_id, [])
product_frames = frames.get(element.id(), [])
for product_frame in product_frames:
if product_frame["relationship"] == "input":
cls.animate_input(obj, settings["start_frame"], product_frame, animation_type)
+2 -2
View File
@@ -1180,9 +1180,9 @@ class Spatial(bonsai.core.tool.Spatial):
SpatialDecompositionData.data["default_container"] = SpatialDecompositionData.default_container()
project = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0])
project_collection = project.BIMObjectProperties.collection
project_collection = tool.Blender.get_object_bim_props(project).collection
obj = tool.Ifc.get_object(container)
if obj and (collection := obj.BIMObjectProperties.collection):
if obj and (collection := tool.Blender.get_object_bim_props(obj).collection):
for layer_collection in bpy.context.view_layer.layer_collection.children:
if layer_collection.collection == project_collection:
for layer_collection2 in layer_collection.children:
+2 -1
View File
@@ -110,7 +110,8 @@ class Structural(bonsai.core.tool.Structural):
def get_product_or_active_object(cls, product: str) -> Union[bpy.types.Object, None]:
product = bpy.data.objects.get(product) if product else bpy.context.active_object
try:
if product.BIMObjectProperties.ifc_definition_id:
props = tool.Blender.get_object_bim_props(product)
if props.ifc_definition_id:
return product
else:
return None
+1 -1
View File
@@ -33,7 +33,7 @@ class Surveyor(bonsai.core.tool.Surveyor):
M_TRANSLATION = (slice(0, 3), 3)
matrix = np.array(obj.matrix_world)
props = tool.Georeference.get_georeference_props()
if props.has_blender_offset and obj.BIMObjectProperties.blender_offset_type != "NOT_APPLICABLE":
if props.has_blender_offset and tool.Blender.get_object_bim_props(obj).blender_offset_type != "NOT_APPLICABLE":
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
coordinate_offset = tool.Geometry.get_cartesian_point_offset(obj)
if coordinate_offset is not None:
+1 -1
View File
@@ -189,7 +189,7 @@ class System(bonsai.core.tool.System):
container = ifcopenshell.util.element.get_container(element)
if container:
collection = tool.Ifc.get_object(container).BIMObjectProperties.collection
collection = tool.Blender.get_object_bim_props(tool.Ifc.get_object(container)).collection
ifc_importer.collections[container.GlobalId] = collection
ifc_importer.place_objects_in_collections()
+13 -13
View File
@@ -192,12 +192,12 @@ def the_object_name_does_not_exist(name):
def the_object_name_is_an_ifc_class(name, ifc_class):
ifc = an_ifc_file_exists()
element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id)
element = ifc.by_id(tool.Blender.get_ifc_definition_id(the_object_name_exists(name)))
assert element.is_a(ifc_class), f'Object "{name}" is an {element.is_a()}'
def the_object_name_is_not_an_ifc_element(name):
id = the_object_name_exists(name).BIMObjectProperties.ifc_definition_id
id = tool.Blender.get_ifc_definition_id(the_object_name_exists(name))
assert id == 0, f"The ID is {id}"
@@ -229,7 +229,7 @@ def the_object_name_is_placed_in_the_collection_collection(name, collection):
def the_object_name_has_a_type_representation_of_context(name, type, context):
ifc = an_ifc_file_exists()
element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id)
element = ifc.by_id(tool.Blender.get_ifc_definition_id(the_object_name_exists(name)))
context, subcontext, target_view = context.split("/")
assert ifcopenshell.util.representation.get_representation(
element, context, subcontext or None, target_view or None
@@ -238,7 +238,7 @@ def the_object_name_has_a_type_representation_of_context(name, type, context):
def the_object_name_is_contained_in_container_name(name, container_name):
ifc = an_ifc_file_exists()
element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id)
element = ifc.by_id(tool.Blender.get_ifc_definition_id(the_object_name_exists(name)))
container = ifcopenshell.util.element.get_container(element)
if not container:
assert False, f'Object "{name}" is not in any container'
@@ -257,8 +257,8 @@ def i_delete_the_selected_objects():
def the_object_name1_and_name2_are_different_elements(name1, name2):
ifc = an_ifc_file_exists()
element1 = ifc.by_id(the_object_name_exists(name1).BIMObjectProperties.ifc_definition_id)
element2 = ifc.by_id(the_object_name_exists(name2).BIMObjectProperties.ifc_definition_id)
element1 = ifc.by_id(tool.Blender.get_ifc_definition_id(the_object_name_exists(name1)))
element2 = ifc.by_id(tool.Blender.get_ifc_definition_id(the_object_name_exists(name2)))
assert element1 != element2, f"Objects {name1} and {name2} have same elements {element1} and {element2}"
@@ -284,7 +284,7 @@ def the_object_name1_has_no_boolean_difference_by_name2(name1, name2):
def the_object_name_is_voided_by_void(name, void):
ifc = tool.Ifc.get()
element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id)
element = ifc.by_id(tool.Blender.get_ifc_definition_id(the_object_name_exists(name)))
for rel in element.HasOpenings:
if rel.RelatedOpeningElement.Name == void:
return True
@@ -293,7 +293,7 @@ def the_object_name_is_voided_by_void(name, void):
def the_object_name_is_not_voided_by_void(name, void):
ifc = tool.Ifc.get()
element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id)
element = ifc.by_id(tool.Blender.get_ifc_definition_id(the_object_name_exists(name)))
for rel in element.HasOpenings:
if rel.RelatedOpeningElement.Name == void:
assert False, "A void was found"
@@ -301,21 +301,21 @@ def the_object_name_is_not_voided_by_void(name, void):
def the_object_name_is_not_voided(name):
ifc = tool.Ifc.get()
element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id)
element = ifc.by_id(tool.Blender.get_ifc_definition_id(the_object_name_exists(name)))
if any(element.HasOpenings):
assert False, "An opening was found"
def the_object_name_is_not_a_void(name):
ifc = tool.Ifc.get()
element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id)
element = ifc.by_id(tool.Blender.get_ifc_definition_id(the_object_name_exists(name)))
if any(element.VoidsElements):
assert False, "A void was found"
def the_void_name_is_filled_by_filling(name, filling):
ifc = tool.Ifc.get()
element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id)
element = ifc.by_id(tool.Blender.get_ifc_definition_id(the_object_name_exists(name)))
if any(rel.RelatedBuildingElement.Name == filling for rel in element.HasFillings):
return True
assert False, "No filling found"
@@ -323,14 +323,14 @@ def the_void_name_is_filled_by_filling(name, filling):
def the_void_name_is_not_filled_by_filling(name, filling):
ifc = tool.Ifc.get()
element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id)
element = ifc.by_id(tool.Blender.get_ifc_definition_id(the_object_name_exists(name)))
if any(rel.RelatedBuildingElement.Name == filling for rel in element.HasFillings):
assert False, "A filling was found"
def the_object_name_is_not_a_filling(name):
ifc = tool.Ifc.get()
element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id)
element = ifc.by_id(tool.Blender.get_ifc_definition_id(the_object_name_exists(name)))
if any(element.FillsVoids):
assert False, "A filling was found"
+19 -18
View File
@@ -718,8 +718,8 @@ def the_collection_exclude_status_is(name: str, exclude: str) -> None:
@then(parsers.parse('the object "{name1}" and "{name2}" are different elements'))
def the_object_name1_and_name2_are_different_elements(name1, name2):
ifc = an_ifc_file_exists()
element1 = ifc.by_id(the_object_name_exists(name1).BIMObjectProperties.ifc_definition_id)
element2 = ifc.by_id(the_object_name_exists(name2).BIMObjectProperties.ifc_definition_id)
element1 = ifc.by_id(tool.Blender.get_ifc_definition_id(the_object_name_exists(name1)))
element2 = ifc.by_id(tool.Blender.get_ifc_definition_id(the_object_name_exists(name2)))
assert element1 != element2, f"Objects {name1} and {name2} have same elements {element1} and {element2}"
@@ -732,7 +732,7 @@ def the_object_name_has_a_body_of_value(name, value):
@then(parsers.parse('the object "{name}" has a "{type}" representation of "{context}"'))
def the_object_name_has_a_representation_type_of_context(name, type, context):
ifc = an_ifc_file_exists()
element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id)
element = ifc.by_id(tool.Blender.get_ifc_definition_id(the_object_name_exists(name)))
context, subcontext, target_view = context.split("/")
rep = ifcopenshell.util.representation.get_representation(element, context, subcontext or None, target_view or None)
assert rep
@@ -808,7 +808,7 @@ def the_object_name_should_display_as_mode(name, mode):
@then(parsers.parse('the object "{name}" is voided by "{void}"'))
def the_object_name_is_voided_by_void(name, void):
ifc = tool.Ifc.get()
element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id)
element = ifc.by_id(tool.Blender.get_ifc_definition_id(the_object_name_exists(name)))
assert any((rel for rel in element.HasOpenings if rel.RelatedOpeningElement.Name == void)), "No void found"
@@ -824,7 +824,7 @@ def the_object_name_is_not_voided_by_void(name, void):
@then(parsers.parse('the object "{name}" is not voided'))
def the_object_name_is_not_voided(name):
ifc = tool.Ifc.get()
element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id)
element = ifc.by_id(tool.Blender.get_ifc_definition_id(the_object_name_exists(name)))
assert not element.HasOpenings, "A void was found"
@@ -832,7 +832,7 @@ def the_object_name_is_not_voided(name):
def the_object_name_is_a_void(name):
ifc = tool.Ifc.get()
obj = the_object_name_exists(name)
element = ifc.by_id(obj.BIMObjectProperties.ifc_definition_id)
element = ifc.by_id(tool.Blender.get_ifc_definition_id(obj))
assert any((element.VoidsElements)), "No void was found"
@@ -872,14 +872,14 @@ def the_object_name_is_not_visible(name):
@then(parsers.parse('the object "{name}" is an "{ifc_class}"'))
def the_object_name_is_an_ifc_class(name, ifc_class):
ifc = an_ifc_file_exists()
element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id)
element = ifc.by_id(tool.Blender.get_ifc_definition_id(the_object_name_exists(name)))
assert element.is_a(ifc_class), f'Object "{name}" is an {element.is_a()}'
@then(parsers.parse('the object "{name}" is not an IFC element'))
def the_object_name_is_not_an_ifc_element(name):
obj = the_object_name_exists(name)
ifc_definition_id = obj.BIMObjectProperties.ifc_definition_id
ifc_definition_id = tool.Blender.get_ifc_definition_id(obj)
assert ifc_definition_id == 0, f"The object {obj} has an ID of {ifc_definition_id}"
@@ -897,14 +897,14 @@ def the_object_name_has_ifc_representation_data(name):
@then(parsers.parse('the material "{name}" is an IFC material'))
def the_material_name_is_an_ifc_material(name):
obj = the_material_name_exists(name)
ifc_definition_id = obj.BIMObjectProperties.ifc_definition_id
ifc_definition_id = tool.Blender.get_ifc_definition_id(obj)
assert ifc_definition_id != 0, f"The material {obj} has no ID: {ifc_definition_id}"
@then(parsers.parse('the material "{name}" is not an IFC material'))
def the_material_name_is_not_an_ifc_material(name):
obj = the_material_name_exists(name)
ifc_definition_id = obj.BIMObjectProperties.ifc_definition_id
ifc_definition_id = tool.Blender.get_ifc_definition_id(obj)
assert ifc_definition_id == 0, f"The material {obj} has an ID of {ifc_definition_id}"
@@ -937,7 +937,7 @@ def the_object_name_has_number_vertices(name, number):
@then(parsers.parse('the void "{name}" is filled by "{filling}"'))
def the_void_name_is_filled_by_filling(name, filling):
ifc = tool.Ifc.get()
element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id)
element = ifc.by_id(tool.Blender.get_ifc_definition_id(the_object_name_exists(name)))
assert any((rel.RelatedBuildingElement.Name == filling for rel in element.HasFillings)), "No filling found"
@@ -954,7 +954,7 @@ def the_void_name_is_not_filled_by_filling(name, filling):
@then(parsers.parse('the object "{name}" is not a filling'))
def the_object_name_is_not_a_filling(name):
ifc = tool.Ifc.get()
element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id)
element = ifc.by_id(tool.Blender.get_ifc_definition_id(the_object_name_exists(name)))
assert not any(element.FillsVoids), "A filling was found"
@@ -1010,8 +1010,9 @@ def prop_is_roughly_value(prop, value):
def the_object_name_has_a_cartesian_point_offset_of_offset(name: str, offset: str) -> None:
offset = replace_variables(offset)
obj = the_object_name_exists(name)
assert obj.BIMObjectProperties.blender_offset_type == "CARTESIAN_POINT"
obj_offset = np.array(tuple(map(float, obj.BIMObjectProperties.cartesian_point_offset.split(","))))
props = tool.Blender.get_object_props(obj)
assert props.blender_offset_type == "CARTESIAN_POINT"
obj_offset = np.array(tuple(map(float, props.cartesian_point_offset.split(","))))
offset = np.array(tuple(map(float, offset.split(","))))
assert np.allclose(obj_offset, offset)
@@ -1169,7 +1170,7 @@ def the_object_name_bottom_left_corner_is_at_location(name, location):
@then(parsers.parse('the object "{name}" is contained in "{container_name}"'))
def the_object_name_is_contained_in_container_name(name, container_name):
ifc = an_ifc_file_exists()
element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id)
element = ifc.by_id(tool.Blender.get_ifc_definition_id(the_object_name_exists(name)))
container = ifcopenshell.util.element.get_container(element)
if not container:
assert False, f'Object "{name}" is not in any container'
@@ -1179,7 +1180,7 @@ def the_object_name_is_contained_in_container_name(name, container_name):
@then(parsers.parse('the object "{name}" is contained in object "{container_name}"'))
def the_object_name_is_contained_in_object_container_name(name: str, container_name: str) -> None:
ifc = an_ifc_file_exists()
element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id)
element = ifc.by_id(tool.Blender.get_ifc_definition_id(the_object_name_exists(name)))
container = ifcopenshell.util.element.get_container(element)
if not container:
assert False, f'Object "{name}" is not in any container'
@@ -1190,7 +1191,7 @@ def the_object_name_is_contained_in_object_container_name(name: str, container_n
@then(parsers.parse('the object "{name}" is aggregated by object "{aggregate_name}"'))
def the_object_name_is_aggregated_by_object_aggregate_name(name: str, aggregate_name: str) -> None:
ifc = an_ifc_file_exists()
element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id)
element = ifc.by_id(tool.Blender.get_ifc_definition_id(the_object_name_exists(name)))
aggregate = ifcopenshell.util.element.get_aggregate(element)
if not aggregate:
assert False, f'Object "{name}" is not aggregated by any element'
@@ -1201,7 +1202,7 @@ def the_object_name_is_aggregated_by_object_aggregate_name(name: str, aggregate_
@then(parsers.parse('the object "{name}" has no aggregate'))
def the_object_name_has_no_aggregate(name: str) -> None:
ifc = an_ifc_file_exists()
element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id)
element = ifc.by_id(tool.Blender.get_ifc_definition_id(the_object_name_exists(name)))
aggregate = ifcopenshell.util.element.get_aggregate(element)
if aggregate:
assert False, f'Object "{name}" is aggregated by element "{aggregate}"'
+3 -1
View File
@@ -19,6 +19,7 @@
import bpy
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.api.aggregate
import ifcopenshell.util.element
import bonsai.core.tool
import bonsai.tool as tool
@@ -167,7 +168,8 @@ class TestAssign(NewIfc):
tool.Ifc.link(building_element, building_obj)
building_collection = bpy.data.collections.new("Foobar")
bpy.context.scene.collection.children.link(building_collection)
building_obj.BIMObjectProperties.collection = building_collection
props = tool.Blender.get_object_bim_props(building_obj)
props.collection = building_collection
building_collection.objects.link(building_obj)
ifcopenshell.api.aggregate.assign_object(
tool.Ifc.get(),
+2 -1
View File
@@ -319,7 +319,8 @@ class TestGetDrawingCollection(NewFile):
collection = bpy.data.collections.new("Collection")
bpy.context.scene.collection.children.link(collection)
collection.objects.link(obj)
obj.BIMObjectProperties.collection = collection
props = tool.Blender.get_object_bim_props(obj)
props.collection = collection
collection.BIMCollectionProperties.obj = obj
element = ifc.createIfcAnnotation()
+11 -7
View File
@@ -170,22 +170,25 @@ class TestGetTextLiteral(NewFile):
class TestGetCartesianPointCoordinateOffset(NewFile):
def test_run(self):
obj = bpy.data.objects.new("Object", None)
obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT"
oprops = tool.Blender.get_object_bim_props(obj)
oprops.blender_offset_type = "CARTESIAN_POINT"
props = tool.Georeference.get_georeference_props()
props.has_blender_offset = True
obj.BIMObjectProperties.cartesian_point_offset = "1,2,3"
oprops.cartesian_point_offset = "1,2,3"
assert np.allclose(subject.get_cartesian_point_offset(obj), np.array((1.0, 2.0, 3.0)))
def test_get_null_if_not_a_cartesian_point_offset_type(self):
obj = bpy.data.objects.new("Object", None)
props = tool.Georeference.get_georeference_props()
props.has_blender_offset = True
obj.BIMObjectProperties.cartesian_point_offset = "1,2,3"
oprops = tool.Blender.get_object_bim_props(obj)
oprops.cartesian_point_offset = "1,2,3"
assert subject.get_cartesian_point_offset(obj) is None
def test_get_null_if_no_blender_offset(self):
obj = bpy.data.objects.new("Object", None)
obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT"
oprops = tool.Blender.get_object_bim_props(obj)
oprops.blender_offset_type = "CARTESIAN_POINT"
props = tool.Georeference.get_georeference_props()
props.has_blender_offset = False
assert subject.get_cartesian_point_offset(obj) is None
@@ -307,15 +310,16 @@ class TestRecordObjectMaterials(NewFile):
material.BIMStyleProperties.ifc_definition_id = style.id()
obj.data.materials.append(material)
subject.record_object_materials(obj)
assert tool.Geometry.get_mesh_props(obj).material_checksum == str([style.id()])
assert tool.Geometry.get_mesh_props(obj.data).material_checksum == str([style.id()])
class TestRecordObjectPosition(NewFile):
def test_run(self):
obj = bpy.data.objects.new("Object", None)
props = tool.Blender.get_object_bim_props(obj)
subject.record_object_position(obj)
assert obj.BIMObjectProperties.location_checksum == repr(np.array(obj.matrix_world.translation).tobytes())
assert obj.BIMObjectProperties.rotation_checksum == repr(np.array(obj.matrix_world.to_3x3()).tobytes())
assert props.location_checksum == repr(np.array(obj.matrix_world.translation).tobytes())
assert props.rotation_checksum == repr(np.array(obj.matrix_world.to_3x3()).tobytes())
class TestRemoveConnection(NewFile):
+4 -2
View File
@@ -129,14 +129,16 @@ class TestGetEntity(test.bim.bootstrap.NewFile):
def test_attempting_without_a_file(self):
obj = bpy.data.objects.new("Object", None)
obj.BIMObjectProperties.ifc_definition_id = 1
props = tool.Blender.get_object_bim_props(obj)
props.ifc_definition_id = 1
assert subject.get_entity(obj) is None
def test_attempting_to_get_an_invalidly_linked_object(self):
ifc = ifcopenshell.file()
subject.set(ifc)
obj = bpy.data.objects.new("Object", None)
obj.BIMObjectProperties.ifc_definition_id = 1
props = tool.Blender.get_object_bim_props(obj)
props.ifc_definition_id = 1
assert subject.get_entity(obj) is None
+9 -6
View File
@@ -383,23 +383,26 @@ class TestUsingArrays(NewFile):
bpy.ops.mesh.primitive_cube_add()
obj = bpy.context.active_object
assert obj
rprops = tool.Root.get_root_props()
rprops.ifc_product = "IfcElement"
bpy.ops.bim.assign_class(ifc_class="IfcActuator", predefined_type="ELECTRICACTUATOR", userdefined_type="")
bpy.ops.bim.add_array()
bpy.ops.bim.enable_editing_array(item=0)
obj.BIMArrayProperties.count = 4
obj.BIMArrayProperties.x = 4
obj.BIMArrayProperties.sync_children = sync_children
props = tool.Model.get_array_props(obj)
props.count = 4
props.x = 4
props.sync_children = sync_children
bpy.ops.bim.edit_array(item=0)
if add_second_layer:
bpy.ops.bim.add_array()
bpy.ops.bim.enable_editing_array(item=1)
obj.BIMArrayProperties.count = 3
obj.BIMArrayProperties.y = 4
obj.BIMArrayProperties.sync_children = sync_children
props = tool.Model.get_array_props(obj)
props.count = 3
props.y = 4
props.sync_children = sync_children
bpy.ops.bim.edit_array(item=1)
def test_remove_array_last_to_first(self):
+2 -1
View File
@@ -54,6 +54,7 @@ class TestGetGlobalMatrix(test.bim.bootstrap.NewFile):
props.blender_x_axis_abscissa = "0"
props.blender_x_axis_ordinate = "1"
obj = bpy.data.objects.new("Object", None)
obj.BIMObjectProperties.blender_offset_type = "OBJECT_PLACEMENT"
props = tool.Blender.get_object_bim_props(obj)
props.blender_offset_type = "OBJECT_PLACEMENT"
matrix = ifcopenshell.util.geolocation.local2global(np.array(obj.matrix_world), 1.0, 2.0, 3.0, 0.0, 1.0)
assert (subject.get_absolute_matrix(obj) == matrix).all()
+1 -1
View File
@@ -240,7 +240,7 @@ class TestImportUnitAttributes(NewFile):
assert props.unit_attributes["UnitType"].enum_value == "ABSORBEDDOSEUNIT"
assert props.unit_attributes["Prefix"].enum_value == "EXA"
assert props.unit_attributes["Name"].enum_value == "AMPERE"
assert props.unit_attributes["Dimensions"] is None
assert "Dimensions" not in props.unit_attributes
class TestImportUnits(NewFile):
@@ -442,7 +442,7 @@ class Usecase:
panel_schema: list[list[int]] = self.settings["panel_schema"]
panels: list[dict[str, Any]] = self.settings["panel_properties"]
accumulated_height = [0] * len(panel_schema[0])
accumulated_height: list[float] = [0] * len(panel_schema[0])
built_panels: list[int] = []
window_items: list[ifcopenshell.entity_instance] = []
lining_items: list[ifcopenshell.entity_instance] = []
@@ -96,7 +96,8 @@ class Patcher:
angle_threshold = 0.3
for obj in bpy.data.objects:
if not obj.BIMObjectProperties.ifc_definition_id or not obj.data:
ifc_id = tool.Blender.get_ifc_definition_id(obj)
if not ifc_id or not obj.data:
continue
if not obj.data.polygons:
continue