This commit is contained in:
Andrej730
2025-02-19 11:06:03 +05:00
parent 6c511f6b1b
commit 67bac441ad
92 changed files with 1261 additions and 837 deletions
+2 -1
View File
@@ -132,7 +132,8 @@ class IfcExporter:
bpy.ops.bim.update_representation(obj=obj.name) bpy.ops.bim.update_representation(obj=obj.name)
def has_changed_materials(self, obj: bpy.types.Object) -> bool: def has_changed_materials(self, obj: bpy.types.Object) -> bool:
checksum = obj.data.BIMMeshProperties.material_checksum mprops = tool.Geometry.get_mesh_props(obj.data)
checksum = mprops.material_checksum
return checksum != tool.Geometry.get_material_checksum(obj) return checksum != tool.Geometry.get_material_checksum(obj)
def sync_object_placement(self, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]: def sync_object_placement(self, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
+3 -2
View File
@@ -221,7 +221,8 @@ def refresh_ui_data():
if isinstance(tool.Ifc.get(), ifcopenshell.sqlite): if isinstance(tool.Ifc.get(), ifcopenshell.sqlite):
tool.Ifc.get().clear_cache() tool.Ifc.get().clear_cache()
bpy.context.scene.DocProperties.should_draw_decorations = bpy.context.scene.DocProperties.should_draw_decorations props = tool.Drawing.get_document_props()
props.should_draw_decorations = props.should_draw_decorations
if bpy.context.scene.WebProperties.is_connected: if bpy.context.scene.WebProperties.is_connected:
tool.Web.send_webui_data() tool.Web.send_webui_data()
@@ -343,7 +344,7 @@ def load_post(scene):
bpy.context.scene.BIMProperties.has_blend_warning = True bpy.context.scene.BIMProperties.has_blend_warning = True
# Bonsai overlays # Bonsai overlays
georeference_props = bpy.context.scene.BIMGeoreferenceProperties georeference_props = tool.Georeference.get_georeference_props()
aggregate_props = bpy.context.scene.BIMAggregateProperties aggregate_props = bpy.context.scene.BIMAggregateProperties
nest_props = bpy.context.scene.BIMNestProperties nest_props = bpy.context.scene.BIMNestProperties
model_props = tool.Model.get_model_props() model_props = tool.Model.get_model_props()
+5 -5
View File
@@ -178,6 +178,7 @@ class IfcStore:
if not os.path.isfile(path): if not os.path.isfile(path):
return return
extension = path.split(".")[-1] extension = path.split(".")[-1]
props = tool.Project.get_project_props()
if extension.lower() == "ifczip": if extension.lower() == "ifczip":
with tempfile.TemporaryDirectory() as unzipped_path: with tempfile.TemporaryDirectory() as unzipped_path:
with zipfile.ZipFile(path, "r") as zip_ref: with zipfile.ZipFile(path, "r") as zip_ref:
@@ -187,7 +188,7 @@ class IfcStore:
return return
elif extension.lower() == "ifcxml": elif extension.lower() == "ifcxml":
IfcStore.file = ifcopenshell.file(ifcopenshell.ifcopenshell_wrapper.parse_ifcxml(path)) IfcStore.file = ifcopenshell.file(ifcopenshell.ifcopenshell_wrapper.parse_ifcxml(path))
elif bpy.context.scene.BIMProjectProperties.should_stream: elif props.should_stream:
IfcStore.file = ifcopenshell.open(path, should_stream=True) IfcStore.file = ifcopenshell.open(path, should_stream=True)
else: else:
IfcStore.file = ifcopenshell.open(path) IfcStore.file = ifcopenshell.open(path)
@@ -195,9 +196,8 @@ class IfcStore:
@staticmethod @staticmethod
def get_schema() -> ifcopenshell.ifcopenshell_wrapper.schema_definition: def get_schema() -> ifcopenshell.ifcopenshell_wrapper.schema_definition:
if IfcStore.file is None: if IfcStore.file is None:
IfcStore.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name( props = tool.Project.get_project_props()
bpy.context.scene.BIMProjectProperties.export_schema IfcStore.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(props.export_schema)
)
elif IfcStore.schema is None: elif IfcStore.schema is None:
IfcStore.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(IfcStore.file.schema_identifier) IfcStore.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(IfcStore.file.schema_identifier)
return IfcStore.schema return IfcStore.schema
@@ -247,7 +247,7 @@ class IfcStore:
# refactor this class and deprecate usage of IfcStore in favour of # refactor this class and deprecate usage of IfcStore in favour of
# tools. # tools.
if not isinstance(obj, (bpy.types.Object, bpy.types.Material)): if not isinstance(obj, (bpy.types.Object, bpy.types.Material)):
obj.BIMMeshProperties.ifc_definition_id = element.id() tool.Geometry.get_mesh_props(obj).ifc_definition_id = element.id()
return return
existing_obj = IfcStore.id_map.get(element.id(), None) existing_obj = IfcStore.id_map.get(element.id(), None)
+4 -3
View File
@@ -90,7 +90,8 @@ class MaterialCreator:
return # Already has materials assign to the representation itself return # Already has materials assign to the representation itself
# Otherwise, we need to check for material styles on the element, since # Otherwise, we need to check for material styles on the element, since
# create_shape on types only works on representations. # create_shape on types only works on representations.
context = tool.Ifc.get().by_id(self.mesh.BIMMeshProperties.ifc_definition_id).ContextOfItems mprops = tool.Geometry.get_mesh_props(self.mesh)
context = tool.Ifc.get().by_id(mprops.ifc_definition_id).ContextOfItems
for material in ifcopenshell.util.element.get_materials(element): for material in ifcopenshell.util.element.get_materials(element):
if style := ifcopenshell.util.representation.get_material_style(material, context): if style := ifcopenshell.util.representation.get_material_style(material, context):
self.mesh["ios_materials"] = (style.id(),) self.mesh["ios_materials"] = (style.id(),)
@@ -218,7 +219,7 @@ class IfcImporter:
self.progress = 0 self.progress = 0
self.material_creator = MaterialCreator(ifc_import_settings, self) self.material_creator = MaterialCreator(ifc_import_settings, self)
classes_to_wireframe_str = bpy.context.scene.DocProperties.classes_to_wireframe classes_to_wireframe_str = tool.Drawing.get_document_props().classes_to_wireframe
self.classes_to_wireframe_list = [word.strip() for word in classes_to_wireframe_str.split(",")] self.classes_to_wireframe_list = [word.strip() for word in classes_to_wireframe_str.split(",")]
def profile_code(self, message: str) -> None: def profile_code(self, message: str) -> None:
@@ -434,7 +435,7 @@ class IfcImporter:
return False return False
def calculate_model_offset(self) -> None: def calculate_model_offset(self) -> None:
props = bpy.context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
if self.ifc_import_settings.false_origin_mode == "MANUAL": if self.ifc_import_settings.false_origin_mode == "MANUAL":
tool.Loader.set_manual_blender_offset(self.file) tool.Loader.set_manual_blender_offset(self.file)
elif self.ifc_import_settings.false_origin_mode == "AUTOMATIC": elif self.ifc_import_settings.false_origin_mode == "AUTOMATIC":
+1 -1
View File
@@ -1316,7 +1316,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
[0, 0, 0, 1], [0, 0, 0, 1],
) )
) )
props = context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
if props.has_blender_offset: if props.has_blender_offset:
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file) unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
matrix = ifcopenshell.util.geolocation.global2local( matrix = ifcopenshell.util.geolocation.global2local(
@@ -114,7 +114,7 @@ class Loader:
bm.edges.new((verts[-1], verts[0])) bm.edges.new((verts[-1], verts[0]))
bm.to_mesh(mesh) bm.to_mesh(mesh)
bm.free() bm.free()
mesh.BIMMeshProperties.ifc_definition_id = surface.id() tool.Ifc.link(surface, mesh)
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
matrix = mathutils.Matrix( matrix = mathutils.Matrix(
ifcopenshell.util.placement.get_axis2placement(surface.BasisSurface.Position).tolist() ifcopenshell.util.placement.get_axis2placement(surface.BasisSurface.Position).tolist()
+25 -8
View File
@@ -69,10 +69,12 @@ class CadTool(WorkSpaceTool):
("bim.cad_hotkey", {"type": "X", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_X")]}), ("bim.cad_hotkey", {"type": "X", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_X")]}),
) )
def draw_settings(context, layout, workspace_tool): def draw_settings(
context: bpy.types.Context, layout: bpy.types.UILayout, workspace_tool: bpy.types.WorkSpaceTool
) -> None:
ui_context = str(context.region.type) ui_context = str(context.region.type)
obj = context.active_object obj = context.active_object
if not obj or not obj.data: if not obj or not (data := obj.data):
return return
is_profile = tool.Geometry.is_profile_object_active() is_profile = tool.Geometry.is_profile_object_active()
if is_profile: if is_profile:
@@ -122,7 +124,10 @@ class CadTool(WorkSpaceTool):
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True) row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
add_layout_hotkey_operator(row, "Reset Vertex", "S_X", bpy.ops.bim.reset_vertex.__doc__, ui_context) add_layout_hotkey_operator(row, "Reset Vertex", "S_X", bpy.ops.bim.reset_vertex.__doc__, ui_context)
elif hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.subshape_type == "AXIS": elif (
isinstance(data, tool.Geometry.TYPES_WITH_MESH_PROPERTIES)
and tool.Geometry.get_mesh_props(data).subshape_type == "AXIS"
):
add_header_apply_button( add_header_apply_button(
layout, "Edit Axis", "bim.edit_extrusion_axis", "bim.disable_editing_extrusion_axis", ui_context layout, "Edit Axis", "bim.edit_extrusion_axis", "bim.disable_editing_extrusion_axis", ui_context
) )
@@ -141,7 +146,7 @@ class CadTool(WorkSpaceTool):
if ( if (
(RailingData.is_loaded or not RailingData.load()) (RailingData.is_loaded or not RailingData.load())
and RailingData.data["pset_data"] and RailingData.data["pset_data"]
and context.active_object.BIMRailingProperties.is_editing_path and obj.BIMRailingProperties.is_editing_path
): ):
add_header_apply_button( add_header_apply_button(
layout, layout,
@@ -154,7 +159,7 @@ class CadTool(WorkSpaceTool):
elif ( elif (
(RoofData.is_loaded or not RoofData.load()) (RoofData.is_loaded or not RoofData.load())
and RoofData.data["pset_data"] and RoofData.data["pset_data"]
and context.active_object.BIMRoofProperties.is_editing_path and obj.BIMRoofProperties.is_editing_path
): ):
add_header_apply_button( add_header_apply_button(
layout, "Edit Roof Path", "bim.finish_editing_roof_path", "bim.cancel_editing_roof_path", ui_context layout, "Edit Roof Path", "bim.finish_editing_roof_path", "bim.cancel_editing_roof_path", ui_context
@@ -252,15 +257,27 @@ class CadHotkey(bpy.types.Operator):
bpy.ops.bim.cad_offset(distance=self.props.distance / si_conversion) bpy.ops.bim.cad_offset(distance=self.props.distance / si_conversion)
def hotkey_S_Q(self): def hotkey_S_Q(self):
element = tool.Ifc.get_entity(bpy.context.active_object) obj = bpy.context.active_object
if bpy.context.active_object.data.BIMMeshProperties.subshape_type == "PROFILE":
if not obj:
return
if not tool.Geometry.has_mesh_properties(data := obj.data):
return
element = tool.Ifc.get_entity(obj)
if not element:
return
mprops = tool.Geometry.get_mesh_props(data)
if mprops.subshape_type == "PROFILE":
if element.is_a("IfcProfileDef"): if element.is_a("IfcProfileDef"):
bpy.ops.bim.edit_arbitrary_profile() bpy.ops.bim.edit_arbitrary_profile()
elif element.is_a("IfcRelSpaceBoundary"): elif element.is_a("IfcRelSpaceBoundary"):
bpy.ops.bim.edit_boundary_geometry() bpy.ops.bim.edit_boundary_geometry()
else: else:
bpy.ops.bim.edit_extrusion_profile() bpy.ops.bim.edit_extrusion_profile()
elif bpy.context.active_object.data.BIMMeshProperties.subshape_type == "AXIS": elif mprops.subshape_type == "AXIS":
bpy.ops.bim.edit_extrusion_axis() bpy.ops.bim.edit_extrusion_axis()
def hotkey_S_R(self): def hotkey_S_R(self):
@@ -201,7 +201,8 @@ class EnableEditingClassification(bpy.types.Operator):
def execute(self, context): def execute(self, context):
def callback(name, prop, data): def callback(name, prop, data):
if name == "ReferenceTokens": if name == "ReferenceTokens":
new = bpy.context.scene.BIMGeoreferenceProperties.projected_crs.add() geo_props = tool.Georeference.get_georeference_props()
new = geo_props.projected_crs.add()
new.name = name new.name = name
new.data_type = "string" new.data_type = "string"
new.is_null = data[name] is None new.is_null = data[name] is None
+14 -12
View File
@@ -101,13 +101,13 @@ class ConvertToBlender(bpy.types.Operator):
if tool.Geometry.has_mesh_properties(data): if tool.Geometry.has_mesh_properties(data):
if data.library: if data.library:
continue continue
data.BIMMeshProperties.ifc_definition_id = 0 tool.Geometry.get_mesh_props(data).ifc_definition_id = 0
for material in bpy.data.materials: for material in bpy.data.materials:
if material.library: if material.library:
continue continue
tool.Ifc.unlink(obj=material) tool.Ifc.unlink(obj=material)
context.scene.BIMProperties.ifc_file = "" context.scene.BIMProperties.ifc_file = ""
context.scene.BIMDebugProperties.attributes.clear() tool.Debug.get_debug_props().attributes.clear()
IfcStore.purge() IfcStore.purge()
bonsai.bim.handler.refresh_ui_data() bonsai.bim.handler.refresh_ui_data()
return {"FINISHED"} return {"FINISHED"}
@@ -256,7 +256,7 @@ class CreateShapeFromStepId(bpy.types.Operator):
logger = logging.getLogger("ImportIFC") logger = logging.getLogger("ImportIFC")
self.ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger) self.ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger)
self.file = tool.Ifc.get() self.file = tool.Ifc.get()
element = self.file.by_id(self.step_id or int(context.scene.BIMDebugProperties.step_id)) element = self.file.by_id(self.step_id or int(tool.Debug.get_debug_props().step_id))
settings = ifcopenshell.geom.settings() settings = ifcopenshell.geom.settings()
settings.set("keep-bounding-boxes", True) settings.set("keep-bounding-boxes", True)
if self.should_include_curves: if self.should_include_curves:
@@ -309,7 +309,7 @@ class RewindInspector(bpy.types.Operator):
bl_description = "Rewind the Inspector to the previously inspected element" bl_description = "Rewind the Inspector to the previously inspected element"
def execute(self, context): def execute(self, context):
props = context.scene.BIMDebugProperties props = tool.Debug.get_debug_props()
total_breadcrumbs = len(props.step_id_breadcrumb) total_breadcrumbs = len(props.step_id_breadcrumb)
if total_breadcrumbs < 2: if total_breadcrumbs < 2:
return {"FINISHED"} return {"FINISHED"}
@@ -332,9 +332,9 @@ class InspectFromStepId(bpy.types.Operator):
def execute(self, context): def execute(self, context):
self.file = IfcStore.get_file() self.file = IfcStore.get_file()
debug_props = context.scene.BIMDebugProperties debug_props = tool.Debug.get_debug_props()
debug_props.active_step_id = self.step_id debug_props.active_step_id = self.step_id
crumb = context.scene.BIMDebugProperties.step_id_breadcrumb.add() crumb = debug_props.step_id_breadcrumb.add()
crumb.name = str(self.step_id) crumb.name = str(self.step_id)
element = self.file.by_id(self.step_id) element = self.file.by_id(self.step_id)
debug_props.attributes.clear() debug_props.attributes.clear()
@@ -385,7 +385,7 @@ class InspectFromObject(bpy.types.Operator):
if ( if (
(data := obj.data) (data := obj.data)
and tool.Geometry.has_mesh_properties(data) and tool.Geometry.has_mesh_properties(data)
and (ifc_id := data.BIMMeshProperties.ifc_definition_id) and (ifc_id := tool.Geometry.get_mesh_props(data).ifc_definition_id)
): ):
return ifc_id return ifc_id
@@ -438,7 +438,8 @@ class ParseExpress(bpy.types.Operator):
bl_label = "Parse Express" bl_label = "Parse Express"
def execute(self, context): def execute(self, context):
core.parse_express(tool.Debug, context.scene.BIMDebugProperties.express_file) props = tool.Debug.get_debug_props()
core.parse_express(tool.Debug, props.express_file)
bonsai.bim.handler.refresh_ui_data() bonsai.bim.handler.refresh_ui_data()
return {"FINISHED"} return {"FINISHED"}
@@ -452,8 +453,9 @@ class SelectExpressFile(bpy.types.Operator):
filter_glob: bpy.props.StringProperty(default="*.exp", options={"HIDDEN"}) filter_glob: bpy.props.StringProperty(default="*.exp", options={"HIDDEN"})
def execute(self, context): def execute(self, context):
props = tool.Debug.get_debug_props()
if os.path.exists(self.filepath) and "exp" in os.path.splitext(self.filepath)[1]: if os.path.exists(self.filepath) and "exp" in os.path.splitext(self.filepath)[1]:
context.scene.BIMDebugProperties.express_file = self.filepath props.express_file = self.filepath
return {"FINISHED"} return {"FINISHED"}
def invoke(self, context, event): def invoke(self, context, event):
@@ -497,7 +499,7 @@ class PrintUnusedElementStats(bpy.types.Operator):
ignore_styled_items: bpy.props.BoolProperty(name="Ignore Styled Items", default=True) ignore_styled_items: bpy.props.BoolProperty(name="Ignore Styled Items", default=True)
def execute(self, context): def execute(self, context):
props = context.scene.BIMDebugProperties props = tool.Debug.get_debug_props()
# ignore some classes that could have zero 0 inverse references by their nature # ignore some classes that could have zero 0 inverse references by their nature
ignore_classes = [] ignore_classes = []
if self.ignore_contexts: if self.ignore_contexts:
@@ -543,7 +545,7 @@ class PurgeUnusedElementsByClass(bpy.types.Operator, tool.Ifc.Operator):
return True return True
def _execute(self, context): def _execute(self, context):
props = context.scene.BIMDebugProperties props = tool.Debug.get_debug_props()
if props.ifc_class_purge: if props.ifc_class_purge:
purged_elements = core.purge_unused_elements(tool.Ifc, tool.Debug, props.ifc_class_purge) purged_elements = core.purge_unused_elements(tool.Ifc, tool.Debug, props.ifc_class_purge)
self.report({"INFO"}, f"{purged_elements} unused elements found and removed.") self.report({"INFO"}, f"{purged_elements} unused elements found and removed.")
@@ -803,7 +805,7 @@ class DebugActiveDrawing(bpy.types.Operator):
) )
def execute(self, context: bpy.types.Context): def execute(self, context: bpy.types.Context):
props = context.scene.DocProperties props = tool.Drawing.get_document_props()
drawing_item = props.drawings[props.active_drawing_index] drawing_item = props.drawings[props.active_drawing_index]
drawing = tool.Ifc.get().by_id(drawing_item.ifc_definition_id) drawing = tool.Ifc.get().by_id(drawing_item.ifc_definition_id)
+19 -6
View File
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>. # along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bpy
from bonsai.bim.prop import StrProperty, Attribute from bonsai.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup from bpy.types import PropertyGroup
from bpy.props import ( from bpy.props import (
@@ -28,6 +29,9 @@ from bpy.props import (
FloatVectorProperty, FloatVectorProperty,
CollectionProperty, CollectionProperty,
) )
from typing import TYPE_CHECKING, Literal, get_args
DisplayType = Literal["BOUNDS", "WIRE", "SOLID", "TEXTURED"]
class BIMDebugProperties(PropertyGroup): class BIMDebugProperties(PropertyGroup):
@@ -41,14 +45,23 @@ class BIMDebugProperties(PropertyGroup):
inverse_references: CollectionProperty(name="Inverse References", type=Attribute) inverse_references: CollectionProperty(name="Inverse References", type=Attribute)
express_file: StringProperty(name="Express File") express_file: StringProperty(name="Express File")
display_type: EnumProperty( display_type: EnumProperty(
items=[ items=[(display_type, display_type.capitalize(), "") for display_type in get_args(DisplayType)],
("BOUNDS", "Bounds", ""),
("WIRE", "Wire", ""),
("SOLID", "Solid", ""),
("TEXTURED", "Textured", ""),
],
name="Display Type", name="Display Type",
default="BOUNDS", default="BOUNDS",
) )
ifc_class_purge: StringProperty(name="Unused Elements IFC Class", default="") ifc_class_purge: StringProperty(name="Unused Elements IFC Class", default="")
package_name: StringProperty(name="Package Name", default="") package_name: StringProperty(name="Package Name", default="")
if TYPE_CHECKING:
step_id: int
number_of_polygons: int
percentile_of_polygons: int
active_step_id: int
step_id_breadcrumb: bpy.types.bpy_prop_collection_idprop[StrProperty]
attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
inverse_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
inverse_references: bpy.types.bpy_prop_collection_idprop[Attribute]
express_file: str
display_type: str
ifc_class_purge: str
package_name: str
+9 -10
View File
@@ -17,6 +17,7 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>. # along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bpy import bpy
import bonsai.tool as tool
from bpy.types import Panel from bpy.types import Panel
@@ -32,7 +33,7 @@ class BIM_PT_debug(Panel):
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
props = context.scene.BIMDebugProperties props = tool.Debug.get_debug_props()
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.prop(context.scene.BIMProperties, "ifc_file", text="") row.prop(context.scene.BIMProperties, "ifc_file", text="")
@@ -87,27 +88,25 @@ class BIM_PT_debug(Panel):
row.prop(props, "step_id", text="") row.prop(props, "step_id", text="")
row = layout.split(factor=0.7, align=True) row = layout.split(factor=0.7, align=True)
row.operator("bim.select_high_polygon_meshes").threshold = context.scene.BIMDebugProperties.number_of_polygons row.operator("bim.select_high_polygon_meshes").threshold = props.number_of_polygons
row.prop(props, "number_of_polygons", text="") row.prop(props, "number_of_polygons", text="")
row = layout.split(factor=0.7, align=True) row = layout.split(factor=0.7, align=True)
row.operator("bim.select_highest_polygon_meshes").percentile = ( row.operator("bim.select_highest_polygon_meshes").percentile = props.percentile_of_polygons
context.scene.BIMDebugProperties.percentile_of_polygons
)
row.prop(props, "percentile_of_polygons", text="") row.prop(props, "percentile_of_polygons", text="")
row = layout.split(factor=0.5, align=True) row = layout.split(factor=0.5, align=True)
row.prop(props, "display_type", text="") row.prop(props, "display_type", text="")
row.operator("bim.override_display_type").display = context.scene.BIMDebugProperties.display_type row.operator("bim.override_display_type").display = props.display_type
layout.operator("bim.purge_unused_representations") layout.operator("bim.purge_unused_representations")
row = layout.row(align=True) row = layout.row(align=True)
row.prop(context.scene.BIMDebugProperties, "ifc_class_purge", text="") row.prop(props, "ifc_class_purge", text="")
row.operator("bim.purge_unused_elements_by_class", text="Purge Orphaned", icon="TRASH") row.operator("bim.purge_unused_elements_by_class", text="Purge Orphaned", icon="TRASH")
row.operator("bim.print_unused_elements_stats", text="", icon="INFO") row.operator("bim.print_unused_elements_stats", text="", icon="INFO")
if context.active_object and context.active_object.data: if context.active_object and (data := context.active_object.data):
mprops = context.active_object.data.BIMMeshProperties mprops = tool.Geometry.get_mesh_props(data)
row = layout.row() row = layout.row()
row.operator("bim.get_representation_ifc_parameters") row.operator("bim.get_representation_ifc_parameters")
for index, ifc_parameter in enumerate(mprops.ifc_parameters): for index, ifc_parameter in enumerate(mprops.ifc_parameters):
@@ -123,7 +122,7 @@ class BIM_PT_debug(Panel):
row.operator("bim.rewind_inspector", icon="FRAME_PREV", text="") row.operator("bim.rewind_inspector", icon="FRAME_PREV", text="")
row.prop(props, "active_step_id", text="") row.prop(props, "active_step_id", text="")
row = layout.row(align=True) row = layout.row(align=True)
row.operator("bim.inspect_from_step_id").step_id = context.scene.BIMDebugProperties.active_step_id row.operator("bim.inspect_from_step_id").step_id = props.active_step_id
row.operator("bim.inspect_from_object") row.operator("bim.inspect_from_object")
if props.attributes: if props.attributes:
+8 -5
View File
@@ -88,7 +88,8 @@ class SheetsData:
project = tool.Ifc.get().by_type("IfcProject")[0] project = tool.Ifc.get().by_type("IfcProject")[0]
titleblocks_dir = ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "TitleblocksDir") titleblocks_dir = ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "TitleblocksDir")
if not titleblocks_dir: if not titleblocks_dir:
titleblocks_dir = bpy.context.scene.DocProperties.titleblocks_dir props = tool.Drawing.get_document_props()
titleblocks_dir = props.titleblocks_dir
titleblocks_dir = tool.Ifc.resolve_uri(titleblocks_dir) titleblocks_dir = tool.Ifc.resolve_uri(titleblocks_dir)
if os.path.exists(titleblocks_dir): if os.path.exists(titleblocks_dir):
files.extend([str(f.stem) for f in Path(titleblocks_dir).glob("*.svg")]) files.extend([str(f.stem) for f in Path(titleblocks_dir).glob("*.svg")])
@@ -120,23 +121,25 @@ class DrawingsData:
@classmethod @classmethod
def location_hint(cls): def location_hint(cls):
if bpy.context.scene.DocProperties.target_view in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]: props = tool.Drawing.get_document_props()
if props.target_view in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]:
results = [("0", "Origin", "")] results = [("0", "Origin", "")]
results.extend( results.extend(
[(str(s.id()), s.Name or "Unnamed", "") for s in tool.Ifc.get().by_type("IfcBuildingStorey")] [(str(s.id()), s.Name or "Unnamed", "") for s in tool.Ifc.get().by_type("IfcBuildingStorey")]
) )
return results return results
elif bpy.context.scene.DocProperties.target_view in ["MODEL_VIEW"]: elif props.target_view in ["MODEL_VIEW"]:
return [(h.upper(), h, "") for h in ["Orthographic", "Perspective"]] return [(h.upper(), h, "") for h in ["Orthographic", "Perspective"]]
return [(h.upper(), h, "") for h in ["North", "South", "East", "West"]] return [(h.upper(), h, "") for h in ["North", "South", "East", "West"]]
@classmethod @classmethod
def active_drawing_pset_data(cls): def active_drawing_pset_data(cls):
ifc_file = tool.Ifc.get() ifc_file = tool.Ifc.get()
drawing_id = bpy.context.scene.DocProperties.active_drawing_id props = tool.Drawing.get_document_props()
drawing_id = props.active_drawing_id
if drawing_id == 0: if drawing_id == 0:
return {} return {}
drawing = ifc_file.by_id(bpy.context.scene.DocProperties.active_drawing_id) drawing = ifc_file.by_id(drawing_id)
return ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing") return ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing")
@@ -423,7 +423,8 @@ class BaseDecorator:
# font_size = 16 <-- this is a good default # font_size = 16 <-- this is a good default
# TODO: need to synchronize it better with svg # TODO: need to synchronize it better with svg
magic_font_scale = bpy.context.scene.DocProperties.magic_font_scale props = tool.Drawing.get_document_props()
magic_font_scale = props.magic_font_scale
font_size_px = int(magic_font_scale * mm_to_px) * font_size_mm / 2.5 font_size_px = int(magic_font_scale * mm_to_px) * font_size_mm / 2.5
pos = pos - line_no * font_size_px * rotation_matrix[1] pos = pos - line_no * font_size_px * rotation_matrix[1]
@@ -2022,7 +2023,8 @@ class DecorationsHandler:
for object_type in ("SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT"): for object_type in ("SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT"):
self.decorators[object_type] = self.decorators["FALL"] self.decorators[object_type] = self.decorators["FALL"]
self.decorators["MULTI_SYMBOL"] = self.decorators["SYMBOL"] self.decorators["MULTI_SYMBOL"] = self.decorators["SYMBOL"]
if drawing_font := bpy.context.scene.DocProperties.drawing_font: props = tool.Drawing.get_document_props()
if drawing_font := props.drawing_font:
drawing_font_path = tool.Blender.get_data_dir_path(Path("fonts") / drawing_font) drawing_font_path = tool.Blender.get_data_dir_path(Path("fonts") / drawing_font)
if drawing_font_path.is_file(): if drawing_font_path.is_file():
font_id = blf.load(drawing_font_path.__str__()) font_id = blf.load(drawing_font_path.__str__())
+23 -15
View File
@@ -19,6 +19,7 @@
import bpy import bpy
import blf import blf
import gpu import gpu
import bonsai.tool as tool
from bpy import types from bpy import types
from mathutils import Vector from mathutils import Vector
from mathutils import geometry from mathutils import geometry
@@ -478,21 +479,25 @@ class ExtrusionWidget(types.GizmoGroup):
bl_options = {"3D", "PERSISTENT", "SHOW_MODAL_ALL"} bl_options = {"3D", "PERSISTENT", "SHOW_MODAL_ALL"}
@classmethod @classmethod
def poll(cls, ctx): def poll(cls, context):
obj = ctx.object obj = context.active_object
return ( return (
obj obj
and obj.type == "MESH" and (data := obj.data)
and obj.data.BIMMeshProperties.ifc_parameters.get("IfcExtrudedAreaSolid/Depth") is not None and isinstance(data, bpy.types.Mesh)
and tool.Geometry.get_mesh_props(data).ifc_parameters.get("IfcExtrudedAreaSolid/Depth") is not None
) )
def setup(self, ctx): def setup(self, context: bpy.types.Context) -> None:
target = ctx.object target = context.object
prop = target.data.BIMMeshProperties.ifc_parameters.get("IfcExtrudedAreaSolid/Depth") assert target
mesh = target.data
assert isinstance(mesh, bpy.types.Mesh)
prop = tool.Geometry.get_mesh_props(mesh).ifc_parameters.get("IfcExtrudedAreaSolid/Depth")
basis = target.matrix_world.normalized() basis = target.matrix_world.normalized()
theme = ctx.preferences.themes[0].user_interface theme = context.preferences.themes[0].user_interface
scale_value = self.get_scale_value(ctx.scene.unit_settings.system, ctx.scene.unit_settings.length_unit) scale_value = self.get_scale_value(context.scene.unit_settings.system, context.scene.unit_settings.length_unit)
# setup handle # setup handle
gz = self.handle = self.gizmos.new("BIM_GT_uglydot_3d") gz = self.handle = self.gizmos.new("BIM_GT_uglydot_3d")
@@ -521,23 +526,26 @@ class ExtrusionWidget(types.GizmoGroup):
# gz.use_draw_modal = True # gz.use_draw_modal = True
# gz.target_set_prop('value', target.demo, 'depth') # gz.target_set_prop('value', target.demo, 'depth')
def refresh(self, ctx): def refresh(self, context: bpy.types.Context) -> None:
"""updating gizmos""" """updating gizmos"""
target = ctx.object target = context.active_object
basis = target.matrix_world.normalized() basis = target.matrix_world.normalized()
self.handle.matrix_basis = basis self.handle.matrix_basis = basis
self.guides.matrix_basis = basis self.guides.matrix_basis = basis
def update(self, ctx): def update(self, context: bpy.types.Context) -> None:
"""updating object""" """updating object"""
bpy.ops.bim.update_parametric_representation() bpy.ops.bim.update_parametric_representation()
target = ctx.object target = context.active_object
prop = target.data.BIMMeshProperties.ifc_parameters.get("IfcExtrudedAreaSolid/Depth") assert target
mesh = target.data
assert isinstance(mesh, bpy.types.Mesh)
prop = tool.Geometry.get_mesh_props(mesh).ifc_parameters.get("IfcExtrudedAreaSolid/Depth")
self.handle.target_set_prop("offset", prop, "value") self.handle.target_set_prop("offset", prop, "value")
self.guides.target_set_prop("depth", prop, "value") self.guides.target_set_prop("depth", prop, "value")
@staticmethod @staticmethod
def get_scale_value(system, length_unit): def get_scale_value(system: str, length_unit: str) -> float:
scale_value = 1 scale_value = 1
if system == "METRIC": if system == "METRIC":
if length_unit == "KILOMETERS": if length_unit == "KILOMETERS":
@@ -24,7 +24,8 @@ from bpy.app.handlers import persistent
@persistent @persistent
def load_post(*args): def load_post(*args):
if bpy.context.scene.DocProperties.should_draw_decorations: props = tool.Drawing.get_document_props()
if props.should_draw_decorations:
decoration.DecorationsHandler.install(bpy.context) decoration.DecorationsHandler.install(bpy.context)
else: else:
decoration.DecorationsHandler.uninstall() decoration.DecorationsHandler.uninstall()
@@ -35,9 +36,11 @@ def depsgraph_update_pre_handler(scene):
set_active_camera_resolution(scene) set_active_camera_resolution(scene)
def set_active_camera_resolution(scene): def set_active_camera_resolution(scene: bpy.types.Scene) -> None:
if not scene.camera or "/" not in scene.camera.name or not scene.DocProperties.drawings: props = tool.Drawing.get_document_props()
if not scene.camera or "/" not in scene.camera.name or not props.drawings:
return return
assert isinstance(scene.camera.data, bpy.types.Camera)
props = scene.camera.data.BIMCameraProperties props = scene.camera.data.BIMCameraProperties
ortho_scale = max((props.width, props.height)) ortho_scale = max((props.width, props.height))
aspect_ratio = props.width / props.height aspect_ratio = props.width / props.height
@@ -60,5 +63,3 @@ def set_active_camera_resolution(scene):
scene.render.resolution_x = scene.camera.data.BIMCameraProperties.raster_x = int(raster_x) scene.render.resolution_x = scene.camera.data.BIMCameraProperties.raster_x = int(raster_x)
scene.render.resolution_y = scene.camera.data.BIMCameraProperties.raster_y = int(raster_y) scene.render.resolution_y = scene.camera.data.BIMCameraProperties.raster_y = int(raster_y)
current_drawing = scene.DocProperties.drawings[scene.DocProperties.current_drawing_index]
@@ -22,6 +22,7 @@ import mathutils.geometry
import ifcopenshell import ifcopenshell
import bonsai.tool as tool import bonsai.tool as tool
from mathutils import Vector from mathutils import Vector
from typing import Union
# Code taken and updated from https://blenderartists.org/t/detecting-intersection-of-bounding-boxes/457520/2 # Code taken and updated from https://blenderartists.org/t/detecting-intersection-of-bounding-boxes/457520/2
@@ -136,7 +137,9 @@ def format_distance(
scaleFactor = bpy.context.scene.unit_settings.scale_length scaleFactor = bpy.context.scene.unit_settings.scale_length
unit_system = bpy.context.scene.unit_settings.system unit_system = bpy.context.scene.unit_settings.system
unit_length = bpy.context.scene.unit_settings.length_unit unit_length = bpy.context.scene.unit_settings.length_unit
area_unit_symbol = " " + ifcopenshell.util.unit.get_unit_symbol(ifcopenshell.util.unit.get_project_unit(tool.Ifc.get(), "AREAUNIT")) area_unit_symbol = " " + ifcopenshell.util.unit.get_unit_symbol(
ifcopenshell.util.unit.get_project_unit(tool.Ifc.get(), "AREAUNIT")
)
value *= scaleFactor value *= scaleFactor
@@ -326,16 +329,18 @@ def format_distance(
fmt += area_unit_symbol fmt += area_unit_symbol
d_cm = value * (1000000) d_cm = value * (1000000)
tx_dist = fmt % d_cm tx_dist = fmt % d_cm
else: else:
tx_dist = fmt % value tx_dist = fmt % value
return tx_dist return tx_dist
def get_active_drawing(scene): def get_active_drawing(
scene: bpy.types.Scene,
) -> Union[tuple[bpy.types.Collection, bpy.types.Camera], tuple[None, None]]:
"""Get active drawing collection and camera""" """Get active drawing collection and camera"""
props = scene.DocProperties props = tool.Drawing.get_document_props()
try: try:
camera = tool.Ifc.get_object(tool.Ifc.get().by_id(props.active_drawing_id)) camera = tool.Ifc.get_object(tool.Ifc.get().by_id(props.active_drawing_id))
return camera.BIMObjectProperties.collection, camera return camera.BIMObjectProperties.collection, camera
@@ -134,19 +134,19 @@ class AddDrawing(bpy.types.Operator, tool.Ifc.Operator):
bl_description = "Add a drawing view to the IFC project" bl_description = "Add a drawing view to the IFC project"
def _execute(self, context): def _execute(self, context):
self.props = context.scene.DocProperties props = tool.Drawing.get_document_props()
hint = self.props.location_hint hint = props.location_hint
if self.props.target_view in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]: if props.target_view in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]:
hint = int(hint) hint = int(hint)
core.add_drawing( core.add_drawing(
tool.Ifc, tool.Ifc,
tool.Collector, tool.Collector,
tool.Drawing, tool.Drawing,
target_view=self.props.target_view, target_view=props.target_view,
location_hint=hint, location_hint=hint,
) )
try: try:
drawing = tool.Ifc.get().by_id(self.props.active_drawing_id) drawing = tool.Ifc.get().by_id(props.active_drawing_id)
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=drawing) core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=drawing)
except: except:
pass pass
@@ -162,7 +162,7 @@ class DuplicateDrawing(bpy.types.Operator, tool.Ifc.Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
props = context.scene.DocProperties props = tool.Drawing.get_document_props()
if not tool.Drawing.get_active_drawing_item(): if not tool.Drawing.get_active_drawing_item():
cls.poll_message_set("No drawing selected.") cls.poll_message_set("No drawing selected.")
return False return False
@@ -176,7 +176,7 @@ class DuplicateDrawing(bpy.types.Operator, tool.Ifc.Operator):
row.prop(self, "should_duplicate_annotations") row.prop(self, "should_duplicate_annotations")
def _execute(self, context): def _execute(self, context):
self.props = context.scene.DocProperties props = tool.Drawing.get_document_props()
core.duplicate_drawing( core.duplicate_drawing(
tool.Ifc, tool.Ifc,
tool.Drawing, tool.Drawing,
@@ -184,7 +184,7 @@ class DuplicateDrawing(bpy.types.Operator, tool.Ifc.Operator):
should_duplicate_annotations=self.should_duplicate_annotations, should_duplicate_annotations=self.should_duplicate_annotations,
) )
try: try:
drawing = tool.Ifc.get().by_id(self.props.active_drawing_id) drawing = tool.Ifc.get().by_id(props.active_drawing_id)
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=drawing) core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=drawing)
except: except:
pass pass
@@ -244,7 +244,7 @@ class CreateDrawing(bpy.types.Operator):
return self.execute(context) return self.execute(context)
def execute(self, context): def execute(self, context):
self.props = context.scene.DocProperties self.props = tool.Drawing.get_document_props()
active_drawing_id = context.scene.camera.BIMObjectProperties.ifc_definition_id active_drawing_id = context.scene.camera.BIMObjectProperties.ifc_definition_id
if self.print_all: if self.print_all:
@@ -385,7 +385,7 @@ class CreateDrawing(bpy.types.Operator):
obj.hide_render = obj.name not in visible_object_names obj.hide_render = obj.name not in visible_object_names
context.scene.render.filepath = str(Path(svg_path).with_suffix(".png")) context.scene.render.filepath = str(Path(svg_path).with_suffix(".png"))
drawing_style = context.scene.DocProperties.drawing_styles[self.cprops.active_drawing_style_index] drawing_style = self.props.drawing_styles[self.cprops.active_drawing_style_index]
if drawing_style.render_type == "DEFAULT": if drawing_style.render_type == "DEFAULT":
bpy.ops.render.render(write_still=True) bpy.ops.render.render(write_still=True)
@@ -714,7 +714,8 @@ class CreateDrawing(bpy.types.Operator):
files = {context.scene.BIMProperties.ifc_file: tool.Ifc.get()} files = {context.scene.BIMProperties.ifc_file: tool.Ifc.get()}
for link in context.scene.BIMProjectProperties.links: props = tool.Project.get_project_props()
for link in props.links:
if link.name not in IfcStore.session_files: if link.name not in IfcStore.session_files:
IfcStore.session_files[link.name] = ifcopenshell.open(link.name) IfcStore.session_files[link.name] = ifcopenshell.open(link.name)
files[link.name] = IfcStore.session_files[link.name] files[link.name] = IfcStore.session_files[link.name]
@@ -1141,7 +1142,8 @@ class CreateDrawing(bpy.types.Operator):
try: try:
return tool.Ifc.get().by_guid(guid) return tool.Ifc.get().by_guid(guid)
except: except:
for link in bpy.context.scene.BIMProjectProperties.links: props = tool.Project.get_project_props()
for link in props.links:
if link.name not in IfcStore.session_files: if link.name not in IfcStore.session_files:
IfcStore.session_files[link.name] = ifcopenshell.open(link.name) IfcStore.session_files[link.name] = ifcopenshell.open(link.name)
try: try:
@@ -1458,7 +1460,8 @@ class AddSheet(bpy.types.Operator, tool.Ifc.Operator):
bl_description = "Add a sheet to the project" bl_description = "Add a sheet to the project"
def _execute(self, context): def _execute(self, context):
core.add_sheet(tool.Ifc, tool.Drawing, titleblock=context.scene.DocProperties.titleblock) props = tool.Drawing.get_document_props()
core.add_sheet(tool.Ifc, tool.Drawing, titleblock=props.titleblock)
class DuplicateSheet(bpy.types.Operator, tool.Ifc.Operator): class DuplicateSheet(bpy.types.Operator, tool.Ifc.Operator):
@@ -1474,7 +1477,7 @@ class DuplicateSheet(bpy.types.Operator, tool.Ifc.Operator):
cls.poll_message_set("Not implemented yet.") cls.poll_message_set("Not implemented yet.")
return False return False
props = context.scene.DocProperties props = tool.Drawing.get_document_props()
if not tool.Drawing.get_active_drawing_item(): if not tool.Drawing.get_active_drawing_item():
cls.poll_message_set("No drawing selected.") cls.poll_message_set("No drawing selected.")
return False return False
@@ -1483,7 +1486,7 @@ class DuplicateSheet(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context): def _execute(self, context):
pass pass
""" """
self.props = context.scene.DocProperties self.props = tool.Drawing.get_document_props()
core.duplicate_sheet( core.duplicate_sheet(
tool.Ifc, tool.Ifc,
tool.Drawing, tool.Drawing,
@@ -1508,7 +1511,7 @@ class OpenLayout(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def _execute(self, context): def _execute(self, context):
self.props = context.scene.DocProperties self.props = tool.Drawing.get_document_props()
sheet = tool.Ifc.get().by_id(self.props.sheets[self.props.active_sheet_index].ifc_definition_id) sheet = tool.Ifc.get().by_id(self.props.sheets[self.props.active_sheet_index].ifc_definition_id)
sheet_builder = sheeter.SheetBuilder() sheet_builder = sheeter.SheetBuilder()
sheet_builder.update_sheet_drawing_sizes(sheet) sheet_builder.update_sheet_drawing_sizes(sheet)
@@ -1530,7 +1533,8 @@ class SelectAllSheets(bpy.types.Operator):
return self.execute(context) return self.execute(context)
def execute(self, context): def execute(self, context):
for sheet in context.scene.DocProperties.sheets: props = tool.Drawing.get_document_props()
for sheet in props.sheets:
if sheet.is_selected != self.select_all: if sheet.is_selected != self.select_all:
sheet.is_selected = self.select_all sheet.is_selected = self.select_all
return {"FINISHED"} return {"FINISHED"}
@@ -1550,7 +1554,7 @@ class OpenSheet(bpy.types.Operator, tool.Ifc.Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
props = context.scene.DocProperties props = tool.Drawing.get_document_props()
if not tool.Drawing.get_active_sheet_item(is_sheet=True): if not tool.Drawing.get_active_sheet_item(is_sheet=True):
cls.poll_message_set("No sheet selected.") cls.poll_message_set("No sheet selected.")
return False return False
@@ -1564,7 +1568,7 @@ class OpenSheet(bpy.types.Operator, tool.Ifc.Operator):
return self.execute(context) return self.execute(context)
def execute(self, context): def execute(self, context):
self.props = context.scene.DocProperties self.props = tool.Drawing.get_document_props()
svg2pdf_command = tool.Blender.get_addon_preferences().svg2pdf_command svg2pdf_command = tool.Blender.get_addon_preferences().svg2pdf_command
if self.open_all: if self.open_all:
@@ -1612,7 +1616,7 @@ class AddDrawingToSheet(bpy.types.Operator, tool.Ifc.Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
props = context.scene.DocProperties props = tool.Drawing.get_document_props()
# Won't be visible in UI anyway. # Won't be visible in UI anyway.
if not props.sheets or not context.scene.BIMProperties.data_dir: if not props.sheets or not context.scene.BIMProperties.data_dir:
return False return False
@@ -1622,8 +1626,8 @@ class AddDrawingToSheet(bpy.types.Operator, tool.Ifc.Operator):
return True return True
def _execute(self, context): def _execute(self, context):
props = context.scene.DocProperties props = tool.Drawing.get_document_props()
active_drawing = tool.Drawing.get_active_drawing_item() active_drawing = props.drawings[props.active_drawing_index]
assert active_drawing assert active_drawing
active_sheet = tool.Drawing.get_active_sheet(context) active_sheet = tool.Drawing.get_active_sheet(context)
@@ -1680,7 +1684,7 @@ class RemoveDrawingFromSheet(bpy.types.Operator, tool.Ifc.Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
props = context.scene.DocProperties props = tool.Drawing.get_document_props()
active_item = tool.Drawing.get_active_sheet_item() active_item = tool.Drawing.get_active_sheet_item()
if active_item is None: if active_item is None:
return False return False
@@ -1714,7 +1718,7 @@ class CreateSheets(bpy.types.Operator, tool.Ifc.Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
props = context.scene.DocProperties props = tool.Drawing.get_document_props()
if not tool.Drawing.get_active_sheet_item(is_sheet=True): if not tool.Drawing.get_active_sheet_item(is_sheet=True):
cls.poll_message_set("No sheet selected.") cls.poll_message_set("No sheet selected.")
return False return False
@@ -1731,7 +1735,7 @@ class CreateSheets(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context): def _execute(self, context):
scene = context.scene scene = context.scene
props = scene.DocProperties props = tool.Drawing.get_document_props()
svg2pdf_command = tool.Blender.get_addon_preferences().svg2pdf_command svg2pdf_command = tool.Blender.get_addon_preferences().svg2pdf_command
svg2dxf_command = tool.Blender.get_addon_preferences().svg2dxf_command svg2dxf_command = tool.Blender.get_addon_preferences().svg2dxf_command
@@ -1838,7 +1842,8 @@ class SelectAllDrawings(bpy.types.Operator):
return self.execute(context) return self.execute(context)
def execute(self, context): def execute(self, context):
for drawing in context.scene.DocProperties.drawings: props = tool.Drawing.get_document_props()
for drawing in props.drawings:
if drawing.is_selected != self.select_all: if drawing.is_selected != self.select_all:
drawing.is_selected = self.select_all drawing.is_selected = self.select_all
return {"FINISHED"} return {"FINISHED"}
@@ -1857,7 +1862,7 @@ class OpenDrawing(bpy.types.Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
props = context.scene.DocProperties props = tool.Drawing.get_document_props()
if not tool.Drawing.get_active_drawing_item(): if not tool.Drawing.get_active_drawing_item():
cls.poll_message_set("No drawing selected.") cls.poll_message_set("No drawing selected.")
return False return False
@@ -1871,7 +1876,7 @@ class OpenDrawing(bpy.types.Operator):
return self.execute(context) return self.execute(context)
def execute(self, context): def execute(self, context):
self.props = context.scene.DocProperties self.props = tool.Drawing.get_document_props()
if self.open_all: if self.open_all:
drawings = [ drawings = [
tool.Ifc.get().by_id(d.ifc_definition_id) for d in self.props.drawings if d.is_drawing and d.is_selected tool.Ifc.get().by_id(d.ifc_definition_id) for d in self.props.drawings if d.is_drawing and d.is_selected
@@ -1907,7 +1912,7 @@ class ActivateModel(bpy.types.Operator):
bl_description = "Activate the model view, hide all annotations" bl_description = "Activate the model view, hide all annotations"
def execute(self, context): def execute(self, context):
dprops = bpy.context.scene.DocProperties dprops = tool.Drawing.get_document_props()
dprops.active_drawing_id = 0 dprops.active_drawing_id = 0
CutDecorator.uninstall() CutDecorator.uninstall()
@@ -1971,11 +1976,12 @@ class ActivateDrawingBase:
return self.execute(context) return self.execute(context)
def execute(self, context): def execute(self, context):
if bpy.context.scene.DocProperties.is_editing_drawings == False: props = tool.Drawing.get_document_props()
if props.is_editing_drawings == False:
bpy.ops.bim.load_drawings() bpy.ops.bim.load_drawings()
drawing = tool.Ifc.get().by_id(self.drawing) drawing = tool.Ifc.get().by_id(self.drawing)
dprops = bpy.context.scene.DocProperties dprops = tool.Drawing.get_document_props()
if self.use_quick_preview: if self.use_quick_preview:
tool.Blender.activate_camera(tool.Drawing.import_temporary_drawing_camera(drawing)) tool.Blender.activate_camera(tool.Drawing.import_temporary_drawing_camera(drawing))
@@ -2027,7 +2033,7 @@ class ActivateDrawing(bpy.types.Operator, ActivateDrawingBase):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
props = context.scene.DocProperties props = tool.Drawing.get_document_props()
if not tool.Drawing.get_active_drawing_item(): if not tool.Drawing.get_active_drawing_item():
cls.poll_message_set("No drawing selected.") cls.poll_message_set("No drawing selected.")
return False return False
@@ -2050,7 +2056,7 @@ class ActivateDrawingFromSheet(bpy.types.Operator, ActivateDrawingBase):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
props = context.scene.DocProperties props = tool.Drawing.get_document_props()
if not tool.Drawing.get_active_sheet_item(reference_type="DRAWING"): if not tool.Drawing.get_active_sheet_item(reference_type="DRAWING"):
cls.poll_message_set("No drawing selected.") cls.poll_message_set("No drawing selected.")
return False return False
@@ -2066,7 +2072,8 @@ class SelectDocIfcFile(bpy.types.Operator):
index: bpy.props.IntProperty() index: bpy.props.IntProperty()
def execute(self, context): def execute(self, context):
context.scene.DocProperties.ifc_files[self.index].name = self.filepath props = tool.Drawing.get_document_props()
props.ifc_files[self.index].name = self.filepath
return {"FINISHED"} return {"FINISHED"}
def invoke(self, context, event): def invoke(self, context, event):
@@ -2098,7 +2105,7 @@ class RemoveDrawing(bpy.types.Operator, tool.Ifc.Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
props = context.scene.DocProperties props = tool.Drawing.get_document_props()
if not tool.Drawing.get_active_drawing_item(): if not tool.Drawing.get_active_drawing_item():
cls.poll_message_set("No drawing selected.") cls.poll_message_set("No drawing selected.")
return False return False
@@ -2112,12 +2119,10 @@ class RemoveDrawing(bpy.types.Operator, tool.Ifc.Operator):
return self.execute(context) return self.execute(context)
def _execute(self, context): def _execute(self, context):
props = context.scene.DocProperties props = tool.Drawing.get_document_props()
if self.remove_all: if self.remove_all:
drawings = [ drawings = [
tool.Ifc.get().by_id(d.ifc_definition_id) tool.Ifc.get().by_id(d.ifc_definition_id) for d in props.drawings if d.is_drawing and d.is_selected
for d in context.scene.DocProperties.drawings
if d.is_drawing and d.is_selected
] ]
else: else:
if not self.drawing: if not self.drawing:
@@ -2187,7 +2192,8 @@ class ReloadDrawingStyles(bpy.types.Operator):
with open(json_path, "r") as fi: with open(json_path, "r") as fi:
shading_styles_json = json.load(fi) shading_styles_json = json.load(fi)
drawing_styles = context.scene.DocProperties.drawing_styles props = tool.Drawing.get_document_props()
drawing_styles = props.drawing_styles
drawing_styles.clear() drawing_styles.clear()
styles = [style for style in shading_styles_json] styles = [style for style in shading_styles_json]
for style_name in styles: for style_name in styles:
@@ -2212,7 +2218,8 @@ class AddDrawingStyle(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def execute(self, context): def execute(self, context):
drawing_styles = context.scene.DocProperties.drawing_styles props = tool.Drawing.get_document_props()
drawing_styles = props.drawing_styles
new = drawing_styles.add() new = drawing_styles.add()
# drawing style is saved to ifc on rename # drawing style is saved to ifc on rename
new.name = tool.Blender.ensure_unique_name("New Drawing Style", drawing_styles) new.name = tool.Blender.ensure_unique_name("New Drawing Style", drawing_styles)
@@ -2227,7 +2234,8 @@ class RemoveDrawingStyle(bpy.types.Operator, tool.Ifc.Operator):
index: bpy.props.IntProperty() index: bpy.props.IntProperty()
def execute(self, context): def execute(self, context):
context.scene.DocProperties.drawing_styles.remove(self.index) props = tool.Drawing.get_document_props()
props.drawing_styles.remove(self.index)
context.scene.camera.data.BIMCameraProperties.active_drawing_style_index = max(self.index - 1, 0) context.scene.camera.data.BIMCameraProperties.active_drawing_style_index = max(self.index - 1, 0)
bpy.ops.bim.save_drawing_styles_data() bpy.ops.bim.save_drawing_styles_data()
return {"FINISHED"} return {"FINISHED"}
@@ -2283,7 +2291,8 @@ class SaveDrawingStyle(bpy.types.Operator, tool.Ifc.Operator):
index = int(self.index) index = int(self.index)
else: else:
index = context.scene.camera.data.BIMCameraProperties.active_drawing_style_index index = context.scene.camera.data.BIMCameraProperties.active_drawing_style_index
scene.DocProperties.drawing_styles[index].raster_style = json.dumps(style) props = tool.Drawing.get_document_props()
props.drawing_styles[index].raster_style = json.dumps(style)
bpy.ops.bim.save_drawing_styles_data() bpy.ops.bim.save_drawing_styles_data()
return {"FINISHED"} return {"FINISHED"}
@@ -2309,7 +2318,8 @@ class SaveDrawingStylesData(bpy.types.Operator, tool.Ifc.Operator):
if not DrawingsData.is_loaded: if not DrawingsData.is_loaded:
DrawingsData.load() DrawingsData.load()
drawing_pset_data = DrawingsData.data["active_drawing_pset_data"] drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
drawing_styles = context.scene.DocProperties.drawing_styles props = tool.Drawing.get_document_props()
drawing_styles = props.drawing_styles
rel_path = drawing_pset_data["ShadingStyles"] rel_path = drawing_pset_data["ShadingStyles"]
current_style = drawing_pset_data.get("CurrentShadingStyle", None) current_style = drawing_pset_data.get("CurrentShadingStyle", None)
@@ -2338,7 +2348,7 @@ class SaveDrawingStylesData(bpy.types.Operator, tool.Ifc.Operator):
new_style_name = None new_style_name = None
ifc_file = tool.Ifc.get() ifc_file = tool.Ifc.get()
drawing = ifc_file.by_id(context.scene.DocProperties.active_drawing_id) drawing = ifc_file.by_id(props.active_drawing_id)
pset = tool.Pset.get_element_pset(drawing, "EPset_Drawing") pset = tool.Pset.get_element_pset(drawing, "EPset_Drawing")
ifcopenshell.api.run( ifcopenshell.api.run(
"pset.edit_pset", ifc_file, pset=pset, properties={"CurrentShadingStyle": new_style_name} "pset.edit_pset", ifc_file, pset=pset, properties={"CurrentShadingStyle": new_style_name}
@@ -2357,17 +2367,18 @@ class ActivateDrawingStyle(bpy.types.Operator, tool.Ifc.Operator):
scene = context.scene scene = context.scene
ifc_file = tool.Ifc.get() ifc_file = tool.Ifc.get()
active_drawing_style_index = scene.camera.data.BIMCameraProperties.active_drawing_style_index active_drawing_style_index = scene.camera.data.BIMCameraProperties.active_drawing_style_index
props = tool.Drawing.get_document_props()
if active_drawing_style_index >= len(scene.DocProperties.drawing_styles): if active_drawing_style_index >= len(props.drawing_styles):
self.report({"ERROR"}, "Could not find active drawing style") self.report({"ERROR"}, "Could not find active drawing style")
return {"CANCELLED"} return {"CANCELLED"}
self.drawing_style = scene.DocProperties.drawing_styles[active_drawing_style_index] self.drawing_style = props.drawing_styles[active_drawing_style_index]
self.set_raster_style(context) self.set_raster_style(context)
self.set_query(context) self.set_query(context)
drawing = ifc_file.by_id(scene.DocProperties.active_drawing_id) drawing = ifc_file.by_id(props.active_drawing_id)
pset = tool.Pset.get_element_pset(drawing, "EPset_Drawing") pset = tool.Pset.get_element_pset(drawing, "EPset_Drawing")
ifcopenshell.api.run( ifcopenshell.api.run(
"pset.edit_pset", ifc_file, pset=pset, properties={"CurrentShadingStyle": self.drawing_style.name} "pset.edit_pset", ifc_file, pset=pset, properties={"CurrentShadingStyle": self.drawing_style.name}
@@ -2392,7 +2403,8 @@ class ActivateDrawingStyle(bpy.types.Operator, tool.Ifc.Operator):
def set_query(self, context: bpy.types.Context) -> None: def set_query(self, context: bpy.types.Context) -> None:
self.include_global_ids = [] self.include_global_ids = []
self.exclude_global_ids = [] self.exclude_global_ids = []
for ifc_file in context.scene.DocProperties.ifc_files: props = tool.Drawing.get_document_props()
for ifc_file in props.ifc_files:
try: try:
ifc = ifcopenshell.open(ifc_file.name) ifc = ifcopenshell.open(ifc_file.name)
except: except:
@@ -2507,14 +2519,14 @@ class AddScheduleToSheet(bpy.types.Operator, tool.Ifc.Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
props = context.scene.DocProperties props = tool.Drawing.get_document_props()
if not props.schedules: if not props.schedules:
cls.poll_message_set("No schedule selected.") cls.poll_message_set("No schedule selected.")
return False return False
return props.schedules and props.sheets and context.scene.BIMProperties.data_dir return props.schedules and props.sheets and context.scene.BIMProperties.data_dir
def _execute(self, context): def _execute(self, context):
props = context.scene.DocProperties props = tool.Drawing.get_document_props()
active_schedule = props.schedules[props.active_schedule_index] active_schedule = props.schedules[props.active_schedule_index]
active_sheet = tool.Drawing.get_active_sheet(context) active_sheet = tool.Drawing.get_active_sheet(context)
schedule = tool.Ifc.get().by_id(active_schedule.ifc_definition_id) schedule = tool.Ifc.get().by_id(active_schedule.ifc_definition_id)
@@ -2573,14 +2585,14 @@ class AddReferenceToSheet(bpy.types.Operator, tool.Ifc.Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
props = context.scene.DocProperties props = tool.Drawing.get_document_props()
if not props.references: if not props.references:
cls.poll_message_set("No reference selected.") cls.poll_message_set("No reference selected.")
return False return False
return props.references and props.sheets and context.scene.BIMProperties.data_dir return props.references and props.sheets and context.scene.BIMProperties.data_dir
def _execute(self, context): def _execute(self, context):
props = context.scene.DocProperties props = tool.Drawing.get_document_props()
active_reference = props.references[props.active_reference_index] active_reference = props.references[props.active_reference_index]
active_sheet = tool.Drawing.get_active_sheet(context) active_sheet = tool.Drawing.get_active_sheet(context)
extref = tool.Ifc.get().by_id(active_reference.ifc_definition_id) extref = tool.Ifc.get().by_id(active_reference.ifc_definition_id)
@@ -2682,7 +2694,8 @@ class AddDrawingStyleAttribute(bpy.types.Operator):
def execute(self, context): def execute(self, context):
props = context.scene.camera.data.BIMCameraProperties props = context.scene.camera.data.BIMCameraProperties
context.scene.DocProperties.drawing_styles[props.active_drawing_style_index].attributes.add() dprops = tool.Drawing.get_document_props()
dprops.drawing_styles[props.active_drawing_style_index].attributes.add()
return {"FINISHED"} return {"FINISHED"}
@@ -2695,7 +2708,8 @@ class RemoveDrawingStyleAttribute(bpy.types.Operator):
def execute(self, context): def execute(self, context):
props = context.scene.camera.data.BIMCameraProperties props = context.scene.camera.data.BIMCameraProperties
context.scene.DocProperties.drawing_styles[props.active_drawing_style_index].attributes.remove(self.index) dprops = tool.Drawing.get_document_props()
dprops.drawing_styles[props.active_drawing_style_index].attributes.remove(self.index)
return {"FINISHED"} return {"FINISHED"}
@@ -2979,7 +2993,7 @@ class LoadSheets(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context): def _execute(self, context):
core.load_sheets(tool.Drawing) core.load_sheets(tool.Drawing)
props = context.scene.DocProperties props = tool.Drawing.get_document_props()
sheets_not_found = [] sheets_not_found = []
for sheet_prop in props.sheets: for sheet_prop in props.sheets:
if not sheet_prop.is_sheet: if not sheet_prop.is_sheet:
@@ -3009,7 +3023,7 @@ class EditSheet(bpy.types.Operator, tool.Ifc.Operator):
document_type: Literal["SHEET", "TITLEBLOCK", "EMBEDDED"] document_type: Literal["SHEET", "TITLEBLOCK", "EMBEDDED"]
def invoke(self, context, event): def invoke(self, context, event):
self.props = context.scene.DocProperties self.props = tool.Drawing.get_document_props()
sheet = tool.Ifc.get().by_id(self.props.sheets[self.props.active_sheet_index].ifc_definition_id) sheet = tool.Ifc.get().by_id(self.props.sheets[self.props.active_sheet_index].ifc_definition_id)
if sheet.is_a("IfcDocumentInformation"): if sheet.is_a("IfcDocumentInformation"):
self.document_type = "SHEET" self.document_type = "SHEET"
@@ -3023,6 +3037,7 @@ class EditSheet(bpy.types.Operator, tool.Ifc.Operator):
return context.window_manager.invoke_props_dialog(self) return context.window_manager.invoke_props_dialog(self)
def draw(self, context): def draw(self, context):
props = tool.Drawing.get_document_props()
if self.document_type == "SHEET": if self.document_type == "SHEET":
row = self.layout.row() row = self.layout.row()
row.prop(self, "identification", text="Identification") row.prop(self, "identification", text="Identification")
@@ -3030,13 +3045,13 @@ class EditSheet(bpy.types.Operator, tool.Ifc.Operator):
row.prop(self, "name", text="Name") row.prop(self, "name", text="Name")
elif self.document_type == "TITLEBLOCK": elif self.document_type == "TITLEBLOCK":
row = self.layout.row() row = self.layout.row()
row.prop(context.scene.DocProperties, "titleblock", text="Titleblock") row.prop(props, "titleblock", text="Titleblock")
elif self.document_type == "EMBEDDED": elif self.document_type == "EMBEDDED":
row = self.layout.row() row = self.layout.row()
row.prop(self, "identification", text="Identification") row.prop(self, "identification", text="Identification")
def _execute(self, context): def _execute(self, context):
self.props = context.scene.DocProperties self.props = tool.Drawing.get_document_props()
sheet = tool.Ifc.get().by_id(self.props.sheets[self.props.active_sheet_index].ifc_definition_id) sheet = tool.Ifc.get().by_id(self.props.sheets[self.props.active_sheet_index].ifc_definition_id)
if self.document_type == "SHEET": if self.document_type == "SHEET":
core.rename_sheet(tool.Ifc, tool.Drawing, sheet=sheet, identification=self.identification, name=self.name) core.rename_sheet(tool.Ifc, tool.Drawing, sheet=sheet, identification=self.identification, name=self.name)
@@ -3134,7 +3149,7 @@ class ExpandTargetView(bpy.types.Operator):
target_view: bpy.props.StringProperty() target_view: bpy.props.StringProperty()
def execute(self, context): def execute(self, context):
props = context.scene.DocProperties props = tool.Drawing.get_document_props()
for drawing in [d for d in props.drawings if d.target_view == self.target_view]: for drawing in [d for d in props.drawings if d.target_view == self.target_view]:
drawing.is_expanded = True drawing.is_expanded = True
core.load_drawings(tool.Drawing) core.load_drawings(tool.Drawing)
@@ -3150,7 +3165,7 @@ class ContractTargetView(bpy.types.Operator):
target_view: bpy.props.StringProperty() target_view: bpy.props.StringProperty()
def execute(self, context): def execute(self, context):
props = context.scene.DocProperties props = tool.Drawing.get_document_props()
for drawing in [d for d in props.drawings if d.target_view == self.target_view]: for drawing in [d for d in props.drawings if d.target_view == self.target_view]:
drawing.is_expanded = False drawing.is_expanded = False
core.load_drawings(tool.Drawing) core.load_drawings(tool.Drawing)
@@ -3166,7 +3181,7 @@ class ExpandSheet(bpy.types.Operator):
sheet: bpy.props.IntProperty() sheet: bpy.props.IntProperty()
def execute(self, context): def execute(self, context):
props = context.scene.DocProperties props = tool.Drawing.get_document_props()
for sheet in [s for s in props.sheets if s.ifc_definition_id == self.sheet]: for sheet in [s for s in props.sheets if s.ifc_definition_id == self.sheet]:
sheet.is_expanded = True sheet.is_expanded = True
core.load_sheets(tool.Drawing) core.load_sheets(tool.Drawing)
@@ -3182,7 +3197,7 @@ class ContractSheet(bpy.types.Operator):
sheet: bpy.props.IntProperty() sheet: bpy.props.IntProperty()
def execute(self, context): def execute(self, context):
props = context.scene.DocProperties props = tool.Drawing.get_document_props()
for sheet in [s for s in props.sheets if s.ifc_definition_id == self.sheet]: for sheet in [s for s in props.sheets if s.ifc_definition_id == self.sheet]:
sheet.is_expanded = False sheet.is_expanded = False
core.load_sheets(tool.Drawing) core.load_sheets(tool.Drawing)
@@ -3373,7 +3388,7 @@ class ConvertSVGToDXF(bpy.types.Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
props = context.scene.DocProperties props = tool.Drawing.get_document_props()
if not tool.Drawing.get_active_drawing_item(): if not tool.Drawing.get_active_drawing_item():
cls.poll_message_set("No drawing selected.") cls.poll_message_set("No drawing selected.")
return False return False
@@ -3387,14 +3402,13 @@ class ConvertSVGToDXF(bpy.types.Operator):
return self.execute(context) return self.execute(context)
def execute(self, context): def execute(self, context):
props = tool.Drawing.get_document_props()
if self.convert_all: if self.convert_all:
drawings = [ drawings = [
tool.Ifc.get().by_id(d.ifc_definition_id) tool.Ifc.get().by_id(d.ifc_definition_id) for d in props.drawings if d.is_drawing and d.is_selected
for d in context.scene.DocProperties.drawings
if d.is_drawing and d.is_selected
] ]
else: else:
drawings = [tool.Ifc.get().by_id(context.scene.DocProperties.drawings.get(self.view).ifc_definition_id)] drawings = [tool.Ifc.get().by_id(props.drawings.get(self.view).ifc_definition_id)]
drawing_uris: list[Path] = [] drawing_uris: list[Path] = []
drawings_not_found: list[str] = [] drawings_not_found: list[str] = []
+5 -5
View File
@@ -76,7 +76,7 @@ def update_diagram_scale(self, context):
try: try:
element = ( element = (
tool.Ifc.get() tool.Ifc.get()
.by_id(self.id_data.BIMMeshProperties.ifc_definition_id) .by_id(tool.Geometry.get_mesh_props(self.id_data).ifc_definition_id)
.OfProductRepresentation[0] .OfProductRepresentation[0]
.ShapeOfProduct[0] .ShapeOfProduct[0]
) )
@@ -93,7 +93,7 @@ def update_diagram_scale(self, context):
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties=diagram_scale) ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties=diagram_scale)
def update_is_nts(self, context): def update_is_nts(self: "BIMCameraProperties", context: bpy.types.Context) -> None:
if not self.update_props: if not self.update_props:
return return
if not context.scene.camera or context.scene.camera.data != self.id_data: if not context.scene.camera or context.scene.camera.data != self.id_data:
@@ -104,7 +104,7 @@ def update_is_nts(self, context):
try: try:
element = ( element = (
tool.Ifc.get() tool.Ifc.get()
.by_id(self.id_data.BIMMeshProperties.ifc_definition_id) .by_id(tool.Geometry.get_mesh_props(self.id_data).ifc_definition_id)
.OfProductRepresentation[0] .OfProductRepresentation[0]
.ShapeOfProduct[0] .ShapeOfProduct[0]
) )
@@ -192,8 +192,8 @@ def get_drawing_style_name(self: "DrawingStyle"):
def set_drawing_style_name(self: "DrawingStyle", new_value: str) -> None: def set_drawing_style_name(self: "DrawingStyle", new_value: str) -> None:
"""ensure the name is unique""" """ensure the name is unique"""
scene = bpy.context.scene props = tool.Drawing.get_document_props()
drawing_styles = [s.name for s in scene.DocProperties.drawing_styles if s.name != self.name] drawing_styles = [s.name for s in props.drawing_styles if s.name != self.name]
new_value = tool.Blender.ensure_unique_name(new_value, drawing_styles) new_value = tool.Blender.ensure_unique_name(new_value, drawing_styles)
old_value = self.name old_value = self.name
self["name"] = new_value self["name"] = new_value
@@ -56,7 +56,7 @@ def a1_to_rc(cell):
class Scheduler: class Scheduler:
def schedule(self, infile, outfile): def schedule(self, infile: str, outfile: str) -> None:
self.svg = svgwrite.Drawing( self.svg = svgwrite.Drawing(
outfile, outfile,
debug=False, debug=False,
@@ -71,11 +71,12 @@ class Scheduler:
elif infile.endswith("xlsx"): elif infile.endswith("xlsx"):
self.schedule_xlsx(infile, outfile) self.schedule_xlsx(infile, outfile)
def parse_css(self, infile): def parse_css(self, infile: str) -> None:
props = tool.Drawing.get_document_props()
stylesheet_path = os.path.splitext(infile)[0] + ".css" stylesheet_path = os.path.splitext(infile)[0] + ".css"
if not os.path.exists(stylesheet_path): if not os.path.exists(stylesheet_path):
stylesheet_rel_path = getattr(bpy.context.scene.DocProperties, "schedules_stylesheet_path") stylesheet_rel_path = props.schedules_stylesheet_path
ifc_file_path = os.path.dirname(IfcStore.path) ifc_file_path = os.path.dirname(tool.Ifc.get_path())
stylesheet_path = ifc_file_path + "\\" + stylesheet_rel_path stylesheet_path = ifc_file_path + "\\" + stylesheet_rel_path
if not os.path.exists(stylesheet_path): if not os.path.exists(stylesheet_path):
stylesheet_path = tool.Blender.get_data_dir_path(Path("assets") / "schedule.css") stylesheet_path = tool.Blender.get_data_dir_path(Path("assets") / "schedule.css")
@@ -91,7 +92,7 @@ class Scheduler:
self.svg.defs.add(self.svg.style(css)) self.svg.defs.add(self.svg.style(css))
def schedule_xlsx(self, infile, outfile): def schedule_xlsx(self, infile: str, outfile: str) -> None:
workbook = openpyxl.open(infile, data_only=True) workbook = openpyxl.open(infile, data_only=True)
sheet = workbook.active sheet = workbook.active
@@ -236,7 +237,7 @@ class Scheduler:
self.svg["viewBox"] = "0 0 {} {}".format(total_width, total_height) self.svg["viewBox"] = "0 0 {} {}".format(total_width, total_height)
self.svg.save(pretty=True) self.svg.save(pretty=True)
def schedule_ods(self, infile, outfile): def schedule_ods(self, infile: str, outfile: str) -> None:
doc = load_ods(infile) doc = load_ods(infile)
# useful for debugging ods # useful for debugging ods
@@ -495,11 +496,11 @@ class Scheduler:
self.svg["viewBox"] = "0 0 {} {}".format(total_width, total_height) self.svg["viewBox"] = "0 0 {} {}".format(total_width, total_height)
self.svg.save(pretty=True) self.svg.save(pretty=True)
def get_style(self, style_name, styles): def get_style(self, style_name: str, styles: dict) -> dict:
style = styles[style_name] if style_name else {} style = styles[style_name] if style_name else {}
return style return style
def get_box_alignment(self, style): def get_box_alignment(self, style: dict) -> str:
if style and "vertical-align" in style and style["vertical-align"] != "automatic": if style and "vertical-align" in style and style["vertical-align"] != "automatic":
vertical_align = style["vertical-align"] vertical_align = style["vertical-align"]
else: else:
+7 -7
View File
@@ -49,7 +49,7 @@ class BIM_PT_camera(Panel):
return return
self.layout.use_property_split = True self.layout.use_property_split = True
dprops = context.scene.DocProperties dprops = tool.Drawing.get_document_props()
props = context.scene.camera.data.BIMCameraProperties props = context.scene.camera.data.BIMCameraProperties
col = self.layout.column(align=True) col = self.layout.column(align=True)
@@ -161,7 +161,7 @@ class BIM_PT_drawing_underlay(Panel):
layout.use_property_split = True layout.use_property_split = True
camera = context.scene.camera camera = context.scene.camera
assert camera assert camera
dprops = context.scene.DocProperties dprops = tool.Drawing.get_document_props()
props = camera.data.BIMCameraProperties props = camera.data.BIMCameraProperties
drawing_index_is_valid = props.active_drawing_style_index < len(dprops.drawing_styles) drawing_index_is_valid = props.active_drawing_style_index < len(dprops.drawing_styles)
@@ -229,7 +229,7 @@ class BIM_PT_drawings(Panel):
draw_project_not_saved_ui(self) draw_project_not_saved_ui(self)
return return
self.props = context.scene.DocProperties self.props = tool.Drawing.get_document_props()
if not self.props.is_editing_drawings: if not self.props.is_editing_drawings:
row = self.layout.row(align=True) row = self.layout.row(align=True)
@@ -302,7 +302,7 @@ class BIM_PT_schedules(Panel):
draw_project_not_saved_ui(self) draw_project_not_saved_ui(self)
return return
self.props = context.scene.DocProperties self.props = tool.Drawing.get_document_props()
if not self.props.is_editing_schedules: if not self.props.is_editing_schedules:
row = self.layout.row(align=True) row = self.layout.row(align=True)
@@ -352,7 +352,7 @@ class BIM_PT_references(Panel):
draw_project_not_saved_ui(self) draw_project_not_saved_ui(self)
return return
self.props = context.scene.DocProperties self.props = tool.Drawing.get_document_props()
if not self.props.is_editing_references: if not self.props.is_editing_references:
row = self.layout.row(align=True) row = self.layout.row(align=True)
@@ -394,7 +394,7 @@ class BIM_PT_sheets(Panel):
draw_project_not_saved_ui(self) draw_project_not_saved_ui(self)
return return
self.props = context.scene.DocProperties self.props = tool.Drawing.get_document_props()
if not self.props.is_editing_sheets: if not self.props.is_editing_sheets:
row = self.layout.row(align=True) row = self.layout.row(align=True)
@@ -601,7 +601,7 @@ class BIM_UL_drawinglist(bpy.types.UIList):
selected_icon = "CHECKBOX_HLT" if item.is_selected else "CHECKBOX_DEHLT" selected_icon = "CHECKBOX_HLT" if item.is_selected else "CHECKBOX_DEHLT"
row.prop(item, "is_selected", text="", icon=selected_icon, emboss=False) row.prop(item, "is_selected", text="", icon=selected_icon, emboss=False)
row.prop(item, "name", text="", emboss=False) row.prop(item, "name", text="", emboss=False)
self.props = context.scene.DocProperties self.props = tool.Drawing.get_document_props()
if ( if (
self.props.drawings self.props.drawings
and self.props.active_drawing_id and self.props.active_drawing_id
@@ -197,6 +197,8 @@ def create_annotation_occurrence(context):
class AnnotationToolUI: class AnnotationToolUI:
layout: bpy.types.UILayout
@classmethod @classmethod
def draw(cls, context, layout): def draw(cls, context, layout):
cls.layout = layout cls.layout = layout
@@ -224,7 +226,8 @@ class AnnotationToolUI:
@classmethod @classmethod
def draw_create_object_interface(cls): def draw_create_object_interface(cls):
row = cls.layout.row(align=True) row = cls.layout.row(align=True)
row.prop(bpy.context.scene.DocProperties, "should_draw_decorations", text="Viewport Annotations") props = tool.Drawing.get_document_props()
row.prop(props, "should_draw_decorations", text="Viewport Annotations")
@classmethod @classmethod
def draw_edit_object_interface(cls, context): def draw_edit_object_interface(cls, context):
@@ -98,12 +98,14 @@ addon_keymaps = []
@persistent @persistent
def block_scale(scene): 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 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 obj.BIMObjectProperties.ifc_definition_id:
if obj.scale != (1, 1, 1): if obj.scale != (1, 1, 1):
obj.scale = (1, 1, 1) obj.scale = (1, 1, 1)
elif isinstance(obj, bpy.types.Mesh) and obj.BIMMeshProperties.ifc_definition_id: elif isinstance(obj, bpy.types.Mesh) and tool.Geometry.get_mesh_props(obj).ifc_definition_id:
if obj.scale != (1, 1, 1): if obj.scale != (1, 1, 1):
obj.scale = (1, 1, 1) obj.scale = (1, 1, 1)
+12 -10
View File
@@ -53,15 +53,16 @@ class ViewportData:
obj = bpy.context.active_object obj = bpy.context.active_object
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
modes = [obj_mode] modes: list[tuple[str, str, str, str, int]] = [obj_mode]
gprops = tool.Geometry.get_geometry_props()
if bpy.context.scene.BIMGeometryProperties.representation_obj: if gprops.representation_obj:
modes.append(item_mode) modes.append(item_mode)
if not obj: if not obj:
return modes return modes
if obj in bpy.context.scene.BIMProjectProperties.clipping_planes_objs: pprops = tool.Project.get_project_props()
if obj in pprops.clipping_planes_objs:
pass pass
elif element: elif element:
if tool.Geometry.is_locked(element): if tool.Geometry.is_locked(element):
@@ -107,8 +108,9 @@ class RepresentationsData:
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
active_representation_id = None active_representation_id = None
if obj.data and hasattr(obj.data, "BIMMeshProperties"): active_representation = tool.Geometry.get_active_representation(obj)
active_representation_id = obj.data.BIMMeshProperties.ifc_definition_id if active_representation:
active_representation_id = active_representation.id()
for representation in tool.Geometry.get_representations_iter(element): for representation in tool.Geometry.get_representations_iter(element):
representation_type = representation.RepresentationType representation_type = representation.RepresentationType
@@ -158,9 +160,9 @@ class RepresentationsData:
if not obj.data: if not obj.data:
return [] return []
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
if not (active_representation_id := obj.data.BIMMeshProperties.ifc_definition_id): base_representation = tool.Geometry.get_active_representation(obj)
if not base_representation:
return [] # Maybe in profile editing mode return [] # Maybe in profile editing mode
base_representation = tool.Ifc.get().by_id(active_representation_id)
# shape aspects matching context of the active representation # shape aspects matching context of the active representation
matching_shape_aspects = [] matching_shape_aspects = []
@@ -390,7 +392,7 @@ class PlacementData:
def load(cls): def load(cls):
cls.data = {"has_placement": cls.has_placement()} cls.data = {"has_placement": cls.has_placement()}
props = bpy.context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
obj = bpy.context.active_object obj = bpy.context.active_object
if obj and props.has_blender_offset: if obj and props.has_blender_offset:
xyz = cls.original_xyz(obj) xyz = cls.original_xyz(obj)
@@ -413,7 +415,7 @@ class PlacementData:
@classmethod @classmethod
def original_xyz(cls, obj): def original_xyz(cls, obj):
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
props = bpy.context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
xyz = ifcopenshell.util.geolocation.xyz2enh( xyz = ifcopenshell.util.geolocation.xyz2enh(
obj.matrix_world[0][3], obj.matrix_world[0][3],
obj.matrix_world[1][3], obj.matrix_world[1][3],
@@ -46,12 +46,14 @@ class ItemDecorator:
obj_is_boolean: dict[str, list[ifcopenshell.entity_instance]] = {} obj_is_boolean: dict[str, list[ifcopenshell.entity_instance]] = {}
objs: dict[str, dict[str, list]] = {} objs: dict[str, dict[str, list]] = {}
obj_matrix: dict[str, Matrix] = {} obj_matrix: dict[str, Matrix] = {}
for item_obj in context.scene.BIMGeometryProperties.item_objs: props = tool.Geometry.get_geometry_props()
for item_obj in props.item_objs:
if obj := item_obj.obj: if obj := item_obj.obj:
obj: bpy.types.Object obj: bpy.types.Object
objs[obj.name] = cls.get_obj_data(obj) objs[obj.name] = cls.get_obj_data(obj)
obj_is_selected[obj.name] = obj.select_get() obj_is_selected[obj.name] = obj.select_get()
item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) item = tool.Geometry.get_active_representation(obj)
assert item
obj_is_boolean[obj.name] = [i for i in tool.Ifc.get().get_inverse(item) if i.is_a("IfcBooleanResult")] obj_is_boolean[obj.name] = [i for i in tool.Ifc.get().get_inverse(item) if i.is_a("IfcBooleanResult")]
obj_matrix[obj.name] = obj.matrix_world.copy() obj_matrix[obj.name] = obj.matrix_world.copy()
@@ -143,7 +145,8 @@ class ItemDecorator:
color = selected_elements_color color = selected_elements_color
blf.color(font_id, *color) blf.color(font_id, *color)
for item in context.scene.BIMGeometryProperties.item_objs: props = tool.Geometry.get_geometry_props()
for item in props.item_objs:
if (obj := item.obj) and obj.hide_get() == False: if (obj := item.obj) and obj.hide_get() == False:
if obj.select_get(): if obj.select_get():
centroid = obj.matrix_world @ Vector(obj.bound_box[0]).lerp(Vector(obj.bound_box[6]), 0.5) centroid = obj.matrix_world @ Vector(obj.bound_box[0]).lerp(Vector(obj.bound_box[6]), 0.5)
+146 -97
View File
@@ -23,6 +23,7 @@ import numpy as np
import numpy.typing as npt import numpy.typing as npt
import ifcopenshell import ifcopenshell
import ifcopenshell.api.layer import ifcopenshell.api.layer
import ifcopenshell.api.style
import ifcopenshell.util.element import ifcopenshell.util.element
import ifcopenshell.util.placement import ifcopenshell.util.placement
import ifcopenshell.util.representation import ifcopenshell.util.representation
@@ -78,7 +79,8 @@ class OverrideMeshSeparate(bpy.types.Operator, tool.Ifc.Operator):
self.separate_element(element) self.separate_element(element)
def separate_item(self, context, obj): def separate_item(self, context, obj):
item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) item = tool.Geometry.get_active_representation(obj)
assert item
if tool.Geometry.is_meshlike_item(item): if tool.Geometry.is_meshlike_item(item):
previous_selected_objects = context.selected_objects previous_selected_objects = context.selected_objects
bpy.ops.mesh.separate(type=self.type) bpy.ops.mesh.separate(type=self.type)
@@ -86,7 +88,7 @@ class OverrideMeshSeparate(bpy.types.Operator, tool.Ifc.Operator):
if obj in previous_selected_objects: if obj in previous_selected_objects:
continue continue
self.add_meshlike_item(obj) self.add_meshlike_item(obj)
tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj) tool.Geometry.reload_representation(tool.Geometry.get_geometry_props().representation_obj)
else: else:
self.report({"INFO"}, f"Separating an {item.is_a()} is not supported") self.report({"INFO"}, f"Separating an {item.is_a()} is not supported")
@@ -121,7 +123,7 @@ class OverrideMeshSeparate(bpy.types.Operator, tool.Ifc.Operator):
representation.Items = list(representation.Items) + [item] representation.Items = list(representation.Items) + [item]
obj.name = obj.data.name = f"Item/{item.is_a()}/{item.id()}" obj.name = obj.data.name = f"Item/{item.is_a()}/{item.id()}"
obj.data.BIMMeshProperties.ifc_definition_id = item.id() tool.Ifc.link(item, obj)
props.add_item_object(obj, item) props.add_item_object(obj, item)
def separate_element(self, element): def separate_element(self, element):
@@ -302,11 +304,12 @@ class AddRepresentation(bpy.types.Operator, tool.Ifc.Operator):
return context.window_manager.invoke_props_dialog(self) return context.window_manager.invoke_props_dialog(self)
def draw(self, context): def draw(self, context):
props = tool.Geometry.get_geometry_props()
row = self.layout.row() row = self.layout.row()
row.prop(self, "representation_conversion_method", text="") row.prop(self, "representation_conversion_method", text="")
if self.representation_conversion_method == "OBJECT": if self.representation_conversion_method == "OBJECT":
row = self.layout.row() row = self.layout.row()
row.prop(context.scene.BIMGeometryProperties, "representation_from_object", text="") row.prop(props, "representation_from_object", text="")
class SelectConnection(bpy.types.Operator, tool.Ifc.Operator): class SelectConnection(bpy.types.Operator, tool.Ifc.Operator):
@@ -457,13 +460,17 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator):
return {"FINISHED"} return {"FINISHED"}
def update_obj_mesh_representation(self, context: bpy.types.Context, obj: bpy.types.Object) -> None: def update_obj_mesh_representation(self, context: bpy.types.Context, obj: bpy.types.Object) -> None:
data = obj.data
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 = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
material = ifcopenshell.util.element.get_material(product, should_skip_usage=True) material = ifcopenshell.util.element.get_material(product, should_skip_usage=True)
# NOTE: Currently iterator doesn't detect whether opening is actually affected the representation # NOTE: Currently iterator doesn't detect whether opening is actually affected the representation
# or it's just present on the element. In theory, we can also allow editing representations # or it's just present on the element. In theory, we can also allow editing representations
# if we know that representation wasn't affected by existing openings. # if we know that representation wasn't affected by existing openings.
has_openings = tool.Geometry.has_openings(product) and obj.data.BIMMeshProperties.has_openings_applied has_openings = tool.Geometry.has_openings(product) and tool.Geometry.get_mesh_props(data).has_openings_applied
if has_openings and not self.apply_openings: if has_openings and not self.apply_openings:
# Meshlike things with openings can only be updated without openings applied. # Meshlike things with openings can only be updated without openings applied.
if self.from_ui: if self.from_ui:
@@ -486,7 +493,8 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator):
if tool.Ifc.is_moved(obj) or tool.Geometry.is_scaled(obj): if tool.Ifc.is_moved(obj) or tool.Geometry.is_scaled(obj):
core.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj) core.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
old_representation = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id) old_representation = tool.Geometry.get_active_representation(obj)
assert old_representation
if material and material.is_a() in ["IfcMaterialProfileSet", "IfcMaterialLayerSet"]: if material and material.is_a() in ["IfcMaterialProfileSet", "IfcMaterialLayerSet"]:
if self.ifc_representation_class == "IfcTessellatedFaceSet": if self.ifc_representation_class == "IfcTessellatedFaceSet":
# We are explicitly casting to a tessellation, so remove all parametric materials. # We are explicitly casting to a tessellation, so remove all parametric materials.
@@ -545,12 +553,12 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator):
tool.Geometry.run_style_add_style(obj=mat) tool.Geometry.run_style_add_style(obj=mat)
for mat in tool.Geometry.get_object_materials_without_styles(obj) for mat in tool.Geometry.get_object_materials_without_styles(obj)
] ]
ifcopenshell.api.run( props = tool.Geometry.get_geometry_props()
"style.assign_representation_styles", ifcopenshell.api.style.assign_representation_styles(
self.file, self.file,
shape_representation=new_representation, shape_representation=new_representation,
styles=tool.Geometry.get_styles(obj, only_assigned_to_faces=True), styles=tool.Geometry.get_styles(obj, only_assigned_to_faces=True),
should_use_presentation_style_assignment=context.scene.BIMGeometryProperties.should_use_presentation_style_assignment, should_use_presentation_style_assignment=props.should_use_presentation_style_assignment,
) )
tool.Geometry.record_object_materials(obj) tool.Geometry.record_object_materials(obj)
@@ -569,8 +577,8 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator):
continue continue
representation.RepresentationIdentifier = "Reference" representation.RepresentationIdentifier = "Reference"
obj.data.BIMMeshProperties.ifc_definition_id = int(new_representation.id()) tool.Ifc.link(new_representation, data)
obj.data.name = f"{old_representation.ContextOfItems.id()}/{new_representation.id()}" data.name = tool.Loader.get_mesh_name(new_representation)
# TODO: In simple scenarios, a type has a ShapeRepresentation of ID # TODO: In simple scenarios, a type has a ShapeRepresentation of ID
# 123. This is then mapped through mapped representations by # 123. This is then mapped through mapped representations by
@@ -586,7 +594,7 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator):
# transformations. # transformations.
core.remove_representation(tool.Ifc, tool.Geometry, obj=obj, representation=old_representation) core.remove_representation(tool.Ifc, tool.Geometry, obj=obj, representation=old_representation)
if obj.data.BIMMeshProperties.ifc_parameters: if mprops.ifc_parameters:
core.get_representation_ifc_parameters(tool.Geometry, obj=obj) core.get_representation_ifc_parameters(tool.Geometry, obj=obj)
@@ -598,12 +606,13 @@ class UpdateParametricRepresentation(bpy.types.Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return context.active_object and context.active_object.mode == "OBJECT" return (obj := context.active_object) and obj.mode == "OBJECT" and tool.Geometry.has_mesh_properties(obj.data)
def execute(self, context): def execute(self, context):
self.file = IfcStore.get_file() self.file = IfcStore.get_file()
obj = context.active_object obj = context.active_object
props = obj.data.BIMMeshProperties assert obj and tool.Geometry.has_mesh_properties(obj.data)
props = tool.Geometry.get_mesh_props(obj.data)
parameter = props.ifc_parameters[self.index] parameter = props.ifc_parameters[self.index]
self.file.by_id(parameter.step_id)[parameter.index] = parameter.value self.file.by_id(parameter.step_id)[parameter.index] = parameter.value
show_representation_parameters = bool(props.ifc_parameters) show_representation_parameters = bool(props.ifc_parameters)
@@ -627,8 +636,10 @@ class GetRepresentationIfcParameters(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def _execute(self, context): def _execute(self, context):
core.get_representation_ifc_parameters(tool.Geometry, obj=context.active_object) obj = context.active_object
parameters = context.active_object.data.BIMMeshProperties.ifc_parameters assert obj and tool.Geometry.has_mesh_properties((data := obj.data))
core.get_representation_ifc_parameters(tool.Geometry, obj=obj)
parameters = tool.Geometry.get_mesh_props(data).ifc_parameters
self.report({"INFO"}, f"{len(parameters)} parameters found.") self.report({"INFO"}, f"{len(parameters)} parameters found.")
@@ -721,7 +732,7 @@ class OverrideDelete(bpy.types.Operator):
row = self.layout.row() row = self.layout.row()
row.label(text="Warning: Faster deletion will use more memory.", icon="ERROR") row.label(text="Warning: Faster deletion will use more memory.", icon="ERROR")
def _execute(self, context): def _execute(self, context: bpy.types.Context):
start_time = time() start_time = time()
if self.is_batch: if self.is_batch:
@@ -730,9 +741,7 @@ class OverrideDelete(bpy.types.Operator):
self.process_arrays(context) self.process_arrays(context)
clear_active_object = True clear_active_object = True
for obj in context.selected_objects: for obj in context.selected_objects:
try: if not tool.Blender.is_valid_data_block(obj):
obj.name
except:
continue continue
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
if element: if element:
@@ -782,7 +791,7 @@ class OverrideDelete(bpy.types.Operator):
data["old_file"].redo() data["old_file"].redo()
tool.Ifc.set(data["new_file"]) tool.Ifc.set(data["new_file"])
def process_arrays(self, context): def process_arrays(self, context: bpy.types.Context) -> None:
selected_objects = set(context.selected_objects) selected_objects = set(context.selected_objects)
array_parents = set() array_parents = set()
for obj in context.selected_objects: for obj in context.selected_objects:
@@ -1030,7 +1039,8 @@ class OverrideDuplicateMove(bpy.types.Operator):
# Unlink from previous boolean element # Unlink from previous boolean element
# and keep object tracked for decorations. # and keep object tracked for decorations.
if is_tracked_opening: if is_tracked_opening:
new_obj.data.BIMMeshProperties.ifc_boolean_id = 0 mprops = tool.Geometry.get_mesh_props(new_obj.data)
mprops.ifc_boolean_id = 0
tool.Root.add_tracked_opening(new_obj, tracked_opening_type) tool.Root.add_tracked_opening(new_obj, tracked_opening_type)
if obj == context.active_object: if obj == context.active_object:
@@ -1054,7 +1064,7 @@ class OverrideDuplicateMove(bpy.types.Operator):
if new.is_a("IfcRelSpaceBoundary"): if new.is_a("IfcRelSpaceBoundary"):
surface = new.ConnectionGeometry.SurfaceOnRelatingElement surface = new.ConnectionGeometry.SurfaceOnRelatingElement
temp_data.name = f"0/{surface.id()}" temp_data.name = f"0/{surface.id()}"
temp_data.BIMMeshProperties.ifc_definition_id = surface.id() tool.Ifc.link(surface, temp_data)
else: else:
tool.Blender.remove_data_block(temp_data) tool.Blender.remove_data_block(temp_data)
@@ -1090,12 +1100,14 @@ class OverrideDuplicateMove(bpy.types.Operator):
@staticmethod @staticmethod
def duplicate_item(obj: bpy.types.Object) -> None: def duplicate_item(obj: bpy.types.Object) -> None:
props = tool.Geometry.get_geometry_props() props = tool.Geometry.get_geometry_props()
item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) item = tool.Geometry.get_active_representation(obj)
assert item
new_item = ifcopenshell.util.element.copy_deep(tool.Ifc.get(), item) new_item = ifcopenshell.util.element.copy_deep(tool.Ifc.get(), item)
new_obj = obj.copy() new_obj = obj.copy()
assert tool.Geometry.has_mesh_properties(obj.data)
temp_data = obj.data.copy() temp_data = obj.data.copy()
new_obj.data = temp_data new_obj.data = temp_data
new_obj.data.BIMMeshProperties.ifc_definition_id = new_item.id() tool.Ifc.link(new_item, temp_data)
new_obj.name = obj.data.name = f"Item/{new_item.is_a()}/{new_item.id()}" new_obj.name = obj.data.name = f"Item/{new_item.is_a()}/{new_item.id()}"
props.add_item_object(new_obj, new_item) props.add_item_object(new_obj, new_item)
@@ -1671,7 +1683,8 @@ class OverrideJoin(bpy.types.Operator, tool.Ifc.Operator):
def join_item(self) -> None: def join_item(self) -> None:
props = tool.Geometry.get_geometry_props() props = tool.Geometry.get_geometry_props()
ifc_file = tool.Ifc.get() ifc_file = tool.Ifc.get()
item = tool.Ifc.get().by_id(self.target.data.BIMMeshProperties.ifc_definition_id) item = tool.Geometry.get_active_representation(self.target)
assert item
if tool.Geometry.is_meshlike_item(item): if tool.Geometry.is_meshlike_item(item):
tool.Geometry.dissolve_triangulated_edges(self.target) tool.Geometry.dissolve_triangulated_edges(self.target)
item_objs = [i.obj for i in props.item_objs if i.obj] item_objs = [i.obj for i in props.item_objs if i.obj]
@@ -1694,7 +1707,7 @@ class OverrideJoin(bpy.types.Operator, tool.Ifc.Operator):
for item_data in items_data: for item_data in items_data:
props.add_item_object(item_data["obj"], ifc_file.by_id(item_data["ifc_definition_id"])) props.add_item_object(item_data["obj"], ifc_file.by_id(item_data["ifc_definition_id"]))
tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj) tool.Geometry.reload_representation(props.representation_obj)
bpy.context.view_layer.update() bpy.context.view_layer.update()
tool.Root.reload_item_decorator() tool.Root.reload_item_decorator()
@@ -1702,7 +1715,8 @@ class OverrideJoin(bpy.types.Operator, tool.Ifc.Operator):
ifc_file = tool.Ifc.get() ifc_file = tool.Ifc.get()
builder = ShapeBuilder(ifc_file) builder = ShapeBuilder(ifc_file)
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(ifc_file) si_conversion = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
representation = ifc_file.by_id(self.target.data.BIMMeshProperties.ifc_definition_id) representation = tool.Geometry.get_active_representation(self.target)
assert representation
representation_type = representation.RepresentationType representation_type = representation.RepresentationType
if representation_type in ("Tessellation", "Brep"): if representation_type in ("Tessellation", "Brep"):
for obj in bpy.context.selected_objects: for obj in bpy.context.selected_objects:
@@ -1741,7 +1755,8 @@ class OverrideJoin(bpy.types.Operator, tool.Ifc.Operator):
continue continue
# Only objects of the same representation type can be joined # Only objects of the same representation type can be joined
obj_rep = ifc_file.by_id(obj.data.BIMMeshProperties.ifc_definition_id) obj_rep = tool.Geometry.get_active_representation(obj)
assert obj_rep
if obj_rep.RepresentationType != representation_type: if obj_rep.RepresentationType != representation_type:
obj.select_set(False) obj.select_set(False)
self.report( self.report(
@@ -1892,9 +1907,10 @@ class OverrideEscape(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def execute(self, context): def execute(self, context):
if context.scene.BIMGeometryProperties.mode == "ITEM": props = tool.Geometry.get_geometry_props()
if props.mode == "ITEM":
tool.Geometry.disable_item_mode() tool.Geometry.disable_item_mode()
elif context.scene.BIMGeometryProperties.mode == "EDIT": elif props.mode == "EDIT":
bpy.ops.bim.override_mode_set_object("INVOKE_DEFAULT", should_save=False) bpy.ops.bim.override_mode_set_object("INVOKE_DEFAULT", should_save=False)
tool.Geometry.disable_item_mode() tool.Geometry.disable_item_mode()
elif tool.Model.get_model_props().openings: elif tool.Model.get_model_props().openings:
@@ -1950,6 +1966,7 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator):
def handle_single_object(self, context: bpy.types.Context, obj: bpy.types.Object) -> None: def handle_single_object(self, context: bpy.types.Context, obj: bpy.types.Object) -> None:
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
props = tool.Geometry.get_geometry_props() props = tool.Geometry.get_geometry_props()
pprops = tool.Project.get_project_props()
if obj == props.representation_obj: if obj == props.representation_obj:
self.report({"ERROR"}, f"Element '{obj.name}' is in item mode and cannot be edited directly") self.report({"ERROR"}, f"Element '{obj.name}' is in item mode and cannot be edited directly")
elif obj in [o.obj for o in context.scene.BIMAggregateProperties.not_editing_objects]: elif obj in [o.obj for o in context.scene.BIMAggregateProperties.not_editing_objects]:
@@ -1957,7 +1974,7 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator):
self.report( self.report(
{"ERROR"}, f"Element '{obj.name}' does not belong to this aggregate and cannot be edited directly" {"ERROR"}, f"Element '{obj.name}' does not belong to this aggregate and cannot be edited directly"
) )
elif obj in bpy.context.scene.BIMProjectProperties.clipping_planes_objs: elif obj in pprops.clipping_planes_objs:
self.report({"ERROR"}, "Clipping planes cannot be edited") self.report({"ERROR"}, "Clipping planes cannot be edited")
elif element: elif element:
if not obj.data: if not obj.data:
@@ -2005,12 +2022,15 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator):
self.handle_single_object(context, obj) self.handle_single_object(context, obj)
def enable_editing_representation_item(self, context: bpy.types.Context, obj: bpy.types.Object) -> None: def enable_editing_representation_item(self, context: bpy.types.Context, obj: bpy.types.Object) -> None:
item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) item = tool.Geometry.get_active_representation(obj)
element = tool.Ifc.get_entity(context.scene.BIMGeometryProperties.representation_obj) assert item
element = tool.Ifc.get_entity(tool.Geometry.get_geometry_props().representation_obj)
if tool.Geometry.is_meshlike_item(item): if tool.Geometry.is_meshlike_item(item):
tool.Geometry.dissolve_triangulated_edges(obj) tool.Geometry.dissolve_triangulated_edges(obj)
tool.Blender.select_and_activate_single_object(context, obj) tool.Blender.select_and_activate_single_object(context, obj)
obj.data.BIMMeshProperties.mesh_checksum = tool.Geometry.get_mesh_checksum(obj.data) assert isinstance(mesh := obj.data, bpy.types.Mesh)
props = tool.Geometry.get_mesh_props(mesh)
props.mesh_checksum = tool.Geometry.get_mesh_checksum(mesh)
self.enable_edit_mode(context) self.enable_edit_mode(context)
elif ( elif (
item.is_a("IfcSweptAreaSolid") item.is_a("IfcSweptAreaSolid")
@@ -2027,21 +2047,21 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator):
f"Couldn't import profile, editing it directly is not yet supported. Failing profile: {profile}.", f"Couldn't import profile, editing it directly is not yet supported. Failing profile: {profile}.",
) )
return return
obj.data.BIMMeshProperties.ifc_definition_id = item.id() tool.Ifc.link(item, obj.data)
self.enable_edit_mode(context) self.enable_edit_mode(context)
ProfileDecorator.install(context) ProfileDecorator.install(context)
if not bpy.app.background: if not bpy.app.background:
tool.Blender.set_viewport_tool("bim.cad_tool") tool.Blender.set_viewport_tool("bim.cad_tool")
elif item.is_a("IfcAnnotationFillArea"): elif item.is_a("IfcAnnotationFillArea"):
tool.Model.import_annotation_fill_area(item, obj=obj) tool.Model.import_annotation_fill_area(item, obj=obj)
obj.data.BIMMeshProperties.ifc_definition_id = item.id() tool.Ifc.link(item, obj.data)
self.enable_edit_mode(context) self.enable_edit_mode(context)
ProfileDecorator.install(context) ProfileDecorator.install(context)
if not bpy.app.background: if not bpy.app.background:
tool.Blender.set_viewport_tool("bim.cad_tool") tool.Blender.set_viewport_tool("bim.cad_tool")
elif tool.Geometry.is_curvelike_item(item): elif tool.Geometry.is_curvelike_item(item):
tool.Model.import_curve(item, obj=obj) tool.Model.import_curve(item, obj=obj)
obj.data.BIMMeshProperties.ifc_definition_id = item.id() tool.Ifc.link(item, obj.data)
self.enable_edit_mode(context) self.enable_edit_mode(context)
ProfileDecorator.install(context) ProfileDecorator.install(context)
if not bpy.app.background: if not bpy.app.background:
@@ -2052,10 +2072,11 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator):
def enable_edit_mode(self, context: bpy.types.Context) -> Union[None, set[str]]: def enable_edit_mode(self, context: bpy.types.Context) -> Union[None, set[str]]:
if tool.Blender.toggle_edit_mode(context) == {"CANCELLED"}: if tool.Blender.toggle_edit_mode(context) == {"CANCELLED"}:
return {"CANCELLED"} return {"CANCELLED"}
context.scene.BIMGeometryProperties.is_changing_mode = True props = tool.Geometry.get_geometry_props()
if context.scene.BIMGeometryProperties.mode != "EDIT": props.is_changing_mode = True
context.scene.BIMGeometryProperties.mode = "EDIT" if props.mode != "EDIT":
context.scene.BIMGeometryProperties.is_changing_mode = False props.mode = "EDIT"
props.is_changing_mode = False
def has_aggregates(self, objs): def has_aggregates(self, objs):
for obj in objs: for obj in objs:
@@ -2102,14 +2123,15 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
tool.Blender.toggle_edit_mode(context) tool.Blender.toggle_edit_mode(context)
context.scene.BIMGeometryProperties.is_changing_mode = True props = tool.Geometry.get_geometry_props()
if context.scene.BIMGeometryProperties.representation_obj: props.is_changing_mode = True
if context.scene.BIMGeometryProperties.mode != "ITEM": if props.representation_obj:
context.scene.BIMGeometryProperties.mode = "ITEM" if props.mode != "ITEM":
props.mode = "ITEM"
else: else:
if context.scene.BIMGeometryProperties.mode != "OBJECT": if props.mode != "OBJECT":
context.scene.BIMGeometryProperties.mode = "OBJECT" props.mode = "OBJECT"
context.scene.BIMGeometryProperties.is_changing_mode = False props.is_changing_mode = False
if context.active_object and self.should_save: if context.active_object and self.should_save:
element = tool.Ifc.get_entity(context.active_object) element = tool.Ifc.get_entity(context.active_object)
@@ -2155,24 +2177,27 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
else: else:
bpy.ops.bim.edit_extrusion_profile() bpy.ops.bim.edit_extrusion_profile()
return self.execute(context) return self.execute(context)
elif obj.data.BIMMeshProperties.ifc_definition_id: elif representation := tool.Geometry.get_active_representation(obj):
if not tool.Geometry.has_geometric_data(obj): if not tool.Geometry.is_geometric_data(obj.data):
self.is_valid = False self.is_valid = False
self.should_save = False self.should_save = False
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) assert tool.Geometry.has_mesh_properties(obj.data)
mesh_props = tool.Geometry.get_mesh_props(obj.data)
if tool.Geometry.is_meshlike( if tool.Geometry.is_meshlike(
representation representation
) and obj.data.BIMMeshProperties.mesh_checksum != tool.Geometry.get_mesh_checksum(obj.data): ) and mesh_props.mesh_checksum != tool.Geometry.get_mesh_checksum(obj.data):
self.edited_objs.append(obj) self.edited_objs.append(obj)
elif getattr(element, "HasOpenings", None): elif getattr(element, "HasOpenings", None):
self.unchanged_objs_with_openings.append(obj) self.unchanged_objs_with_openings.append(obj)
else: else:
tool.Ifc.finish_edit(obj) tool.Ifc.finish_edit(obj)
elif element.is_a("IfcGridAxis"): elif element.is_a("IfcGridAxis"):
if not tool.Geometry.has_geometric_data(obj): if not tool.Geometry.is_geometric_data(obj.data):
self.is_valid = False self.is_valid = False
self.should_save = False self.should_save = False
if obj.data.BIMMeshProperties.mesh_checksum != tool.Geometry.get_mesh_checksum(obj.data): assert tool.Geometry.has_mesh_properties(obj.data)
mesh_props = tool.Geometry.get_mesh_props(obj.data)
if mesh_props.mesh_checksum != tool.Geometry.get_mesh_checksum(obj.data):
self.edited_objs.append(obj) self.edited_objs.append(obj)
else: else:
tool.Ifc.finish_edit(obj) tool.Ifc.finish_edit(obj)
@@ -2197,9 +2222,11 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
return {"FINISHED"} return {"FINISHED"}
def edit_representation_item(self, obj: bpy.types.Object) -> None: def edit_representation_item(self, obj: bpy.types.Object) -> None:
item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) props = tool.Geometry.get_geometry_props()
item = tool.Geometry.get_active_representation(obj)
assert item
if tool.Geometry.is_meshlike_item(item): if tool.Geometry.is_meshlike_item(item):
if tool.Geometry.has_geometric_data(obj) and obj.data.polygons: if tool.Geometry.is_geometric_data(obj.data) and obj.data.polygons:
tool.Geometry.edit_meshlike_item(obj) tool.Geometry.edit_meshlike_item(obj)
else: else:
tool.Geometry.import_item(obj) tool.Geometry.import_item(obj)
@@ -2220,11 +2247,11 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
ifcopenshell.util.element.replace_attribute(inverse, old_profile, profile) ifcopenshell.util.element.replace_attribute(inverse, old_profile, profile)
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), old_profile) ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), old_profile)
tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj) tool.Geometry.reload_representation(props.representation_obj)
tool.Geometry.import_item(obj) tool.Geometry.import_item(obj)
tool.Geometry.import_item_attributes(obj) tool.Geometry.import_item_attributes(obj)
element = tool.Ifc.get_entity(bpy.context.scene.BIMGeometryProperties.representation_obj) element = tool.Ifc.get_entity(props.representation_obj)
# Only certain classes should have a footprint # Only certain classes should have a footprint
if element.is_a() in ("IfcSlab", "IfcRamp"): if element.is_a() in ("IfcSlab", "IfcRamp"):
footprint_context = ifcopenshell.util.representation.get_context( footprint_context = ifcopenshell.util.representation.get_context(
@@ -2277,9 +2304,9 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
for inverse in tool.Ifc.get().get_inverse(item): for inverse in tool.Ifc.get().get_inverse(item):
ifcopenshell.util.element.replace_attribute(inverse, item, profile) ifcopenshell.util.element.replace_attribute(inverse, item, profile)
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), item) ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), item)
obj.data.BIMMeshProperties.ifc_definition_id = profile.id() tool.Ifc.link(profile, obj.data)
tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj) tool.Geometry.reload_representation(props.representation_obj)
tool.Geometry.import_item(obj) tool.Geometry.import_item(obj)
tool.Geometry.import_item_attributes(obj) tool.Geometry.import_item_attributes(obj)
elif tool.Geometry.is_curvelike_item(item): elif tool.Geometry.is_curvelike_item(item):
@@ -2305,10 +2332,9 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
ifcopenshell.util.element.replace_attribute(inverse, item, new) ifcopenshell.util.element.replace_attribute(inverse, item, new)
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), item) ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), item)
obj.data.BIMMeshProperties.ifc_definition_id = new.id() tool.Ifc.link(new, obj.data)
tool.Geometry.import_item(obj) tool.Geometry.import_item(obj)
props = tool.Geometry.get_geometry_props()
for item in additional_curves: for item in additional_curves:
representation = tool.Geometry.get_active_representation(props.representation_obj) representation = tool.Geometry.get_active_representation(props.representation_obj)
representation = ifcopenshell.util.representation.resolve_representation(representation) representation = ifcopenshell.util.representation.resolve_representation(representation)
@@ -2317,7 +2343,7 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
name = f"Item/{item.is_a()}/{item.id()}" name = f"Item/{item.is_a()}/{item.id()}"
mesh = bpy.data.meshes.new(name) mesh = bpy.data.meshes.new(name)
new_obj = bpy.data.objects.new(name, mesh) new_obj = bpy.data.objects.new(name, mesh)
new_obj.data.BIMMeshProperties.ifc_definition_id = item.id() tool.Ifc.link(item, new_obj.data)
bpy.context.collection.objects.link(new_obj) bpy.context.collection.objects.link(new_obj)
props.add_item_object(new_obj, item) props.add_item_object(new_obj, item)
new_obj.matrix_world = obj.matrix_world new_obj.matrix_world = obj.matrix_world
@@ -2330,10 +2356,11 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
def enable_edit_mode(self, context): def enable_edit_mode(self, context):
if tool.Blender.toggle_edit_mode(context) == {"CANCELLED"}: if tool.Blender.toggle_edit_mode(context) == {"CANCELLED"}:
return {"CANCELLED"} return {"CANCELLED"}
context.scene.BIMGeometryProperties.is_changing_mode = True props = tool.Geometry.get_geometry_props()
if context.scene.BIMGeometryProperties.mode != "EDIT": props.is_changing_mode = True
context.scene.BIMGeometryProperties.mode = "EDIT" if props.mode != "EDIT":
context.scene.BIMGeometryProperties.is_changing_mode = False props.mode = "EDIT"
props.is_changing_mode = False
class FlipObject(bpy.types.Operator): class FlipObject(bpy.types.Operator):
@@ -2370,12 +2397,13 @@ class EnableEditingRepresentationItems(bpy.types.Operator, tool.Ifc.Operator):
item.tags += "," item.tags += ","
item.tags += tag item.tags += tag
if obj.data and hasattr(obj.data, "BIMMeshProperties"): if tool.Geometry.has_mesh_properties((data := obj.data)):
active_representation_id = obj.data.BIMMeshProperties.ifc_definition_id representation = tool.Geometry.get_data_representation(data)
representation = tool.Ifc.get().by_id(active_representation_id) assert representation
# Shape aspects must be considered from the PartOfProductDefinitionShape level # Shape aspects must be considered from the PartOfProductDefinitionShape level
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
assert element
product_reps = [] product_reps = []
if element.is_a("IfcProduct"): if element.is_a("IfcProduct"):
product_reps = [element.Representation] product_reps = [element.Representation]
@@ -2440,7 +2468,8 @@ class DisableEditingRepresentationItems(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context): def _execute(self, context):
obj = tool.Geometry.get_active_or_representation_obj() obj = tool.Geometry.get_active_or_representation_obj()
obj.BIMGeometryProperties.is_editing = False assert obj
tool.Geometry.get_object_geometry_props(obj).is_editing = False
class RemoveRepresentationItem(bpy.types.Operator, tool.Ifc.Operator): class RemoveRepresentationItem(bpy.types.Operator, tool.Ifc.Operator):
@@ -2451,9 +2480,12 @@ class RemoveRepresentationItem(bpy.types.Operator, tool.Ifc.Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
if context.scene.BIMGeometryProperties.representation_obj: if tool.Geometry.get_geometry_props().representation_obj:
return False # Artificial restriction for now to prevent removing when in item mode return False # Artificial restriction for now to prevent removing when in item mode
if not (obj := tool.Geometry.get_active_or_representation_obj()) or len(obj.BIMGeometryProperties.items) <= 1: if (
not (obj := tool.Geometry.get_active_or_representation_obj())
or len(tool.Geometry.get_object_geometry_props(obj).items) <= 1
):
cls.poll_message_set( cls.poll_message_set(
"Active object need to have more than 1 representation items to keep representation valid" "Active object need to have more than 1 representation items to keep representation valid"
) )
@@ -2488,13 +2520,17 @@ class SelectRepresentationItem(bpy.types.Operator):
def execute(self, context): def execute(self, context):
obj = tool.Geometry.get_active_or_representation_obj() obj = tool.Geometry.get_active_or_representation_obj()
item = tool.Ifc.get().by_id(obj.BIMGeometryProperties.active_item.ifc_definition_id) obj_props = tool.Geometry.get_object_geometry_props(obj)
assert obj_props.active_item
item = tool.Ifc.get().by_id(obj_props.active_item.ifc_definition_id)
item_ids = self.get_nested_item_ids(item) item_ids = self.get_nested_item_ids(item)
props = tool.Geometry.get_geometry_props() props = tool.Geometry.get_geometry_props()
for item_obj in props.item_objs: for item_obj in props.item_objs:
if item_obj.obj.data.BIMMeshProperties.ifc_definition_id in item_ids: obj_ = item_obj.obj
tool.Blender.select_object(item_obj.obj) props = tool.Geometry.get_mesh_props(obj_.data)
if props.ifc_definition_id in item_ids:
tool.Blender.select_object(obj_)
return {"FINISHED"} return {"FINISHED"}
def get_nested_item_ids(self, item): def get_nested_item_ids(self, item):
@@ -2512,7 +2548,7 @@ class SelectRepresentationItem(bpy.types.Operator):
def poll_editing_representation_item_style(cls, context): def poll_editing_representation_item_style(cls, context):
if not (obj := tool.Geometry.get_active_or_representation_obj()): if not (obj := tool.Geometry.get_active_or_representation_obj()):
return False return False
props = obj.BIMGeometryProperties props = tool.Geometry.get_object_geometry_props(obj)
if not props.is_editing: if not props.is_editing:
return False return False
if not (item := props.active_item): if not (item := props.active_item):
@@ -2547,7 +2583,8 @@ class EnableEditingRepresentationItemStyle(bpy.types.Operator, tool.Ifc.Operator
def _execute(self, context): def _execute(self, context):
obj = tool.Geometry.get_active_or_representation_obj() obj = tool.Geometry.get_active_or_representation_obj()
props = obj.BIMGeometryProperties assert obj
props = tool.Geometry.get_object_geometry_props(obj)
props.is_editing_item_style = True props.is_editing_item_style = True
ifc_file = tool.Ifc.get() ifc_file = tool.Ifc.get()
@@ -2566,7 +2603,8 @@ class EditRepresentationItemStyle(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context): def _execute(self, context):
obj = tool.Geometry.get_active_or_representation_obj() obj = tool.Geometry.get_active_or_representation_obj()
props = obj.BIMGeometryProperties assert obj
props = tool.Geometry.get_object_geometry_props(obj)
props.is_editing_item_style = False props.is_editing_item_style = False
ifc_file = tool.Ifc.get() ifc_file = tool.Ifc.get()
@@ -2588,7 +2626,7 @@ class DisableEditingRepresentationItemStyle(bpy.types.Operator, tool.Ifc.Operato
def _execute(self, context): def _execute(self, context):
obj = tool.Geometry.get_active_or_representation_obj() obj = tool.Geometry.get_active_or_representation_obj()
props = obj.BIMGeometryProperties props = tool.Geometry.get_object_geometry_props(obj)
props.is_editing_item_style = False props.is_editing_item_style = False
@@ -2604,7 +2642,8 @@ class UnassignRepresentationItemStyle(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context): def _execute(self, context):
active_obj = tool.Geometry.get_active_or_representation_obj() active_obj = tool.Geometry.get_active_or_representation_obj()
active_props = active_obj.BIMGeometryProperties assert active_obj
active_props = tool.Geometry.get_object_geometry_props(active_obj)
active_props.is_editing_item_style = False active_props.is_editing_item_style = False
# Get active representation item # Get active representation item
@@ -2671,7 +2710,8 @@ class EnableEditingRepresentationItemShapeAspect(bpy.types.Operator, tool.Ifc.Op
def _execute(self, context): def _execute(self, context):
obj = tool.Geometry.get_active_or_representation_obj() obj = tool.Geometry.get_active_or_representation_obj()
props = obj.BIMGeometryProperties assert obj
props = tool.Geometry.get_object_geometry_props(obj)
props.is_editing_item_shape_aspect = True props.is_editing_item_shape_aspect = True
# set dropdown to currently active shape aspect # set dropdown to currently active shape aspect
@@ -2687,11 +2727,13 @@ class EditRepresentationItemShapeAspect(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context): def _execute(self, context):
obj = tool.Geometry.get_active_or_representation_obj() obj = tool.Geometry.get_active_or_representation_obj()
assert obj
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
props = obj.BIMGeometryProperties props = tool.Geometry.get_object_geometry_props(obj)
props.is_editing_item_shape_aspect = False props.is_editing_item_shape_aspect = False
ifc_file = tool.Ifc.get() ifc_file = tool.Ifc.get()
assert props.active_item
representation_item_id = props.active_item.ifc_definition_id representation_item_id = props.active_item.ifc_definition_id
representation_item = ifc_file.by_id(representation_item_id) representation_item = ifc_file.by_id(representation_item_id)
@@ -2744,7 +2786,8 @@ class DisableEditingRepresentationItemShapeAspect(bpy.types.Operator, tool.Ifc.O
def _execute(self, context): def _execute(self, context):
obj = tool.Geometry.get_active_or_representation_obj() obj = tool.Geometry.get_active_or_representation_obj()
props = obj.BIMGeometryProperties assert obj
props = tool.Geometry.get_object_geometry_props(obj)
props.is_editing_item_shape_aspect = False props.is_editing_item_shape_aspect = False
@@ -2755,10 +2798,12 @@ class RemoveRepresentationItemFromShapeAspect(bpy.types.Operator, tool.Ifc.Opera
def _execute(self, context): def _execute(self, context):
obj = tool.Geometry.get_active_or_representation_obj() obj = tool.Geometry.get_active_or_representation_obj()
assert obj
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
props = obj.BIMGeometryProperties props = tool.Geometry.get_object_geometry_props(obj)
ifc_file = tool.Ifc.get() ifc_file = tool.Ifc.get()
assert props.active_item
representation_item_id = props.active_item.ifc_definition_id representation_item_id = props.active_item.ifc_definition_id
representation_item = ifc_file.by_id(representation_item_id) representation_item = ifc_file.by_id(representation_item_id)
shape_aspect = ifc_file.by_id(props.active_item.shape_aspect_id) shape_aspect = ifc_file.by_id(props.active_item.shape_aspect_id)
@@ -2841,7 +2886,7 @@ class ImportRepresentationItems(bpy.types.Operator, tool.Ifc.Operator):
boolean_ids.add(item.SecondOperand.id()) boolean_ids.add(item.SecondOperand.id())
continue continue
item_mesh = bpy.data.meshes.new(f"Item/{item.is_a()}/{item_id}") item_mesh = bpy.data.meshes.new(f"Item/{item.is_a()}/{item_id}")
item_mesh.BIMMeshProperties.ifc_definition_id = item_id tool.Ifc.link(item, item_mesh)
item_obj = bpy.data.objects.new(f"Item/{item.is_a()}/{item_id}", item_mesh) item_obj = bpy.data.objects.new(f"Item/{item.is_a()}/{item_id}", item_mesh)
item_obj.matrix_world = obj.matrix_world item_obj.matrix_world = obj.matrix_world
@@ -2871,7 +2916,7 @@ class UpdateItemAttributes(bpy.types.Operator, tool.Ifc.Operator):
obj = context.active_object obj = context.active_object
tool.Geometry.sync_item_positions() tool.Geometry.sync_item_positions()
tool.Geometry.update_item_attributes(obj) tool.Geometry.update_item_attributes(obj)
tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj) tool.Geometry.reload_representation(tool.Geometry.get_geometry_props().representation_obj)
tool.Geometry.import_item(obj) tool.Geometry.import_item(obj)
tool.Root.reload_item_decorator() tool.Root.reload_item_decorator()
@@ -2888,6 +2933,10 @@ class NameProfile(bpy.types.Operator, tool.Ifc.Operator):
options={"SKIP_SAVE"}, options={"SKIP_SAVE"},
) )
if TYPE_CHECKING:
extrusion_item_obj: str
profile_name: str
def invoke(self, context, event): def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self) return context.window_manager.invoke_props_dialog(self)
@@ -2902,7 +2951,7 @@ class NameProfile(bpy.types.Operator, tool.Ifc.Operator):
ifc_file = tool.Ifc.get() ifc_file = tool.Ifc.get()
extrusion_item_obj = bpy.data.objects[self.extrusion_item_obj] extrusion_item_obj = bpy.data.objects[self.extrusion_item_obj]
mesh_props = extrusion_item_obj.data.BIMMeshProperties mesh_props = tool.Geometry.get_mesh_props(extrusion_item_obj.data)
extrusion = ifc_file.by_id(mesh_props.ifc_definition_id) extrusion = ifc_file.by_id(mesh_props.ifc_definition_id)
assert extrusion.is_a("IfcSweptAreaSolid") assert extrusion.is_a("IfcSweptAreaSolid")
profile = extrusion.SweptArea profile = extrusion.SweptArea
@@ -2972,9 +3021,9 @@ class AddMeshlikeItem(bpy.types.Operator, tool.Ifc.Operator):
props.add_item_object(obj, item) props.add_item_object(obj, item)
representation.Items = list(representation.Items) + [item] representation.Items = list(representation.Items) + [item]
tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj) tool.Geometry.reload_representation(props.representation_obj)
obj.name = obj.data.name = f"Item/{item.is_a()}/{item.id()}" obj.name = obj.data.name = f"Item/{item.is_a()}/{item.id()}"
obj.data.BIMMeshProperties.ifc_definition_id = item.id() tool.Geometry.get_mesh_props(obj.data).ifc_definition_id = item.id()
tool.Root.reload_item_decorator() tool.Root.reload_item_decorator()
@@ -3020,10 +3069,10 @@ class AddSweptAreaSolidItem(bpy.types.Operator, tool.Ifc.Operator):
props.add_item_object(obj, item) props.add_item_object(obj, item)
representation.Items = list(representation.Items) + [item] representation.Items = list(representation.Items) + [item]
tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj) tool.Geometry.reload_representation(props.representation_obj)
obj.name = obj.data.name = f"Item/{item.is_a()}/{item.id()}" obj.name = obj.data.name = f"Item/{item.is_a()}/{item.id()}"
obj.data.BIMMeshProperties.ifc_definition_id = item.id() tool.Geometry.get_mesh_props(obj.data).ifc_definition_id = item.id()
tool.Geometry.import_item(obj) tool.Geometry.import_item(obj)
tool.Geometry.import_item_attributes(obj) tool.Geometry.import_item_attributes(obj)
tool.Root.reload_item_decorator() tool.Root.reload_item_decorator()
@@ -3091,10 +3140,10 @@ class AddCurvelikeItem(bpy.types.Operator, tool.Ifc.Operator):
props.add_item_object(obj, item) props.add_item_object(obj, item)
representation.Items = list(representation.Items) + [item] representation.Items = list(representation.Items) + [item]
tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj) tool.Geometry.reload_representation(props.representation_obj)
obj.name = obj.data.name = f"Item/{item.is_a()}/{item.id()}" obj.name = obj.data.name = f"Item/{item.is_a()}/{item.id()}"
obj.data.BIMMeshProperties.ifc_definition_id = item.id() tool.Geometry.get_mesh_props(obj.data).ifc_definition_id = item.id()
tool.Geometry.import_item(obj) tool.Geometry.import_item(obj)
tool.Geometry.import_item_attributes(obj) tool.Geometry.import_item_attributes(obj)
@@ -3140,10 +3189,10 @@ class AddHalfSpaceSolidItem(bpy.types.Operator, tool.Ifc.Operator):
representation = ifcopenshell.util.representation.resolve_representation(representation) representation = ifcopenshell.util.representation.resolve_representation(representation)
representation.Items = list(representation.Items) + [item] representation.Items = list(representation.Items) + [item]
tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj) tool.Geometry.reload_representation(props.representation_obj)
obj.name = obj.data.name = f"Item/{item.is_a()}/{item.id()}" obj.name = obj.data.name = f"Item/{item.is_a()}/{item.id()}"
obj.data.BIMMeshProperties.ifc_definition_id = item.id() tool.Geometry.get_mesh_props(obj.data).ifc_definition_id = item.id()
tool.Geometry.import_item(obj) tool.Geometry.import_item(obj)
# TODO refactor to core and not rely on selection # TODO refactor to core and not rely on selection
@@ -32,7 +32,7 @@ from bpy.props import (
FloatVectorProperty, FloatVectorProperty,
CollectionProperty, CollectionProperty,
) )
from typing import Optional, TYPE_CHECKING, Union from typing import Optional, TYPE_CHECKING, Union, Literal
def get_contexts(self, context): def get_contexts(self, context):
@@ -270,6 +270,9 @@ class BIMObjectGeometryProperties(PropertyGroup):
representation_item_layer: str representation_item_layer: str
GeometryMode = Literal["OBJECT", "ITEM", "EDIT"]
class BIMGeometryProperties(PropertyGroup): class BIMGeometryProperties(PropertyGroup):
# Revit workaround # Revit workaround
should_use_presentation_style_assignment: BoolProperty(name="Force Presentation Style Assignment", default=False) should_use_presentation_style_assignment: BoolProperty(name="Force Presentation Style Assignment", default=False)
@@ -308,7 +311,7 @@ class BIMGeometryProperties(PropertyGroup):
should_force_faceted_brep: bool should_force_faceted_brep: bool
should_force_triangulation: bool should_force_triangulation: bool
is_changing_mode: bool is_changing_mode: bool
mode: str mode: GeometryMode
representation_obj: Union[bpy.types.Object, None] representation_obj: Union[bpy.types.Object, None]
item_objs: bpy.types.bpy_prop_collection_idprop[RepresentationItemObject] item_objs: bpy.types.bpy_prop_collection_idprop[RepresentationItemObject]
representation_from_object: Union[bpy.types.Object, None] representation_from_object: Union[bpy.types.Object, None]
+21 -17
View File
@@ -53,9 +53,10 @@ def mode_menu(self, context):
UIData.load() UIData.load()
ifc_icon = f"{UIData.data['menu_icon_color_mode']}_ifc" ifc_icon = f"{UIData.data['menu_icon_color_mode']}_ifc"
row = self.layout.row(align=True) row = self.layout.row(align=True)
if context.scene.BIMGeometryProperties.mode == "EDIT": props = tool.Geometry.get_geometry_props()
if props.mode == "EDIT":
row.operator("bim.override_mode_set_object", icon="CANCEL", text="Discard Changes").should_save = False row.operator("bim.override_mode_set_object", icon="CANCEL", text="Discard Changes").should_save = False
row.prop(context.scene.BIMGeometryProperties, "mode", text="", icon_value=bonsai.bim.icons[ifc_icon].icon_id) row.prop(props, "mode", text="", icon_value=bonsai.bim.icons[ifc_icon].icon_id)
def object_menu(self, context): def object_menu(self, context):
@@ -411,15 +412,18 @@ class BIM_PT_mesh(Panel):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return ( return (
context.active_object is not None (obj := context.active_object) is not None
and context.active_object.type == "MESH" and (mesh := obj.data)
and hasattr(context.active_object.data, "BIMMeshProperties") and isinstance(mesh, bpy.types.Mesh)
and context.active_object.data.BIMMeshProperties.ifc_definition_id and tool.Geometry.get_mesh_props(mesh).ifc_definition_id
) )
def draw(self, context): def draw(self, context):
if not context.active_object.data: obj = context.active_object
return assert obj
mesh = obj.data
assert isinstance(mesh, bpy.types.Mesh)
row = self.layout.row() row = self.layout.row()
row.label(text="Advanced Users Only", icon="ERROR") row.label(text="Advanced Users Only", icon="ERROR")
@@ -427,7 +431,7 @@ class BIM_PT_mesh(Panel):
row = layout.row() row = layout.row()
text = "Manually Save Representation" text = "Manually Save Representation"
if tool.Ifc.is_edited(context.active_object): if tool.Ifc.is_edited(obj):
text += "*" text += "*"
row.operator("bim.update_representation", text=text) row.operator("bim.update_representation", text=text)
@@ -454,8 +458,8 @@ class BIM_PT_mesh(Panel):
op = row.operator("bim.update_representation", text="Convert To Arbitrary Extrusion With Voids") op = row.operator("bim.update_representation", text="Convert To Arbitrary Extrusion With Voids")
op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids" op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids"
if context.active_object and context.active_object.data: if True:
mprops = context.active_object.data.BIMMeshProperties mprops = tool.Geometry.get_mesh_props(mesh)
row = layout.row() row = layout.row()
row.operator("bim.get_representation_ifc_parameters") row.operator("bim.get_representation_ifc_parameters")
for index, ifc_parameter in enumerate(mprops.ifc_parameters): for index, ifc_parameter in enumerate(mprops.ifc_parameters):
@@ -477,7 +481,7 @@ class BIM_PT_placement(Panel):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return context.active_object and context.active_object.BIMObjectProperties.ifc_definition_id return (obj := context.active_object) and obj.BIMObjectProperties.ifc_definition_id
def draw(self, context): def draw(self, context):
if not PlacementData.is_loaded: if not PlacementData.is_loaded:
@@ -571,10 +575,10 @@ class BIM_PT_workarounds(Panel):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return ( return (
context.active_object is not None (obj := context.active_object) is not None
and context.active_object.type == "MESH" and (mesh := obj.data)
and hasattr(context.active_object.data, "BIMMeshProperties") and isinstance(mesh, bpy.types.Mesh)
and context.active_object.data.BIMMeshProperties.ifc_definition_id and tool.Geometry.get_mesh_props(mesh).ifc_definition_id
) )
def draw(self, context): def draw(self, context):
@@ -588,7 +592,7 @@ class BIM_PT_workarounds(Panel):
class BIM_UL_representation_items(UIList): class BIM_UL_representation_items(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname): def draw_item(self, context, layout: bpy.types.UILayout, data, item, icon, active_data, active_propname):
if item: if item:
icon = "MATERIAL" if item.surface_style else "MESH_UVSPHERE" icon = "MATERIAL" if item.surface_style else "MESH_UVSPHERE"
row = layout.row(align=True) row = layout.row(align=True)
@@ -167,7 +167,7 @@ class GeoreferenceData:
result["rotation"] = str(round(ifcopenshell.util.geolocation.yaxis2angle(*wcs[:, 1][:2]), 3)) result["rotation"] = str(round(ifcopenshell.util.geolocation.yaxis2angle(*wcs[:, 1][:2]), 3))
result["x"], result["y"], result["z"] = wcs[:, 3][:3] result["x"], result["y"], result["z"] = wcs[:, 3][:3]
props = bpy.context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
if props.has_blender_offset: if props.has_blender_offset:
blender_xyz = ifcopenshell.util.geolocation.enh2xyz( blender_xyz = ifcopenshell.util.geolocation.enh2xyz(
result["x"], result["x"],
@@ -193,7 +193,7 @@ class GeoreferenceData:
@classmethod @classmethod
def local_origin(cls): def local_origin(cls):
props = bpy.context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
if not props.has_blender_offset: if not props.has_blender_offset:
return return
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
@@ -21,6 +21,7 @@ import blf
import gpu import gpu
import bmesh import bmesh
import ifcopenshell import ifcopenshell
import ifcopenshell.util.geolocation
import bonsai.tool as tool import bonsai.tool as tool
from math import radians from math import radians
from bpy.types import SpaceView3D from bpy.types import SpaceView3D
@@ -53,7 +54,8 @@ class GeoreferenceDecorator:
cls.is_installed = False cls.is_installed = False
def draw_batch(self, shader_type, content_pos, color, indices=None): def draw_batch(self, shader_type, content_pos, color, indices=None):
self.scale = bpy.context.scene.BIMGeoreferenceProperties.visualization_scale props = tool.Georeference.get_georeference_props()
self.scale = props.visualization_scale
content_pos = [v * self.scale for v in content_pos] content_pos = [v * self.scale for v in content_pos]
shader = self.line_shader if shader_type == "LINES" else self.shader shader = self.line_shader if shader_type == "LINES" else self.shader
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices) batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
@@ -64,7 +66,7 @@ class GeoreferenceDecorator:
if not GeoreferenceData.is_loaded: if not GeoreferenceData.is_loaded:
GeoreferenceData.load() GeoreferenceData.load()
props = context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
if not props.model_origin: # If this is empty, no georeferencing data has been loaded. if not props.model_origin: # If this is empty, no georeferencing data has been loaded.
return return
@@ -177,7 +179,7 @@ class GeoreferenceDecorator:
if not GeoreferenceData.is_loaded: if not GeoreferenceData.is_loaded:
GeoreferenceData.load() GeoreferenceData.load()
props = context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
if not props.model_origin: # If this is empty, no georeferencing data has been loaded. if not props.model_origin: # If this is empty, no georeferencing data has been loaded.
return return
@@ -350,7 +352,7 @@ class GeoreferenceDecorator:
self.gn_angle = float(GeoreferenceData.data["map_derived_angle"] or 0) self.gn_angle = float(GeoreferenceData.data["map_derived_angle"] or 0)
self.tn_angle = float(GeoreferenceData.data["true_derived_angle"] or 0) self.tn_angle = float(GeoreferenceData.data["true_derived_angle"] or 0)
props = context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
if props.has_blender_offset: if props.has_blender_offset:
blender_angle = ifcopenshell.util.geolocation.xaxis2angle( blender_angle = ifcopenshell.util.geolocation.xaxis2angle(
float(props.blender_x_axis_abscissa), float(props.blender_x_axis_ordinate) float(props.blender_x_axis_abscissa), float(props.blender_x_axis_ordinate)
@@ -33,15 +33,18 @@ from bpy.props import (
) )
from bonsai.bim.module.georeference.data import GeoreferenceData from bonsai.bim.module.georeference.data import GeoreferenceData
from bonsai.bim.module.georeference.decorator import GeoreferenceDecorator from bonsai.bim.module.georeference.decorator import GeoreferenceDecorator
from typing import TYPE_CHECKING
def get_coordinate_operation_class(self, context): def get_coordinate_operation_class(
self: "BIMGeoreferenceProperties", context: bpy.types.Context
) -> list[tuple[str, str, str]]:
if not GeoreferenceData.is_loaded: if not GeoreferenceData.is_loaded:
GeoreferenceData.load() GeoreferenceData.load()
return GeoreferenceData.data["coordinate_operation_class"] return GeoreferenceData.data["coordinate_operation_class"]
def update_true_north_angle(self, context): def update_true_north_angle(self: "BIMGeoreferenceProperties", context: bpy.types.Context) -> None:
if self.is_changing_angle: if self.is_changing_angle:
return return
self.is_changing_angle = True self.is_changing_angle = True
@@ -54,7 +57,7 @@ def update_true_north_angle(self, context):
self.is_changing_angle = False self.is_changing_angle = False
def update_true_north_vector(self, context): def update_true_north_vector(self: "BIMGeoreferenceProperties", context: bpy.types.Context) -> None:
if self.is_changing_angle: if self.is_changing_angle:
return return
self.is_changing_angle = True self.is_changing_angle = True
@@ -67,7 +70,7 @@ def update_true_north_vector(self, context):
self.is_changing_angle = False self.is_changing_angle = False
def update_grid_north_angle(self, context): def update_grid_north_angle(self: "BIMGeoreferenceProperties", context: bpy.types.Context) -> None:
if self.is_changing_angle: if self.is_changing_angle:
return return
self.is_changing_angle = True self.is_changing_angle = True
@@ -81,7 +84,7 @@ def update_grid_north_angle(self, context):
self.is_changing_angle = False self.is_changing_angle = False
def update_grid_north_vector(self, context): def update_grid_north_vector(self: "BIMGeoreferenceProperties", context: bpy.types.Context) -> None:
if self.is_changing_angle: if self.is_changing_angle:
return return
self.is_changing_angle = True self.is_changing_angle = True
@@ -95,15 +98,15 @@ def update_grid_north_vector(self, context):
self.is_changing_angle = False self.is_changing_angle = False
def update_should_visualise(self, context): def update_should_visualise(self: "BIMGeoreferenceProperties", context: bpy.types.Context) -> None:
if self.should_visualise: if self.should_visualise:
GeoreferenceDecorator.install(bpy.context) GeoreferenceDecorator.install(bpy.context)
else: else:
GeoreferenceDecorator.uninstall() GeoreferenceDecorator.uninstall()
def update_blender_coordinates(self, context): def update_blender_coordinates(self: "BIMGeoreferenceProperties", context: bpy.types.Context) -> None:
props = bpy.context.scene.BIMGeoreferenceProperties props = self
if props.is_updating_coordinates: if props.is_updating_coordinates:
return return
props.is_updating_coordinates = True props.is_updating_coordinates = True
@@ -123,8 +126,8 @@ def update_blender_coordinates(self, context):
props.is_updating_coordinates = False props.is_updating_coordinates = False
def update_local_coordinates(self, context): def update_local_coordinates(self: "BIMGeoreferenceProperties", context: bpy.types.Context) -> None:
props = bpy.context.scene.BIMGeoreferenceProperties props = self
if props.is_updating_coordinates: if props.is_updating_coordinates:
return return
props.is_updating_coordinates = True props.is_updating_coordinates = True
@@ -147,8 +150,8 @@ def update_local_coordinates(self, context):
props.is_updating_coordinates = False props.is_updating_coordinates = False
def update_map_coordinates(self, context): def update_map_coordinates(self: "BIMGeoreferenceProperties", context: bpy.types.Context) -> None:
props = bpy.context.scene.BIMGeoreferenceProperties props = self
if props.is_updating_coordinates: if props.is_updating_coordinates:
return return
props.is_updating_coordinates = True props.is_updating_coordinates = True
@@ -248,3 +251,45 @@ class BIMGeoreferenceProperties(PropertyGroup):
wcs_y: StringProperty(name="WCS Y", default="0") wcs_y: StringProperty(name="WCS Y", default="0")
wcs_z: StringProperty(name="WCS Z", default="0") wcs_z: StringProperty(name="WCS Z", default="0")
wcs_rotation: StringProperty(name="WCS Rotation", default="0") wcs_rotation: StringProperty(name="WCS Rotation", default="0")
if TYPE_CHECKING:
coordinate_operation_class: str
is_changing_angle: bool
is_editing: bool
is_editing_wcs: bool
is_editing_true_north: bool
coordinate_operation: bpy.types.bpy_prop_collection_idprop[Attribute]
projected_crs: bpy.types.bpy_prop_collection_idprop[Attribute]
is_updating_coordinates: bool
blender_coordinates: str
local_coordinates: str
map_coordinates: str
should_visualise: bool
visualization_scale: float
grid_north_angle: str
x_axis_abscissa: str
x_axis_ordinate: str
x_axis_is_null: bool
host_model_origin: str
host_model_origin_si: str
host_model_project_north: str
model_origin: str
model_origin_si: str
model_project_north: str
has_blender_offset: bool
blender_offset_x: str
blender_offset_y: str
blender_offset_z: str
blender_x_axis_abscissa: str
blender_x_axis_ordinate: str
true_north_angle: str
true_north_abscissa: str
true_north_ordinate: str
wcs_x: str
wcs_y: str
wcs_z: str
wcs_rotation: str
@@ -32,7 +32,7 @@ class BIM_PT_gis(Panel):
bl_parent_id = "BIM_PT_tab_geometry" bl_parent_id = "BIM_PT_tab_geometry"
def draw_header(self, context): def draw_header(self, context):
props = context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
row = self.layout.row(align=True) row = self.layout.row(align=True)
icon = "HIDE_OFF" if props.should_visualise else "HIDE_ON" icon = "HIDE_OFF" if props.should_visualise else "HIDE_ON"
row.label(text="") # empty text occupies the left of the row row.label(text="") # empty text occupies the left of the row
@@ -43,7 +43,7 @@ class BIM_PT_gis(Panel):
def draw(self, context): def draw(self, context):
self.layout.use_property_split = True self.layout.use_property_split = True
self.layout.use_property_decorate = False self.layout.use_property_decorate = False
props = context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
if not GeoreferenceData.is_loaded: if not GeoreferenceData.is_loaded:
GeoreferenceData.load() GeoreferenceData.load()
@@ -54,7 +54,7 @@ class BIM_PT_gis(Panel):
self.draw_ui(context) self.draw_ui(context)
def draw_editable_ui(self, context): def draw_editable_ui(self, context):
props = context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text="Projected CRS", icon="WORLD") row.label(text="Projected CRS", icon="WORLD")
row.operator("bim.edit_georeferencing", icon="CHECKMARK", text="") row.operator("bim.edit_georeferencing", icon="CHECKMARK", text="")
@@ -87,7 +87,7 @@ class BIM_PT_gis(Panel):
draw_attribute(attribute, self.layout.row()) draw_attribute(attribute, self.layout.row())
def draw_ui(self, context): def draw_ui(self, context):
props = context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
if tool.Ifc.get_schema() == "IFC2X3": if tool.Ifc.get_schema() == "IFC2X3":
row = self.layout.row() row = self.layout.row()
@@ -149,7 +149,7 @@ class BIM_PT_gis_true_north(Panel):
if not GeoreferenceData.is_loaded: if not GeoreferenceData.is_loaded:
GeoreferenceData.load() GeoreferenceData.load()
self.props = context.scene.BIMGeoreferenceProperties self.props = tool.Georeference.get_georeference_props()
if self.props.is_editing_true_north: if self.props.is_editing_true_north:
self.draw_editable_ui(context) self.draw_editable_ui(context)
@@ -200,7 +200,7 @@ class BIM_PT_gis_blender(Panel):
if not GeoreferenceData.is_loaded: if not GeoreferenceData.is_loaded:
GeoreferenceData.load() GeoreferenceData.load()
props = context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
if props.has_blender_offset: if props.has_blender_offset:
row = self.layout.row() row = self.layout.row()
@@ -233,7 +233,7 @@ class BIM_PT_gis_wcs(Panel):
if not GeoreferenceData.is_loaded: if not GeoreferenceData.is_loaded:
GeoreferenceData.load() GeoreferenceData.load()
props = context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
if props.is_editing_wcs: if props.is_editing_wcs:
self.draw_editable_ui(context) self.draw_editable_ui(context)
@@ -263,7 +263,7 @@ class BIM_PT_gis_wcs(Panel):
row.operator("bim.enable_editing_wcs", icon="GREASEPENCIL", text="") row.operator("bim.enable_editing_wcs", icon="GREASEPENCIL", text="")
def draw_editable_ui(self, context): def draw_editable_ui(self, context):
props = context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text="World Coordinate System", icon="EMPTY_ARROWS") row.label(text="World Coordinate System", icon="EMPTY_ARROWS")
@@ -293,7 +293,7 @@ class BIM_PT_gis_calculator(Panel):
if not GeoreferenceData.is_loaded: if not GeoreferenceData.is_loaded:
GeoreferenceData.load() GeoreferenceData.load()
props = context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
if props.has_blender_offset: if props.has_blender_offset:
row = self.layout.row(align=True) row = self.layout.row(align=True)
+5 -10
View File
@@ -147,7 +147,7 @@ class AssignPresentationLayer(bpy.types.Operator, tool.Ifc.Operator):
"layer.assign_layer", "layer.assign_layer",
self.file, self.file,
**{ **{
"items": [self.file.by_id(item.BIMMeshProperties.ifc_definition_id)], "items": [self.file.by_id(tool.Geometry.get_mesh_props(item).ifc_definition_id)],
"layer": self.file.by_id(self.layer), "layer": self.file.by_id(self.layer),
}, },
) )
@@ -164,15 +164,10 @@ class UnassignPresentationLayer(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context): def _execute(self, context):
item = bpy.data.meshes.get(self.item) if self.item else context.active_object.data item = bpy.data.meshes.get(self.item) if self.item else context.active_object.data
self.file = IfcStore.get_file() ifc_file = tool.Ifc.get()
ifcopenshell.api.run( representation = tool.Geometry.get_data_representation(item)
"layer.unassign_layer", assert representation
self.file, ifcopenshell.api.layer.unassign_layer(ifc_file, items=[representation], layer=ifc_file.by_id(self.layer))
**{
"items": [self.file.by_id(item.BIMMeshProperties.ifc_definition_id)],
"layer": self.file.by_id(self.layer),
},
)
return {"FINISHED"} return {"FINISHED"}
-1
View File
@@ -77,7 +77,6 @@ class BIM_UL_layers(UIList):
row.label(text=item.name) row.label(text=item.name)
if context.active_object and isinstance(context.active_object.data, Mesh): if context.active_object and isinstance(context.active_object.data, Mesh):
mprops = context.active_object.data.BIMMeshProperties
if item.ifc_definition_id in LayersData.data["active_layers"]: if item.ifc_definition_id in LayersData.data["active_layers"]:
op = row.operator("bim.unassign_presentation_layer", text="", icon="KEYFRAME_HLT", emboss=False) op = row.operator("bim.unassign_presentation_layer", text="", icon="KEYFRAME_HLT", emboss=False)
op.layer = item.ifc_definition_id op.layer = item.ifc_definition_id
@@ -282,8 +282,9 @@ class ObjectMaterialData:
if item.is_a("IfcMaterialLayer"): if item.is_a("IfcMaterialLayer"):
total_thickness = item.LayerThickness total_thickness = item.LayerThickness
unit_system = bpy.context.scene.unit_settings.system unit_system = bpy.context.scene.unit_settings.system
props = tool.Drawing.get_document_props()
if unit_system == "IMPERIAL": if unit_system == "IMPERIAL":
precision = bpy.context.scene.DocProperties.imperial_precision precision = props.imperial_precision
else: else:
precision = None precision = None
formatted_thickness = format_distance( formatted_thickness = format_distance(
+2 -1
View File
@@ -351,9 +351,10 @@ class BIM_PT_object_material(Panel):
if ObjectMaterialData.data["total_thickness"]: if ObjectMaterialData.data["total_thickness"]:
total_thickness = ObjectMaterialData.data["total_thickness"] total_thickness = ObjectMaterialData.data["total_thickness"]
unit_system = bpy.context.scene.unit_settings.system unit_system = bpy.context.scene.unit_settings.system
props = tool.Drawing.get_document_props()
if unit_system == "IMPERIAL": if unit_system == "IMPERIAL":
precision = bpy.context.scene.DocProperties.imperial_precision precision = props.imperial_precision
else: else:
precision = None precision = None
formatted_thickness = format_distance( formatted_thickness = format_distance(
@@ -299,7 +299,7 @@ class DrawSystemArrows(bpy.types.Operator, tool.Ifc.Operator):
tool.Blender.select_and_activate_single_object(context, curve) tool.Blender.select_and_activate_single_object(context, curve)
def get_absolute_matrix(self, matrix): def get_absolute_matrix(self, matrix):
props = bpy.context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
if props.has_blender_offset: if props.has_blender_offset:
matrix = np.array( matrix = np.array(
ifcopenshell.util.geolocation.global2local( ifcopenshell.util.geolocation.global2local(
+19 -14
View File
@@ -236,9 +236,9 @@ class FilledOpeningGenerator:
voided_elements = ifcopenshell.util.element.get_parts(voided_element) or [voided_element] voided_elements = ifcopenshell.util.element.get_parts(voided_element) or [voided_element]
for voided_element in voided_elements: for voided_element in voided_elements:
voided_obj = tool.Ifc.get_object(voided_element) voided_obj = tool.Ifc.get_object(voided_element)
if not voided_obj.data: representation = tool.Geometry.get_active_representation(voided_obj)
if not representation:
continue continue
representation = tool.Ifc.get().by_id(voided_obj.data.BIMMeshProperties.ifc_definition_id)
bonsai.core.geometry.switch_representation( bonsai.core.geometry.switch_representation(
tool.Ifc, tool.Ifc,
tool.Geometry, tool.Geometry,
@@ -465,10 +465,13 @@ class AddBoolean(Operator, tool.Ifc.Operator):
self.report({"INFO"}, "At least two representation items must be selected to add a boolean.") self.report({"INFO"}, "At least two representation items must be selected to add a boolean.")
return {"CANCELLED"} return {"CANCELLED"}
props = context.scene.BIMBooleanProperties props = tool.Feature.get_boolean_props()
first_item = tool.Ifc.get().by_id(first_obj.data.BIMMeshProperties.ifc_definition_id) first_item = tool.Geometry.get_active_representation(first_obj)
second_items = [tool.Ifc.get().by_id(o.data.BIMMeshProperties.ifc_definition_id) for o in second_objs] assert first_item
second_items = [
representation for o in second_objs if (representation := tool.Geometry.get_active_representation(o))
]
booleans = ifcopenshell.api.geometry.add_boolean(tool.Ifc.get(), first_item, second_items, props.operator) booleans = ifcopenshell.api.geometry.add_boolean(tool.Ifc.get(), first_item, second_items, props.operator)
rep_obj = tool.Geometry.get_geometry_props().representation_obj rep_obj = tool.Geometry.get_geometry_props().representation_obj
@@ -664,6 +667,7 @@ class EditOpenings(Operator, tool.Ifc.Operator):
def edit_openings( def edit_openings(
self, building_objs: set[bpy.types.Object], opening_elements: set[ifcopenshell.entity_instance] self, building_objs: set[bpy.types.Object], opening_elements: set[ifcopenshell.entity_instance]
) -> None: ) -> None:
props = tool.Geometry.get_geometry_props()
objects_to_remove: set[bpy.types.Object] = set() objects_to_remove: set[bpy.types.Object] = set()
for opening_element in opening_elements: for opening_element in opening_elements:
opening_obj = tool.Ifc.get_object(opening_element) opening_obj = tool.Ifc.get_object(opening_element)
@@ -690,8 +694,8 @@ class EditOpenings(Operator, tool.Ifc.Operator):
self.get_all_building_objects_of_similar_openings(opening_element) self.get_all_building_objects_of_similar_openings(opening_element)
) # NB this has nothing to do with clone similar_opening ) # NB this has nothing to do with clone similar_opening
tool.Ifc.unlink(element=opening_element) tool.Ifc.unlink(element=opening_element)
if bpy.context.scene.BIMGeometryProperties.representation_obj == opening_obj: if props.representation_obj == opening_obj:
bpy.context.scene.BIMGeometryProperties.representation_obj = None props.representation_obj = None
objects_to_remove.add(opening_obj) objects_to_remove.add(opening_obj)
tool.Blender.remove_data_blocks(objects_to_remove, remove_unused_data=True) tool.Blender.remove_data_blocks(objects_to_remove, remove_unused_data=True)
@@ -817,11 +821,11 @@ class RemoveBoolean(Operator, tool.Ifc.Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
props = context.scene.BIMBooleanProperties props = tool.Feature.get_boolean_props()
return props.active_boolean return props.active_boolean
def _execute(self, context): def _execute(self, context):
props = context.scene.BIMBooleanProperties props = tool.Feature.get_boolean_props()
ifcopenshell.api.geometry.remove_boolean( ifcopenshell.api.geometry.remove_boolean(
tool.Ifc.get(), tool.Ifc.get().by_id(props.active_boolean.ifc_definition_id) tool.Ifc.get(), tool.Ifc.get().by_id(props.active_boolean.ifc_definition_id)
) )
@@ -841,7 +845,7 @@ class SelectBoolean(Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
props = context.scene.BIMBooleanProperties props = tool.Feature.get_boolean_props()
return props.active_boolean return props.active_boolean
def invoke(self, context, event): def invoke(self, context, event):
@@ -850,9 +854,9 @@ class SelectBoolean(Operator):
return self.execute(context) return self.execute(context)
def execute(self, context): def execute(self, context):
props = context.scene.BIMBooleanProperties props = tool.Feature.get_boolean_props()
queue = [tool.Ifc.get().by_id(props.active_boolean.ifc_definition_id)] queue = [tool.Ifc.get().by_id(props.active_boolean.ifc_definition_id)]
items = {i.ifc_definition_id: i.obj for i in context.scene.BIMGeometryProperties.item_objs} items = {i.ifc_definition_id: i.obj for i in tool.Geometry.get_geometry_props().item_objs}
while queue: while queue:
item = queue.pop() item = queue.pop()
if item.is_a("IfcBooleanResult"): if item.is_a("IfcBooleanResult"):
@@ -911,11 +915,12 @@ class DecorationsHandler:
gpu.state.point_size_set(6) gpu.state.point_size_set(6)
gpu.state.blend_set("ALPHA") gpu.state.blend_set("ALPHA")
gprops = tool.Geometry.get_geometry_props()
for opening in props.openings: for opening in props.openings:
obj = opening.obj obj = opening.obj
if context.scene.BIMGeometryProperties.representation_obj == obj: if gprops.representation_obj == obj:
# We are editing the representation of the opening : # We are editing the representation of the opening :
for item in context.scene.BIMGeometryProperties.item_objs: for item in gprops.item_objs:
if item.obj.mode == "EDIT": if item.obj.mode == "EDIT":
obj = item.obj obj = item.obj
break break
@@ -22,6 +22,7 @@ import bmesh
import mathutils.geometry import mathutils.geometry
import ifcopenshell import ifcopenshell
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.api.geometry
import ifcopenshell.util.type import ifcopenshell.util.type
import ifcopenshell.util.unit import ifcopenshell.util.unit
import ifcopenshell.util.element import ifcopenshell.util.element
@@ -265,7 +266,7 @@ class DumbProfileRegenerator:
def _regenerate_from_type(self, related_object: ifcopenshell.entity_instance) -> None: def _regenerate_from_type(self, related_object: ifcopenshell.entity_instance) -> None:
obj = tool.Ifc.get_object(related_object) obj = tool.Ifc.get_object(related_object)
if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id: if not obj or not tool.Geometry.get_active_representation(obj):
return return
DumbProfileRecalculator().recalculate([obj]) DumbProfileRecalculator().recalculate([obj])
@@ -501,7 +502,9 @@ class DumbProfileJoiner:
"geometry.assign_representation", tool.Ifc.get(), product=element, representation=new_axis "geometry.assign_representation", tool.Ifc.get(), product=element, representation=new_axis
) )
def get_placement_axes(body_representation): def get_placement_axes(
body_representation: Union[ifcopenshell.entity_instance, None],
) -> Union[tuple[tuple[float, float, float], tuple[float, float, float]], tuple[None, None]]:
if not body_representation: if not body_representation:
return None, None return None, None
extrusion = tool.Model.get_extrusion(body_representation) extrusion = tool.Model.get_extrusion(body_representation)
@@ -513,8 +516,7 @@ class DumbProfileJoiner:
return ((0.0, 0.0, 1.0), (1.0, 0.0, 0.0)) return ((0.0, 0.0, 1.0), (1.0, 0.0, 0.0))
old_body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW") old_body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
new_body = ifcopenshell.api.run( new_body = ifcopenshell.api.geometry.add_profile_representation(
"geometry.add_profile_representation",
tool.Ifc.get(), tool.Ifc.get(),
context=self.body_context, context=self.body_context,
profile=self.profile, profile=self.profile,
@@ -527,8 +529,9 @@ class DumbProfileJoiner:
if old_body: if old_body:
for inverse in tool.Ifc.get().get_inverse(old_body): for inverse in tool.Ifc.get().get_inverse(old_body):
ifcopenshell.util.element.replace_attribute(inverse, old_body, new_body) ifcopenshell.util.element.replace_attribute(inverse, old_body, new_body)
obj.data.BIMMeshProperties.ifc_definition_id = int(new_body.id()) assert isinstance(mesh := obj.data, bpy.types.Mesh)
obj.data.name = f"{self.body_context.id()}/{new_body.id()}" tool.Ifc.link(new_body, mesh)
mesh.name = tool.Loader.get_mesh_name(new_body)
bonsai.core.geometry.remove_representation(tool.Ifc, tool.Geometry, obj=obj, representation=old_body) bonsai.core.geometry.remove_representation(tool.Ifc, tool.Geometry, obj=obj, representation=old_body)
else: else:
ifcopenshell.api.run( ifcopenshell.api.run(
+1 -1
View File
@@ -259,7 +259,7 @@ class DumbSlabPlaner:
self, related_object: ifcopenshell.entity_instance, layer_set_direction: Optional[str], new_thickness: float self, related_object: ifcopenshell.entity_instance, layer_set_direction: Optional[str], new_thickness: float
) -> None: ) -> None:
obj = tool.Ifc.get_object(related_object) obj = tool.Ifc.get_object(related_object)
if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id: if not obj or not tool.Geometry.get_active_representation(obj):
return return
material = ifcopenshell.util.element.get_material(related_object) material = ifcopenshell.util.element.get_material(related_object)
+12 -6
View File
@@ -908,7 +908,7 @@ class DumbWallPlaner:
self, related_object: ifcopenshell.entity_instance, layer_set_direction: Optional[str] self, related_object: ifcopenshell.entity_instance, layer_set_direction: Optional[str]
) -> None: ) -> None:
obj = tool.Ifc.get_object(related_object) obj = tool.Ifc.get_object(related_object)
if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id: if not obj or not tool.Geometry.get_active_representation(obj):
return return
material = ifcopenshell.util.element.get_material(related_object) material = ifcopenshell.util.element.get_material(related_object)
@@ -1331,7 +1331,9 @@ class DumbWallJoiner:
axis = body = tool.Model.get_wall_axis(obj)["reference"] axis = body = tool.Model.get_wall_axis(obj)["reference"]
self.axis = copy.deepcopy(axis) self.axis = copy.deepcopy(axis)
self.body = copy.deepcopy(body) self.body = copy.deepcopy(body)
extrusion_data = self.get_extrusion_data(tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)) representation = tool.Geometry.get_active_representation(obj)
assert representation
extrusion_data = self.get_extrusion_data(representation)
height = extrusion_data["height"] height = extrusion_data["height"]
x_angle = extrusion_data["x_angle"] x_angle = extrusion_data["x_angle"]
self.clippings = [] self.clippings = []
@@ -1410,8 +1412,9 @@ class DumbWallJoiner:
if old_body: if old_body:
for inverse in tool.Ifc.get().get_inverse(old_body): for inverse in tool.Ifc.get().get_inverse(old_body):
ifcopenshell.util.element.replace_attribute(inverse, old_body, new_body) ifcopenshell.util.element.replace_attribute(inverse, old_body, new_body)
obj.data.BIMMeshProperties.ifc_definition_id = int(new_body.id()) assert isinstance(mesh := obj.data, bpy.types.Mesh)
obj.data.name = f"{self.body_context.id()}/{new_body.id()}" tool.Ifc.link(new_body, mesh)
mesh.name = tool.Loader.get_mesh_name(new_body)
bonsai.core.geometry.remove_representation(tool.Ifc, tool.Geometry, obj=obj, representation=old_body) bonsai.core.geometry.remove_representation(tool.Ifc, tool.Geometry, obj=obj, representation=old_body)
else: else:
ifcopenshell.api.run( ifcopenshell.api.run(
@@ -1708,10 +1711,11 @@ class DumbWallJoiner:
return True return True
def clip(self, wall1, slab2): def clip(self, wall1: bpy.types.Object, slab2: bpy.types.Object) -> float:
"""returns height of the clipped wall, adds clipping plane to `clippings`""" """returns height of the clipped wall, adds clipping plane to `clippings`"""
element1 = tool.Ifc.get_entity(wall1) element1 = tool.Ifc.get_entity(wall1)
element2 = tool.Ifc.get_entity(slab2) element2 = tool.Ifc.get_entity(slab2)
assert element1 and element2
layers1 = tool.Model.get_material_layer_parameters(element1) layers1 = tool.Model.get_material_layer_parameters(element1)
axis1 = tool.Model.get_wall_axis(wall1, layers1) axis1 = tool.Model.get_wall_axis(wall1, layers1)
@@ -1719,7 +1723,9 @@ class DumbWallJoiner:
bases = [axis1["base"][0].to_3d(), axis1["base"][1].to_3d(), axis1["side"][0].to_3d(), axis1["side"][1].to_3d()] bases = [axis1["base"][0].to_3d(), axis1["base"][1].to_3d(), axis1["side"][0].to_3d(), axis1["side"][1].to_3d()]
bases = [Vector((v[0], v[1], wall1.matrix_world.translation.z)) for v in bases] # add wall Z location bases = [Vector((v[0], v[1], wall1.matrix_world.translation.z)) for v in bases] # add wall Z location
extrusion = self.get_extrusion_data(tool.Ifc.get().by_id(wall1.data.BIMMeshProperties.ifc_definition_id)) representation = tool.Geometry.get_active_representation(wall1)
assert representation
extrusion = self.get_extrusion_data(representation)
wall_dir = wall1.matrix_world.to_quaternion() @ extrusion["direction"] wall_dir = wall1.matrix_world.to_quaternion() @ extrusion["direction"]
slab_pt = slab2.matrix_world @ Vector((0, 0, 0)) slab_pt = slab2.matrix_world @ Vector((0, 0, 0))
@@ -96,7 +96,8 @@ class BimTool(WorkSpaceTool):
def draw_settings( def draw_settings(
cls, context: bpy.types.Context, layout: bpy.types.UILayout, ws_tool: bpy.types.WorkSpaceTool cls, context: bpy.types.Context, layout: bpy.types.UILayout, ws_tool: bpy.types.WorkSpaceTool
) -> None: ) -> None:
if context.scene.BIMGeometryProperties.mode == "ITEM": props = tool.Geometry.get_geometry_props()
if props.mode == "ITEM":
EditItemUI.draw(context, layout) EditItemUI.draw(context, layout)
elif ( elif (
active_ifc_object := (context.active_object and tool.Ifc.get_entity(context.active_object)) active_ifc_object := (context.active_object and tool.Ifc.get_entity(context.active_object))
@@ -430,7 +431,7 @@ class EditItemUI:
obj = context.active_object obj = context.active_object
assert obj assert obj
mesh_props = obj.data.BIMMeshProperties mesh_props = tool.Geometry.get_mesh_props(obj.data)
if AuthoringData.data["is_representation_item_swept_solid"]: if AuthoringData.data["is_representation_item_swept_solid"]:
# TODO: support EndSweptArea for IfcRevolvedAreaSolidTapered, # TODO: support EndSweptArea for IfcRevolvedAreaSolidTapered,
# will need to add second attribute for this. # will need to add second attribute for this.
@@ -440,10 +441,10 @@ class EditItemUI:
op = row.operator("bim.name_profile", text="", icon="TAG") op = row.operator("bim.name_profile", text="", icon="TAG")
op.extrusion_item_obj = obj.name op.extrusion_item_obj = obj.name
for item_attribute in obj.data.BIMMeshProperties.item_attributes: for item_attribute in mesh_props.item_attributes:
row = cls.layout.row() row = cls.layout.row()
draw_attribute(item_attribute, cls.layout) draw_attribute(item_attribute, cls.layout)
if len(obj.data.BIMMeshProperties.item_attributes) or AuthoringData.data["is_representation_item_swept_solid"]: if len(mesh_props.item_attributes) or AuthoringData.data["is_representation_item_swept_solid"]:
row = cls.layout.row() row = cls.layout.row()
row.operator("bim.update_item_attributes", icon="FILE_REFRESH", text="") row.operator("bim.update_item_attributes", icon="FILE_REFRESH", text="")
@@ -1136,7 +1137,8 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
row.prop(self, "z") row.prop(self, "z")
def hotkey_S_A(self): def hotkey_S_A(self):
if bpy.context.scene.BIMGeometryProperties.mode == "ITEM": gprops = tool.Geometry.get_geometry_props()
if gprops.mode == "ITEM":
bpy.ops.wm.call_menu(name="BIM_MT_add_representation_item") bpy.ops.wm.call_menu(name="BIM_MT_add_representation_item")
return return
+3 -3
View File
@@ -94,7 +94,7 @@ class ProfileData:
obj = bpy.context.active_object obj = bpy.context.active_object
return ( return (
obj obj
and obj.data and (data := obj.data)
and hasattr(obj.data, "BIMMeshProperties") and isinstance(data, bpy.types.Mesh)
and obj.data.BIMMeshProperties.subshape_type == "PROFILE" and tool.Geometry.get_mesh_props(data).subshape_type == "PROFILE"
) )
@@ -248,7 +248,12 @@ class EnableEditingArbitraryProfile(bpy.types.Operator):
def disable_editing_arbitrary_profile(context): def disable_editing_arbitrary_profile(context):
obj = context.active_object obj = context.active_object
if obj and obj.type == "MESH" and obj.data and obj.data.BIMMeshProperties.subshape_type == "PROFILE": if (
obj
and (mesh := obj.data)
and isinstance(mesh, bpy.types.Mesh)
and tool.Geometry.get_mesh_props(mesh).subshape_type == "PROFILE"
):
ProfileDecorator.uninstall() ProfileDecorator.uninstall()
bpy.ops.object.mode_set(mode="OBJECT") bpy.ops.object.mode_set(mode="OBJECT")
profile_mesh = obj.data profile_mesh = obj.data
@@ -31,7 +31,8 @@ from typing import Union
@persistent @persistent
def toggle_decorations_on_load(*args): def toggle_decorations_on_load(*args):
if bpy.context.scene.BIMProjectProperties.clipping_planes: props = tool.Project.get_project_props()
if props.clipping_planes:
ClippingPlaneDecorator.install(bpy.context) ClippingPlaneDecorator.install(bpy.context)
else: else:
ClippingPlaneDecorator.uninstall() ClippingPlaneDecorator.uninstall()
@@ -99,7 +100,7 @@ class ProjectDecorator:
selected_edges = [] selected_edges = []
selected_tris = [] selected_tris = []
props = context.scene.BIMProjectProperties props = tool.Project.get_project_props()
try: try:
obj = props.queried_obj obj = props.queried_obj
selected_vertices = obj["selected_vertices"] selected_vertices = obj["selected_vertices"]
@@ -171,7 +172,8 @@ class ClippingPlaneDecorator:
unselected_edges = [] unselected_edges = []
unselected_tris = [] unselected_tris = []
for clipping_plane in context.scene.BIMProjectProperties.clipping_planes: props = tool.Project.get_project_props()
for clipping_plane in props.clipping_planes:
obj = clipping_plane.obj obj = clipping_plane.obj
if not obj or not obj.data: if not obj or not obj.data:
continue continue
@@ -18,6 +18,7 @@
import bpy import bpy
import bonsai.tool as tool
from bpy.types import GizmoGroup from bpy.types import GizmoGroup
from mathutils import Matrix from mathutils import Matrix
@@ -32,11 +33,12 @@ class ClippingPlane(GizmoGroup):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
obj = context.object obj = context.object
props = tool.Project.get_project_props()
return ( return (
context.selected_objects context.selected_objects
and obj and obj
and obj.name.startswith("ClippingPlane") and obj.name.startswith("ClippingPlane")
and obj in [sp.obj for sp in context.scene.BIMProjectProperties.clipping_planes] and obj in [sp.obj for sp in props.clipping_planes]
) )
def setup(self, context): def setup(self, context):
@@ -75,35 +75,36 @@ class NewProject(bpy.types.Operator):
def execute(self, context): def execute(self, context):
bpy.ops.wm.read_homefile() bpy.ops.wm.read_homefile()
pprops = tool.Project.get_project_props()
if self.preset == "metric_m": if self.preset == "metric_m":
bpy.context.scene.BIMProjectProperties.export_schema = "IFC4" pprops.export_schema = "IFC4"
bpy.context.scene.unit_settings.system = "METRIC" bpy.context.scene.unit_settings.system = "METRIC"
bpy.context.scene.unit_settings.length_unit = "METERS" bpy.context.scene.unit_settings.length_unit = "METERS"
bpy.context.scene.BIMProperties.area_unit = "SQUARE_METRE" bpy.context.scene.BIMProperties.area_unit = "SQUARE_METRE"
bpy.context.scene.BIMProperties.volume_unit = "CUBIC_METRE" bpy.context.scene.BIMProperties.volume_unit = "CUBIC_METRE"
bpy.context.scene.BIMProjectProperties.template_file = "0" pprops.template_file = "0"
elif self.preset == "metric_mm": elif self.preset == "metric_mm":
bpy.context.scene.BIMProjectProperties.export_schema = "IFC4" pprops.export_schema = "IFC4"
bpy.context.scene.unit_settings.system = "METRIC" bpy.context.scene.unit_settings.system = "METRIC"
bpy.context.scene.unit_settings.length_unit = "MILLIMETERS" bpy.context.scene.unit_settings.length_unit = "MILLIMETERS"
bpy.context.scene.BIMProperties.area_unit = "SQUARE_METRE" bpy.context.scene.BIMProperties.area_unit = "SQUARE_METRE"
bpy.context.scene.BIMProperties.volume_unit = "CUBIC_METRE" bpy.context.scene.BIMProperties.volume_unit = "CUBIC_METRE"
bpy.context.scene.BIMProjectProperties.template_file = "0" pprops.template_file = "0"
elif self.preset == "imperial_ft": elif self.preset == "imperial_ft":
bpy.context.scene.BIMProjectProperties.export_schema = "IFC4" pprops.export_schema = "IFC4"
bpy.context.scene.unit_settings.system = "IMPERIAL" bpy.context.scene.unit_settings.system = "IMPERIAL"
bpy.context.scene.unit_settings.length_unit = "FEET" bpy.context.scene.unit_settings.length_unit = "FEET"
bpy.context.scene.BIMProperties.area_unit = "square foot" bpy.context.scene.BIMProperties.area_unit = "square foot"
bpy.context.scene.BIMProperties.volume_unit = "cubic foot" bpy.context.scene.BIMProperties.volume_unit = "cubic foot"
bpy.context.scene.BIMProjectProperties.template_file = "0" pprops.template_file = "0"
elif self.preset == "demo": elif self.preset == "demo":
bpy.context.scene.BIMProjectProperties.export_schema = "IFC4" pprops.export_schema = "IFC4"
bpy.context.scene.unit_settings.system = "METRIC" bpy.context.scene.unit_settings.system = "METRIC"
bpy.context.scene.unit_settings.length_unit = "MILLIMETERS" bpy.context.scene.unit_settings.length_unit = "MILLIMETERS"
bpy.context.scene.BIMProperties.area_unit = "SQUARE_METRE" bpy.context.scene.BIMProperties.area_unit = "SQUARE_METRE"
bpy.context.scene.BIMProperties.volume_unit = "CUBIC_METRE" bpy.context.scene.BIMProperties.volume_unit = "CUBIC_METRE"
bpy.context.scene.BIMProjectProperties.template_file = "IFC4 Demo Template.ifc" pprops.template_file = "IFC4 Demo Template.ifc"
if self.preset != "wizard": if self.preset != "wizard":
bpy.ops.bim.create_project() bpy.ops.bim.create_project()
@@ -126,7 +127,7 @@ class CreateProject(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
def _execute(self, context): def _execute(self, context):
props = context.scene.BIMProjectProperties props = tool.Project.get_project_props()
template = None if props.template_file == "0" else props.template_file template = None if props.template_file == "0" else props.template_file
if tool.Blender.is_default_scene(): if tool.Blender.is_default_scene():
for obj in bpy.data.objects: for obj in bpy.data.objects:
@@ -592,7 +593,8 @@ class AppendLibraryElement(bpy.types.Operator, tool.Ifc.Operator):
self.import_presentation_style_from_ifc(element, context) self.import_presentation_style_from_ifc(element, context)
try: try:
context.scene.BIMProjectProperties.library_elements[self.prop_index].is_appended = True props = tool.Project.get_project_props()
props.library_elements[self.prop_index].is_appended = True
except: except:
# TODO Remove this terrible code when I refactor this into the core # TODO Remove this terrible code when I refactor this into the core
pass pass
@@ -771,8 +773,8 @@ class EnableEditingHeader(bpy.types.Operator):
return IfcStore.get_file() return IfcStore.get_file()
def execute(self, context): def execute(self, context):
self.file = IfcStore.get_file() self.file = tool.Ifc.get()
props = context.scene.BIMProjectProperties props = tool.Project.get_project_props()
props.is_editing = True props.is_editing = True
mvd = "".join(IfcStore.get_file().wrapped_data.header.file_description.description) mvd = "".join(IfcStore.get_file().wrapped_data.header.file_description.description)
@@ -818,8 +820,8 @@ class EditHeader(bpy.types.Operator):
return result return result
def _execute(self, context): def _execute(self, context):
self.file = IfcStore.get_file() self.file = tool.Ifc.get()
props = context.scene.BIMProjectProperties props = tool.Project.get_project_props()
props.is_editing = True props.is_editing = True
self.file.wrapped_data.header.file_description.description = (f"ViewDefinition[{props.mvd}]",) self.file.wrapped_data.header.file_description.description = (f"ViewDefinition[{props.mvd}]",)
@@ -860,7 +862,8 @@ class DisableEditingHeader(bpy.types.Operator):
bl_description = "Cancel unsaved header information" bl_description = "Cancel unsaved header information"
def execute(self, context): def execute(self, context):
context.scene.BIMProjectProperties.is_editing = False props = tool.Project.get_project_props()
props.is_editing = False
return {"FINISHED"} return {"FINISHED"}
@@ -983,9 +986,10 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
f"Error loading IFC file from filepath '{filepath}'. See logs above in the system console for the details.", f"Error loading IFC file from filepath '{filepath}'. See logs above in the system console for the details.",
) )
return {"CANCELLED"} return {"CANCELLED"}
context.scene.BIMProjectProperties.is_loading = True props = tool.Project.get_project_props()
context.scene.BIMProjectProperties.total_elements = len(tool.Ifc.get().by_type("IfcElement")) props.is_loading = True
context.scene.BIMProjectProperties.use_relative_project_path = self.use_relative_path props.total_elements = len(tool.Ifc.get().by_type("IfcElement"))
props.use_relative_project_path = self.use_relative_path
tool.Blender.register_toolbar() tool.Blender.register_toolbar()
tool.Project.add_recent_ifc_project(self.get_filepath_abs()) tool.Project.add_recent_ifc_project(self.get_filepath_abs())
@@ -1051,8 +1055,8 @@ class LoadProjectElements(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def execute(self, context): def execute(self, context):
self.props = context.scene.BIMProjectProperties self.props = tool.Project.get_project_props()
self.file = IfcStore.get_file() self.file = tool.Ifc.get()
bonsai.bim.schema.reload(self.file.schema_identifier) bonsai.bim.schema.reload(self.file.schema_identifier)
start = time.time() start = time.time()
logger = logging.getLogger("ImportIFC") logger = logging.getLogger("ImportIFC")
@@ -1084,7 +1088,8 @@ class LoadProjectElements(bpy.types.Operator):
ifc_importer.execute() ifc_importer.execute()
settings.logger.info("Import finished in {:.2f} seconds".format(time.time() - start)) settings.logger.info("Import finished in {:.2f} seconds".format(time.time() - start))
print("Import finished in {:.2f} seconds".format(time.time() - start)) print("Import finished in {:.2f} seconds".format(time.time() - start))
context.scene.BIMProjectProperties.is_loading = False props = tool.Project.get_project_props()
props.is_loading = False
tool.Project.load_pset_templates() tool.Project.load_pset_templates()
tool.Project.load_default_thumbnails() tool.Project.load_default_thumbnails()
@@ -1156,7 +1161,8 @@ class ToggleFilterCategories(bpy.types.Operator):
should_select: bpy.props.BoolProperty(name="Should Select", default=True) should_select: bpy.props.BoolProperty(name="Should Select", default=True)
def execute(self, context): def execute(self, context):
for filter_category in context.scene.BIMProjectProperties.filter_categories: props = tool.Project.get_project_props()
for filter_category in props.filter_categories:
filter_category.is_selected = self.should_select filter_category.is_selected = self.should_select
return {"FINISHED"} return {"FINISHED"}
@@ -1183,7 +1189,7 @@ class LinkIfc(bpy.types.Operator):
directory: str directory: str
def draw(self, context): def draw(self, context):
pprops = context.scene.BIMProjectProperties pprops = tool.Project.get_project_props()
row = self.layout.row() row = self.layout.row()
row.prop(self, "use_relative_path") row.prop(self, "use_relative_path")
row = self.layout.row() row = self.layout.row()
@@ -1204,7 +1210,8 @@ class LinkIfc(bpy.types.Operator):
if bpy.data.filepath and filepath.samefile(bpy.data.filepath): if bpy.data.filepath and filepath.samefile(bpy.data.filepath):
self.report({"INFO"}, "Can't link the current .blend file") self.report({"INFO"}, "Can't link the current .blend file")
continue continue
new = context.scene.BIMProjectProperties.links.add() props = tool.Project.get_project_props()
new = props.links.add()
filepath = tool.Ifc.get_uri(filepath, use_relative_path=self.use_relative_path) filepath = tool.Ifc.get_uri(filepath, use_relative_path=self.use_relative_path)
new.name = filepath new.name = filepath
status = bpy.ops.bim.load_link(filepath=filepath, use_cache=self.use_cache) status = bpy.ops.bim.load_link(filepath=filepath, use_cache=self.use_cache)
@@ -1235,9 +1242,10 @@ class UnlinkIfc(bpy.types.Operator):
def execute(self, context): def execute(self, context):
filepath = Path(self.filepath).as_posix() filepath = Path(self.filepath).as_posix()
bpy.ops.bim.unload_link(filepath=filepath) bpy.ops.bim.unload_link(filepath=filepath)
index = context.scene.BIMProjectProperties.links.find(filepath) props = tool.Project.get_project_props()
index = props.links.find(filepath)
if index != -1: if index != -1:
context.scene.BIMProjectProperties.links.remove(index) props.links.remove(index)
return {"FINISHED"} return {"FINISHED"}
@@ -1257,8 +1265,9 @@ class UnloadLink(bpy.types.Operator):
if tool.Blender.ensure_blender_path_is_abs(Path(library.filepath)) == filepath: if tool.Blender.ensure_blender_path_is_abs(Path(library.filepath)) == filepath:
bpy.data.libraries.remove(library) bpy.data.libraries.remove(library)
links = context.scene.BIMProjectProperties.links props = tool.Project.get_project_props()
link = links.get(self.filepath) links = props.links
link = links[self.filepath]
# Let's assume that user might delete it. # Let's assume that user might delete it.
if empty_handle := link.empty_handle: if empty_handle := link.empty_handle:
bpy.data.objects.remove(empty_handle) bpy.data.objects.remove(empty_handle)
@@ -1267,7 +1276,7 @@ class UnloadLink(bpy.types.Operator):
if not any([l.is_loaded for l in links]): if not any([l.is_loaded for l in links]):
ProjectDecorator.uninstall() ProjectDecorator.uninstall()
# we make sure we don't draw queried object from the file that was just unlinked # we make sure we don't draw queried object from the file that was just unlinked
elif queried_obj := context.scene.BIMProjectProperties.queried_obj: elif queried_obj := props.queried_obj:
queried_filepath = Path(queried_obj["ifc_filepath"]) queried_filepath = Path(queried_obj["ifc_filepath"])
if queried_filepath == filepath: if queried_filepath == filepath:
ProjectDecorator.uninstall() ProjectDecorator.uninstall()
@@ -1299,7 +1308,7 @@ class LoadLink(bpy.types.Operator):
def link_blend(self, filepath: Path) -> None: def link_blend(self, filepath: Path) -> None:
with bpy.data.libraries.load(str(filepath), link=True) as (data_from, data_to): with bpy.data.libraries.load(str(filepath), link=True) as (data_from, data_to):
data_to.scenes = data_from.scenes data_to.scenes = data_from.scenes
link = bpy.context.scene.BIMProjectProperties.links[self.filepath] link = tool.Project.get_project_props().links[self.filepath]
for scene in bpy.data.scenes: for scene in bpy.data.scenes:
if not scene.library or Path(scene.library.filepath) != filepath: if not scene.library or Path(scene.library.filepath) != filepath:
continue continue
@@ -1325,13 +1334,13 @@ class LoadLink(bpy.types.Operator):
if not blend_filepath.exists(): if not blend_filepath.exists():
pprops = tool.Project.get_project_props() pprops = tool.Project.get_project_props()
gprops = bpy.context.scene.BIMGeoreferenceProperties gprops = tool.Georeference.get_georeference_props()
code = f""" code = f"""
import bpy import bpy
def run(): def run():
gprops = bpy.context.scene.BIMGeoreferenceProperties gprops = tool.Georeference.get_georeference_props()
# Our model origin becomes their host model origin # Our model origin becomes their host model origin
gprops.host_model_origin = "{gprops.model_origin}" gprops.host_model_origin = "{gprops.model_origin}"
gprops.host_model_origin_si = "{gprops.model_origin_si}" gprops.host_model_origin_si = "{gprops.model_origin_si}"
@@ -1342,7 +1351,7 @@ def run():
gprops.blender_offset_z = "{gprops.blender_offset_z}" gprops.blender_offset_z = "{gprops.blender_offset_z}"
gprops.blender_x_axis_abscissa = "{gprops.blender_x_axis_abscissa}" gprops.blender_x_axis_abscissa = "{gprops.blender_x_axis_abscissa}"
gprops.blender_x_axis_ordinate = "{gprops.blender_x_axis_ordinate}" gprops.blender_x_axis_ordinate = "{gprops.blender_x_axis_ordinate}"
pprops = bpy.context.scene.BIMProjectProperties pprops = tool.Project.get_project_props()
pprops.distance_limit = {pprops.distance_limit} pprops.distance_limit = {pprops.distance_limit}
pprops.false_origin_mode = "{pprops.false_origin_mode}" pprops.false_origin_mode = "{pprops.false_origin_mode}"
pprops.false_origin = "{pprops.false_origin}" pprops.false_origin = "{pprops.false_origin}"
@@ -1397,7 +1406,7 @@ except Exception as e:
with open(json_filepath, "r") as f: with open(json_filepath, "r") as f:
data = json.load(f) data = json.load(f)
gprops = bpy.context.scene.BIMGeoreferenceProperties gprops = tool.Georeference.get_georeference_props()
for prop in ("model_origin", "model_origin_si", "model_project_north"): for prop in ("model_origin", "model_origin_si", "model_project_north"):
if (value := data.get(prop, None)) is not None: if (value := data.get(prop, None)) is not None:
setattr(gprops, prop, value) setattr(gprops, prop, value)
@@ -1433,8 +1442,8 @@ class ToggleLinkSelectability(bpy.types.Operator):
link: bpy.props.StringProperty(name="Linked IFC Filepath") link: bpy.props.StringProperty(name="Linked IFC Filepath")
def execute(self, context): def execute(self, context):
props = context.scene.BIMProjectProperties props = tool.Project.get_project_props()
link = props.links.get(self.link) link = props.links[self.link]
self.library_filepath = tool.Blender.ensure_blender_path_is_abs(Path(self.link).with_suffix(".ifc.cache.blend")) self.library_filepath = tool.Blender.ensure_blender_path_is_abs(Path(self.link).with_suffix(".ifc.cache.blend"))
link.is_selectable = (is_selectable := not link.is_selectable) link.is_selectable = (is_selectable := not link.is_selectable)
for collection in self.get_linked_collections(): for collection in self.get_linked_collections():
@@ -1460,8 +1469,8 @@ class ToggleLinkVisibility(bpy.types.Operator):
mode: bpy.props.EnumProperty(name="Visibility Mode", items=((i, i, "") for i in ("WIREFRAME", "VISIBLE"))) mode: bpy.props.EnumProperty(name="Visibility Mode", items=((i, i, "") for i in ("WIREFRAME", "VISIBLE")))
def execute(self, context): def execute(self, context):
props = context.scene.BIMProjectProperties props = tool.Project.get_project_props()
link = props.links.get(self.link) link = props.links[self.link]
self.library_filepath = tool.Blender.ensure_blender_path_is_abs(Path(self.link).with_suffix(".ifc.cache.blend")) self.library_filepath = tool.Blender.ensure_blender_path_is_abs(Path(self.link).with_suffix(".ifc.cache.blend"))
if self.mode == "WIREFRAME": if self.mode == "WIREFRAME":
self.toggle_wireframe(link) self.toggle_wireframe(link)
@@ -1507,7 +1516,7 @@ class SelectLinkHandle(bpy.types.Operator):
index: bpy.props.IntProperty(name="Link Index") index: bpy.props.IntProperty(name="Link Index")
def execute(self, context): def execute(self, context):
props = context.scene.BIMProjectProperties props = tool.Project.get_project_props()
link = props.links[self.index] link = props.links[self.index]
handle = link.empty_handle handle = link.empty_handle
if not handle: if not handle:
@@ -1549,7 +1558,7 @@ class ExportIFC(bpy.types.Operator):
bpy.ops.wm.save_mainfile("INVOKE_DEFAULT") bpy.ops.wm.save_mainfile("INVOKE_DEFAULT")
return {"FINISHED"} return {"FINISHED"}
self.use_relative_path = context.scene.BIMProjectProperties.use_relative_project_path self.use_relative_path = tool.Project.get_project_props().use_relative_project_path
if (filepath := context.scene.BIMProperties.ifc_file) and not self.should_save_as: if (filepath := context.scene.BIMProperties.ifc_file) and not self.should_save_as:
self.filepath = str(tool.Blender.ensure_blender_path_is_abs(Path(filepath))) self.filepath = str(tool.Blender.ensure_blender_path_is_abs(Path(filepath)))
return self.execute(context) return self.execute(context)
@@ -1564,7 +1573,7 @@ class ExportIFC(bpy.types.Operator):
return {"RUNNING_MODAL"} return {"RUNNING_MODAL"}
def execute(self, context): def execute(self, context):
project_props = context.scene.BIMProjectProperties project_props = tool.Project.get_project_props()
project_props.use_relative_project_path = self.use_relative_path project_props.use_relative_project_path = self.use_relative_path
if project_props.should_disable_undo_on_save: if project_props.should_disable_undo_on_save:
old_history_size = tool.Ifc.get().history_size old_history_size = tool.Ifc.get().history_size
@@ -1612,10 +1621,12 @@ class ExportIFC(bpy.types.Operator):
# New project created in Bonsai should be in recent projects too. # New project created in Bonsai should be in recent projects too.
tool.Project.add_recent_ifc_project(Path(output_file)) tool.Project.add_recent_ifc_project(Path(output_file))
scene = context.scene scene = context.scene
if not scene.DocProperties.ifc_files: props = tool.Drawing.get_document_props()
new = scene.DocProperties.ifc_files.add() if not props.ifc_files:
new = props.ifc_files.add()
new.name = output_file new.name = output_file
if context.scene.BIMProjectProperties.use_relative_project_path and bpy.data.is_saved: props = tool.Project.get_project_props()
if props.use_relative_project_path and bpy.data.is_saved:
output_file = os.path.relpath(output_file, bpy.path.abspath("//")) output_file = os.path.relpath(output_file, bpy.path.abspath("//"))
if scene.BIMProperties.ifc_file != output_file and extension not in ("ifczip", "ifcjson"): if scene.BIMProperties.ifc_file != output_file and extension not in ("ifczip", "ifcjson"):
scene.BIMProperties.ifc_file = output_file scene.BIMProperties.ifc_file = output_file
@@ -1660,8 +1671,8 @@ class LoadLinkedProject(bpy.types.Operator):
start = time.time() start = time.time()
pprops = bpy.context.scene.BIMProjectProperties pprops = tool.Project.get_project_props()
gprops = bpy.context.scene.BIMGeoreferenceProperties gprops = tool.Georeference.get_georeference_props()
self.filepath = Path(self.filepath).as_posix() self.filepath = Path(self.filepath).as_posix()
print("Processing", self.filepath) print("Processing", self.filepath)
@@ -1875,7 +1886,7 @@ class LoadLinkedProject(bpy.types.Operator):
mesh = bpy.data.meshes.new("Mesh") mesh = bpy.data.meshes.new("Mesh")
geometry = shape.geometry geometry = shape.geometry
gprops = bpy.context.scene.BIMGeoreferenceProperties gprops = tool.Georeference.get_georeference_props()
if ( if (
gprops.has_blender_offset gprops.has_blender_offset
and geometry.verts and geometry.verts
@@ -1981,9 +1992,8 @@ class QueryLinkedElement(bpy.types.Operator):
from bpy_extras.view3d_utils import region_2d_to_vector_3d, region_2d_to_origin_3d from bpy_extras.view3d_utils import region_2d_to_vector_3d, region_2d_to_origin_3d
LinksData.linked_data = {} LinksData.linked_data = {}
props = context.scene.BIMProjectProperties props = tool.Project.get_project_props()
props.queried_obj = None props.queried_obj = None
props.quried_obj_root = None
for area in bpy.context.screen.areas: for area in bpy.context.screen.areas:
if area.type == "PROPERTIES": if area.type == "PROPERTIES":
@@ -2119,6 +2129,7 @@ class AppendInspectedLinkedElement(AppendLibraryElement):
def _execute(self, context): def _execute(self, context):
from bonsai.bim.module.project.data import LinksData from bonsai.bim.module.project.data import LinksData
props = tool.Project.get_project_props()
if not LinksData.linked_data: if not LinksData.linked_data:
self.report({"INFO"}, "No linked element found.") self.report({"INFO"}, "No linked element found.")
return {"CANCELLED"} return {"CANCELLED"}
@@ -2128,7 +2139,7 @@ class AppendInspectedLinkedElement(AppendLibraryElement):
self.report({"INFO"}, "Cannot find Global Id for element.") self.report({"INFO"}, "Cannot find Global Id for element.")
return {"CANCELLED"} return {"CANCELLED"}
queried_obj = context.scene.BIMProjectProperties.queried_obj queried_obj = props.queried_obj
ifc_file = tool.Ifc.get() ifc_file = tool.Ifc.get()
linked_ifc_file: ifcopenshell.file linked_ifc_file: ifcopenshell.file
@@ -2275,34 +2286,36 @@ class RefreshClippingPlanes(bpy.types.Operator):
def modal(self, context, event): def modal(self, context, event):
should_refresh = False should_refresh = False
props = tool.Project.get_project_props()
self.clean_deleted_planes(context) self.clean_deleted_planes(context)
for clipping_plane in context.scene.BIMProjectProperties.clipping_planes: for clipping_plane in props.clipping_planes:
if clipping_plane.obj and self.is_moved(clipping_plane.obj): if clipping_plane.obj and self.is_moved(clipping_plane.obj):
should_refresh = True should_refresh = True
break break
total_planes = len(context.scene.BIMProjectProperties.clipping_planes) total_planes = len(props.clipping_planes)
if should_refresh or total_planes != self.total_planes: if should_refresh or total_planes != self.total_planes:
self.refresh_clipping_planes(context) self.refresh_clipping_planes(context)
for clipping_plane in context.scene.BIMProjectProperties.clipping_planes: for clipping_plane in props.clipping_planes:
if clipping_plane.obj: if clipping_plane.obj:
tool.Geometry.record_object_position(clipping_plane.obj) tool.Geometry.record_object_position(clipping_plane.obj)
self.total_planes = total_planes self.total_planes = total_planes
return {"PASS_THROUGH"} return {"PASS_THROUGH"}
def clean_deleted_planes(self, context): def clean_deleted_planes(self, context: bpy.types.Context) -> None:
props = tool.Project.get_project_props()
while True: while True:
for i, clipping_plane in enumerate(context.scene.BIMProjectProperties.clipping_planes): for i, clipping_plane in enumerate(props.clipping_planes):
if clipping_plane.obj: if clipping_plane.obj:
try: try:
clipping_plane.obj.name clipping_plane.obj.name
except: except:
context.scene.BIMProjectProperties.clipping_planes.remove(i) props.clipping_planes.remove(i)
break break
else: else:
context.scene.BIMProjectProperties.clipping_planes.remove(i) props.clipping_planes.remove(i)
break break
else: else:
break break
@@ -2326,14 +2339,15 @@ class RefreshClippingPlanes(bpy.types.Operator):
region = next(r for r in area.regions if r.type == "WINDOW") region = next(r for r in area.regions if r.type == "WINDOW")
data = region.data data = region.data
if not len(context.scene.BIMProjectProperties.clipping_planes): props = tool.Project.get_project_props()
if not len(props.clipping_planes):
data.use_clip_planes = False data.use_clip_planes = False
else: else:
with bpy.context.temp_override(area=area, region=region): with bpy.context.temp_override(area=area, region=region):
bpy.ops.view3d.clip_border() bpy.ops.view3d.clip_border()
clip_planes = [] clip_planes = []
for clipping_plane in bpy.context.scene.BIMProjectProperties.clipping_planes: for clipping_plane in tool.Project.get_project_props().clipping_planes:
obj = clipping_plane.obj obj = clipping_plane.obj
if not obj: if not obj:
continue continue
@@ -2372,8 +2386,8 @@ class CreateClippingPlane(bpy.types.Operator):
from bpy_extras.view3d_utils import region_2d_to_vector_3d, region_2d_to_origin_3d from bpy_extras.view3d_utils import region_2d_to_vector_3d, region_2d_to_origin_3d
# Clean up deleted planes # Clean up deleted planes
props = tool.Project.get_project_props()
if len(context.scene.BIMProjectProperties.clipping_planes) > 5: if len(props.clipping_planes) > 5:
self.report({"INFO"}, "Maximum of six clipping planes allowed.") self.report({"INFO"}, "Maximum of six clipping planes allowed.")
return {"FINISHED"} return {"FINISHED"}
@@ -2413,7 +2427,7 @@ class CreateClippingPlane(bpy.types.Operator):
context.scene.cursor.location = location context.scene.cursor.location = location
new = context.scene.BIMProjectProperties.clipping_planes.add() new = tool.Project.get_project_props().clipping_planes.add()
new.obj = plane_obj new.obj = plane_obj
tool.Blender.set_active_object(plane_obj) tool.Blender.set_active_object(plane_obj)
@@ -2444,7 +2458,7 @@ class FlipClippingPlane(bpy.types.Operator):
def execute(self, context): def execute(self, context):
obj = context.active_object obj = context.active_object
if obj in context.scene.BIMProjectProperties.clipping_planes_objs: if obj in tool.Project.get_project_props().clipping_planes_objs:
obj.rotation_euler[0] += radians(180) obj.rotation_euler[0] += radians(180)
context.view_layer.update() context.view_layer.update()
return {"FINISHED"} return {"FINISHED"}
@@ -2462,14 +2476,15 @@ class BIM_OT_save_clipping_planes(bpy.types.Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
if IfcStore.path: if IfcStore.path:
return context.scene.BIMProjectProperties.clipping_planes return tool.Project.get_project_props().clipping_planes
cls.poll_message_set("Please Save The IFC File") cls.poll_message_set("Please Save The IFC File")
def execute(self, context): def execute(self, context):
clipping_planes_to_serialize = defaultdict(dict) clipping_planes_to_serialize = defaultdict(dict)
clipping_planes = context.scene.BIMProjectProperties.clipping_planes clipping_planes = tool.Project.get_project_props().clipping_planes
for clipping_plane in clipping_planes: for clipping_plane in clipping_planes:
obj = clipping_plane.obj obj = clipping_plane.obj
assert obj
name = obj.name name = obj.name
clipping_planes_to_serialize[name]["location"] = obj.location[0:3] clipping_planes_to_serialize[name]["location"] = obj.location[0:3]
clipping_planes_to_serialize[name]["rotation"] = obj.rotation_euler[0:3] clipping_planes_to_serialize[name]["rotation"] = obj.rotation_euler[0:3]
@@ -2495,13 +2510,14 @@ class BIM_OT_load_clipping_planes(bpy.types.Operator):
cls.poll_message_set("Please Save The IFC File") cls.poll_message_set("Please Save The IFC File")
def execute(self, context): def execute(self, context):
bpy.data.batch_remove(context.scene.BIMProjectProperties.clipping_planes_objs) props = tool.Project.get_project_props()
context.scene.BIMProjectProperties.clipping_planes.clear() bpy.data.batch_remove(props.clipping_planes_objs)
props.clipping_planes.clear()
with open(Path(IfcStore.path).with_name(CLIPPING_PLANES_FILE_NAME), "r") as file: with open(Path(IfcStore.path).with_name(CLIPPING_PLANES_FILE_NAME), "r") as file:
clipping_planes_dict = json.load(file) clipping_planes_dict = json.load(file)
for name, values in clipping_planes_dict.items(): for name, values in clipping_planes_dict.items():
bpy.ops.bim.create_clipping_plane() bpy.ops.bim.create_clipping_plane()
obj = context.scene.BIMProjectProperties.clipping_planes_objs[-1] obj = props.clipping_planes_objs[-1]
obj.name = name obj.name = name
obj.location = values["location"] obj.location = values["location"]
obj.rotation_euler = values["rotation"] obj.rotation_euler = values["rotation"]
@@ -451,7 +451,8 @@ def get_side_area(o: bpy.types.Object) -> float:
def get_cross_section_area(obj: bpy.types.Object) -> float: def get_cross_section_area(obj: bpy.types.Object) -> float:
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) representation = tool.Geometry.get_active_representation(obj)
assert representation
item = representation.Items[0] item = representation.Items[0]
while True: while True:
if item.is_a("IfcExtrudedAreaSolid"): if item.is_a("IfcExtrudedAreaSolid"):
@@ -711,14 +711,14 @@ class SelectSimilar(Operator, tool.Ifc.Operator):
return self.execute(context) return self.execute(context)
def _execute(self, context): def _execute(self, context):
props = context.scene.BIMSearchProperties
obj = context.active_object obj = context.active_object
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
key = self.key key = self.key
if key == "PredefinedType": if key == "PredefinedType":
key = "predefined_type" key = "predefined_type"
value = ifcopenshell.util.selector.get_element_value(element, key) value = ifcopenshell.util.selector.get_element_value(element, key)
tolerance = bpy.context.scene.DocProperties.tolerance dprops = tool.Drawing.get_document_props()
tolerance = dprops.tolerance
# Determine the number of decimal places based on the magnitude of the rounding value # Determine the number of decimal places based on the magnitude of the rounding value
if tolerance < 1: if tolerance < 1:
@@ -155,10 +155,10 @@ class UnlinkStyle(bpy.types.Operator, tool.Ifc.Operator):
# for unlinked blender material. # for unlinked blender material.
updated_meshes = set() updated_meshes = set()
for obj in bpy.data.objects: for obj in bpy.data.objects:
mesh = obj.data if not (mesh := obj.data) or not isinstance(mesh, bpy.types.Mesh):
if not isinstance(mesh, bpy.types.Mesh):
continue continue
if not mesh.BIMMeshProperties.ifc_definition_id: representation = tool.Geometry.get_data_representation(mesh)
if not representation:
continue continue
if mesh in updated_meshes: if mesh in updated_meshes:
continue continue
@@ -1137,13 +1137,16 @@ class AssignStyleToSelected(bpy.types.Operator, tool.Ifc.Operator):
ifc_file = tool.Ifc.get() ifc_file = tool.Ifc.get()
style = ifc_file.by_id(self.style_id) style = ifc_file.by_id(self.style_id)
material = tool.Ifc.get_object(style) material = tool.Ifc.get_object(style)
assert isinstance(material, bpy.types.Material)
has_items = False has_items = False
representations: dict[ifcopenshell.entity_instance, bpy.types.Object] = {} representations: dict[ifcopenshell.entity_instance, bpy.types.Object] = {}
for obj in context.selected_objects: for obj in context.selected_objects:
if tool.Geometry.is_representation_item(obj): if tool.Geometry.is_representation_item(obj):
has_items = True has_items = True
item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) assert isinstance(obj.data, bpy.types.Mesh)
item = tool.Geometry.get_active_representation(obj)
assert item
tool.Style.assign_style_to_representation_item(item, style) tool.Style.assign_style_to_representation_item(item, style)
obj.data.materials.clear() obj.data.materials.clear()
obj.data.materials.append(material) obj.data.materials.append(material)
@@ -1156,7 +1159,8 @@ class AssignStyleToSelected(bpy.types.Operator, tool.Ifc.Operator):
return {"FINISHED"} return {"FINISHED"}
if has_items: if has_items:
tool.Geometry.reload_representation(context.scene.BIMGeometryProperties.representation_obj) gprops = tool.Geometry.get_geometry_props()
tool.Geometry.reload_representation(gprops.representation_obj)
bpy.ops.bim.disable_editing_representation_items() bpy.ops.bim.disable_editing_representation_items()
bpy.ops.bim.enable_editing_representation_items() bpy.ops.bim.enable_editing_representation_items()
+2 -14
View File
@@ -127,26 +127,14 @@ class BooleansData:
def booleans(cls): def booleans(cls):
props = tool.Geometry.get_geometry_props() props = tool.Geometry.get_geometry_props()
obj = props.representation_obj or bpy.context.active_object obj = props.representation_obj or bpy.context.active_object
if ( if not (representation := tool.Geometry.get_active_representation(obj)):
not obj.data
or not hasattr(obj.data, "BIMMeshProperties")
or not obj.data.BIMMeshProperties.ifc_definition_id
):
return [] return []
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
return tool.Model.get_booleans(representation=representation) return tool.Model.get_booleans(representation=representation)
@classmethod @classmethod
def manual_booleans(cls): def manual_booleans(cls):
props = tool.Geometry.get_geometry_props() props = tool.Geometry.get_geometry_props()
obj = props.representation_obj or bpy.context.active_object obj = props.representation_obj or bpy.context.active_object
if ( if not (representation := tool.Geometry.get_active_representation(obj)):
not obj.data
or not hasattr(obj.data, "BIMMeshProperties")
or not obj.data.BIMMeshProperties.ifc_definition_id
):
return [] return []
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
return tool.Model.get_manual_booleans(tool.Ifc.get_entity(obj), representation=representation) return tool.Model.get_manual_booleans(tool.Ifc.get_entity(obj), representation=representation)
+10 -11
View File
@@ -139,7 +139,8 @@ class AddOpening(bpy.types.Operator, tool.Ifc.Operator):
tool.Ifc, tool.Geometry, tool.Surveyor, obj=voided_obj tool.Ifc, tool.Geometry, tool.Surveyor, obj=voided_obj
) )
representation = tool.Ifc.get().by_id(voided_obj.data.BIMMeshProperties.ifc_definition_id) representation = tool.Geometry.get_active_representation(voided_obj)
assert representation
bonsai.core.geometry.switch_representation( bonsai.core.geometry.switch_representation(
tool.Ifc, tool.Ifc,
tool.Geometry, tool.Geometry,
@@ -269,20 +270,18 @@ class BooleansMarkAsManual(bpy.types.Operator, tool.Ifc.Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
obj = context.active_object obj = context.active_object
if ( if obj and tool.Ifc.get_entity(obj) and tool.Geometry.get_active_representation(obj):
obj
and tool.Ifc.get_entity(obj)
and hasattr(obj.data, "BIMMeshProperties")
and obj.data.BIMMeshProperties.ifc_definition_id
):
return True return True
cls.poll_message_set("Need to select IFC element with representation") cls.poll_message_set("Need to select IFC element with representation")
return False return False
def _execute(self, context): def _execute(self, context):
obj = context.active_object obj = context.active_object
assert obj
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) assert element
representation = tool.Geometry.get_active_representation(obj)
assert representation
booleans = tool.Model.get_booleans(representation=representation) booleans = tool.Model.get_booleans(representation=representation)
if self.mark_as_manual: if self.mark_as_manual:
@@ -304,13 +303,13 @@ class EnableEditingBooleans(bpy.types.Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
if not bpy.context.scene.BIMGeometryProperties.representation_obj: if not tool.Geometry.get_geometry_props().representation_obj:
cls.poll_message_set("To enable editing booleans object should be in item mode.") cls.poll_message_set("To enable editing booleans object should be in item mode.")
return False return False
return True return True
def execute(self, context): def execute(self, context):
props = context.scene.BIMBooleanProperties props = tool.Feature.get_boolean_props()
gprops = tool.Geometry.get_geometry_props() gprops = tool.Geometry.get_geometry_props()
rep_obj = gprops.representation_obj rep_obj = gprops.representation_obj
assert rep_obj assert rep_obj
@@ -344,6 +343,6 @@ class DisableEditingBooleans(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def execute(self, context): def execute(self, context):
props = context.scene.BIMBooleanProperties props = tool.Feature.get_boolean_props()
props.is_editing = False props.is_editing = False
return {"FINISHED"} return {"FINISHED"}
+24 -7
View File
@@ -19,34 +19,51 @@
import bpy import bpy
from bpy.types import PropertyGroup from bpy.types import PropertyGroup
from bpy.props import PointerProperty, StringProperty, IntProperty, BoolProperty, CollectionProperty, EnumProperty from bpy.props import PointerProperty, StringProperty, IntProperty, BoolProperty, CollectionProperty, EnumProperty
from typing import Union from typing import Union, TYPE_CHECKING, Literal, get_args
OperatorType = Literal["DIFFERENCE", "INTERSECTION", "UNION"]
class Boolean(PropertyGroup): class Boolean(PropertyGroup):
name: StringProperty(name="Name") name: StringProperty(name="Name")
operator: StringProperty(name="Operator") operator: EnumProperty(
items=[(i, i, "") for i in get_args(OperatorType)],
name="Operator",
default="DIFFERENCE",
)
ifc_definition_id: IntProperty(name="IFC Definition ID") ifc_definition_id: IntProperty(name="IFC Definition ID")
level: IntProperty(name="Level") level: IntProperty(name="Level")
if TYPE_CHECKING:
operator: OperatorType
name: str
ifc_definition_id: int
level: int
class VoidProperties(PropertyGroup): class VoidProperties(PropertyGroup):
desired_opening: PointerProperty(name="Desired Opening To Fill", type=bpy.types.Object) desired_opening: PointerProperty(name="Desired Opening To Fill", type=bpy.types.Object)
if TYPE_CHECKING:
desired_opening: Union[bpy.types.Object, None]
class BIMBooleanProperties(PropertyGroup): class BIMBooleanProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing", default=False) is_editing: BoolProperty(name="Is Editing", default=False)
booleans: CollectionProperty(name="Booleans", type=Boolean) booleans: CollectionProperty(name="Booleans", type=Boolean)
active_boolean_index: IntProperty(name="Active Boolean Index") active_boolean_index: IntProperty(name="Active Boolean Index")
operator: EnumProperty( operator: EnumProperty(
items=[ items=[(i, i, "") for i in get_args(OperatorType)],
("DIFFERENCE", "DIFFERENCE", ""),
("INTERSECTION", "INTERSECTION", ""),
("UNION", "UNION", ""),
],
name="Operator", name="Operator",
default="DIFFERENCE", default="DIFFERENCE",
) )
if TYPE_CHECKING:
is_editing: bool
booleans: bpy.types.bpy_prop_collection_idprop[Boolean]
active_boolean_index: int
operator: OperatorType
@property @property
def active_boolean(self) -> Union[Boolean, None]: def active_boolean(self) -> Union[Boolean, None]:
if self.booleans and 0 <= self.active_boolean_index < len(self.booleans): if self.booleans and 0 <= self.active_boolean_index < len(self.booleans):
+8 -11
View File
@@ -126,13 +126,10 @@ class BIM_PT_booleans(Panel):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return ( return (
context.active_object is not None (obj := context.active_object) is not None
and context.active_object.type == "MESH" and isinstance(data := obj.data, bpy.types.Mesh)
and hasattr(context.active_object.data, "BIMMeshProperties") and (mesh_props := tool.Geometry.get_mesh_props(data))
and ( and (mesh_props.ifc_definition_id or mesh_props.ifc_boolean_id)
context.active_object.data.BIMMeshProperties.ifc_definition_id
or context.active_object.data.BIMMeshProperties.ifc_boolean_id
)
) )
def draw(self, context): def draw(self, context):
@@ -141,13 +138,13 @@ class BIM_PT_booleans(Panel):
obj = context.active_object obj = context.active_object
assert obj assert obj
mesh = obj.data
assert isinstance(mesh, bpy.types.Mesh)
if not context.active_object.data:
return
layout = self.layout layout = self.layout
props = context.scene.BIMBooleanProperties props = tool.Feature.get_boolean_props()
if context.active_object.data.BIMMeshProperties.ifc_definition_id: if tool.Geometry.get_mesh_props(mesh).ifc_definition_id:
row = layout.row(align=True) row = layout.row(align=True)
total_booleans = BooleansData.data["total_booleans"] total_booleans = BooleansData.data["total_booleans"]
manual_booleans = BooleansData.data["manual_booleans"] manual_booleans = BooleansData.data["manual_booleans"]
+11 -8
View File
@@ -828,7 +828,8 @@ class AddIfcFile(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def execute(self, context): def execute(self, context):
context.scene.DocProperties.ifc_files.add() props = tool.Drawing.get_document_props()
props.ifc_files.add()
return {"FINISHED"} return {"FINISHED"}
@@ -839,7 +840,8 @@ class RemoveIfcFile(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def execute(self, context): def execute(self, context):
context.scene.DocProperties.ifc_files.remove(self.index) props = tool.Drawing.get_document_props()
props.ifc_files.remove(self.index)
return {"FINISHED"} return {"FINISHED"}
@@ -1060,7 +1062,8 @@ class ClippingPlaneCutWithCappings(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def execute(self, context): def execute(self, context):
cutting_planes = [p.obj for p in context.scene.BIMProjectProperties.clipping_planes] props = tool.Project.get_project_props()
cutting_planes = [obj for p in props.clipping_planes if (obj := p.obj)]
if not cutting_planes: if not cutting_planes:
self.report({"INFO"}, "No cutting planes found.") self.report({"INFO"}, "No cutting planes found.")
return {"FINISHED"} return {"FINISHED"}
@@ -1070,7 +1073,7 @@ class ClippingPlaneCutWithCappings(bpy.types.Operator):
objects_processed, t0 = 0, time.time() objects_processed, t0 = 0, time.time()
wm.progress_begin(0, len(context.selected_objects)) wm.progress_begin(0, len(context.selected_objects))
for obj_i, obj in enumerate(context.selected_objects): for obj_i, obj in enumerate(context.selected_objects):
if obj.type != "MESH": if not isinstance((mesh := obj.data), bpy.types.Mesh):
continue continue
if obj in cutting_planes: if obj in cutting_planes:
@@ -1082,7 +1085,6 @@ class ClippingPlaneCutWithCappings(bpy.types.Operator):
ws_to_ls = obj.matrix_world.inverted() ws_to_ls = obj.matrix_world.inverted()
rotation = ws_to_ls.to_quaternion() rotation = ws_to_ls.to_quaternion()
mesh = obj.data
bm = tool.Blender.get_bmesh_for_mesh(mesh) bm = tool.Blender.get_bmesh_for_mesh(mesh)
object_changed = False object_changed = False
@@ -1106,7 +1108,7 @@ class ClippingPlaneCutWithCappings(bpy.types.Operator):
# don't swap mesh if it wasn't affected by any of the cutting planes # don't swap mesh if it wasn't affected by any of the cutting planes
if object_changed: if object_changed:
temp_mesh = bpy.data.meshes.new("temp_cut") temp_mesh = bpy.data.meshes.new("temp_cut")
temp_mesh.BIMMeshProperties.replaced_mesh = mesh tool.Geometry.get_mesh_props(temp_mesh).replaced_mesh = mesh
for material in mesh.materials: for material in mesh.materials:
temp_mesh.materials.append(material) temp_mesh.materials.append(material)
obj.data = temp_mesh obj.data = temp_mesh
@@ -1163,9 +1165,10 @@ class RevertClippingPlaneCut(bpy.types.Operator):
self.report({"INFO"}, f"{objects_processed} processed - {time.time()-t0:.3f} sec") self.report({"INFO"}, f"{objects_processed} processed - {time.time()-t0:.3f} sec")
return {"FINISHED"} return {"FINISHED"}
def revert_object_mesh(self, obj): def revert_object_mesh(self, obj: bpy.types.Object) -> None:
mesh = obj.data mesh = obj.data
replaced_mesh = mesh.BIMMeshProperties.replaced_mesh assert isinstance(mesh, bpy.types.Mesh)
replaced_mesh = tool.Geometry.get_mesh_props(mesh).replaced_mesh
if replaced_mesh: if replaced_mesh:
obj.data = replaced_mesh obj.data = replaced_mesh
tool.Blender.remove_data_block(mesh, do_unlink=False) tool.Blender.remove_data_block(mesh, do_unlink=False)
+56 -1
View File
@@ -525,6 +525,13 @@ class IfcParameter(PropertyGroup):
value: FloatProperty(name="Value") # For now, only floats value: FloatProperty(name="Value") # For now, only floats
type: StringProperty(name="Type") type: StringProperty(name="Type")
if TYPE_CHECKING:
name: str
step_id: int
index: int
value: float
type: str
class PsetQto(PropertyGroup): class PsetQto(PropertyGroup):
name: StringProperty(name="Name") name: StringProperty(name="Name")
@@ -532,6 +539,11 @@ class PsetQto(PropertyGroup):
is_expanded: BoolProperty(name="Is Expanded", default=True) is_expanded: BoolProperty(name="Is Expanded", default=True)
is_editable: BoolProperty(name="Is Editable") is_editable: BoolProperty(name="Is Editable")
if TYPE_CHECKING:
properties: bpy.types.bpy_prop_collection_idprop[Attribute]
is_expanded: bool
is_editable: bool
class GlobalId(PropertyGroup): class GlobalId(PropertyGroup):
name: StringProperty(name="Name") name: StringProperty(name="Name")
@@ -540,6 +552,9 @@ class GlobalId(PropertyGroup):
class BIMCollectionProperties(PropertyGroup): class BIMCollectionProperties(PropertyGroup):
obj: PointerProperty(type=bpy.types.Object) obj: PointerProperty(type=bpy.types.Object)
if TYPE_CHECKING:
obj: Union[bpy.types.Object, None]
class BIMObjectProperties(PropertyGroup): class BIMObjectProperties(PropertyGroup):
collection: PointerProperty(type=bpy.types.Collection) collection: PointerProperty(type=bpy.types.Collection)
@@ -564,6 +579,9 @@ def get_profiles(self: "BIMMeshProperties", context: bpy.types.Context):
return ItemData.data["profiles_enum"] return ItemData.data["profiles_enum"]
SubshapeType = Literal["-", "PROFILE", "AXIS"]
class BIMMeshProperties(PropertyGroup): class BIMMeshProperties(PropertyGroup):
ifc_definition_id: IntProperty(name="IFC Definition ID") ifc_definition_id: IntProperty(name="IFC Definition ID")
ifc_boolean_id: IntProperty(name="IFC Boolean ID") ifc_boolean_id: IntProperty(name="IFC Boolean ID")
@@ -572,7 +590,7 @@ class BIMMeshProperties(PropertyGroup):
is_native: BoolProperty(name="Is Native", default=False) is_native: BoolProperty(name="Is Native", default=False)
is_swept_solid: BoolProperty(name="Is Swept Solid") is_swept_solid: BoolProperty(name="Is Swept Solid")
is_parametric: BoolProperty(name="Is Parametric", default=False) is_parametric: BoolProperty(name="Is Parametric", default=False)
subshape_type: EnumProperty(name="Subshape Type", items=[(i, i, "") for i in ("-", "PROFILE", "AXIS")]) subshape_type: EnumProperty(name="Subshape Type", items=[(i, i, "") for i in get_args(SubshapeType)])
ifc_parameters: CollectionProperty(name="IFC Parameters", type=IfcParameter) ifc_parameters: CollectionProperty(name="IFC Parameters", type=IfcParameter)
item_attributes: CollectionProperty(name="Item Attributes", type=Attribute) item_attributes: CollectionProperty(name="Item Attributes", type=Attribute)
item_profile: EnumProperty(name="Item Profile", items=get_profiles) item_profile: EnumProperty(name="Item Profile", items=get_profiles)
@@ -580,6 +598,22 @@ class BIMMeshProperties(PropertyGroup):
mesh_checksum: StringProperty(name="Mesh Checksum", default="") mesh_checksum: StringProperty(name="Mesh Checksum", default="")
replaced_mesh: PointerProperty(type=bpy.types.Mesh, description="Original mesh to revert section cutaway") replaced_mesh: PointerProperty(type=bpy.types.Mesh, description="Original mesh to revert section cutaway")
if TYPE_CHECKING:
ifc_definition_id: int
ifc_boolean_id: int
obj: Union[bpy.types.Object, None]
has_openings_applied: bool
is_native: bool
is_swept_solid: bool
is_parametric: bool
subshape_type: SubshapeType
ifc_parameters: bpy.types.bpy_prop_collection_idprop[IfcParameter]
item_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
item_profile: str
material_checksum: str
mesh_checksum: str
replaced_mesh: Union[bpy.types.Mesh, None]
class BIMFacet(PropertyGroup): class BIMFacet(PropertyGroup):
name: StringProperty(name="Name") name: StringProperty(name="Name")
@@ -599,16 +633,30 @@ class BIMFacet(PropertyGroup):
], ],
) )
if TYPE_CHECKING:
pset: str
value: str
type: str
comparison: Literal["=", "!=", ">=", "<=", ">", "<", "*=", "!*="]
class BIMFilterGroup(PropertyGroup): class BIMFilterGroup(PropertyGroup):
filters: CollectionProperty(type=BIMFacet, name="filters") filters: CollectionProperty(type=BIMFacet, name="filters")
if TYPE_CHECKING:
filters: bpy.types.bpy_prop_collection_idprop[BIMFacet]
class BIMSnapGroups(PropertyGroup): class BIMSnapGroups(PropertyGroup):
object: BoolProperty(name="Object", default=True) object: BoolProperty(name="Object", default=True)
polyline: BoolProperty(name="Polyline", default=True) polyline: BoolProperty(name="Polyline", default=True)
measure: BoolProperty(name="Measure", default=True) measure: BoolProperty(name="Measure", default=True)
if TYPE_CHECKING:
object: bool
polyline: bool
measure: bool
class BIMSnapProperties(PropertyGroup): class BIMSnapProperties(PropertyGroup):
vertex: BoolProperty(name="Vertex", default=True) vertex: BoolProperty(name="Vertex", default=True)
@@ -616,3 +664,10 @@ class BIMSnapProperties(PropertyGroup):
edge_center: BoolProperty(name="Edge Center", default=True) edge_center: BoolProperty(name="Edge Center", default=True)
edge_intersection: BoolProperty(name="Edge Intersection", default=True) edge_intersection: BoolProperty(name="Edge Intersection", default=True)
face: BoolProperty(name="Face", default=True) face: BoolProperty(name="Face", default=True)
if TYPE_CHECKING:
vertex: bool
edge: bool
edge_center: bool
edge_intersection: bool
face: bool
+59 -33
View File
@@ -19,6 +19,7 @@
import os import os
import bpy import bpy
import platform import platform
import bonsai.bim.helper
from pathlib import Path from pathlib import Path
from bpy.types import Panel from bpy.types import Panel
from bpy.props import StringProperty, IntProperty, BoolProperty from bpy.props import StringProperty, IntProperty, BoolProperty
@@ -34,7 +35,7 @@ import bonsai.bim
import bonsai.tool as tool import bonsai.tool as tool
from ifcopenshell.util.file import IfcHeaderExtractor from ifcopenshell.util.file import IfcHeaderExtractor
from bonsai.bim.prop import Attribute from bonsai.bim.prop import Attribute
from typing import Optional from typing import Optional, TYPE_CHECKING
class IFCFileSelector: class IFCFileSelector:
@@ -147,7 +148,7 @@ class BIM_PT_section_with_cappings(Panel):
row.operator("bim.clipping_plane_cut_with_cappings", icon="XRAY", text="Cut") row.operator("bim.clipping_plane_cut_with_cappings", icon="XRAY", text="Cut")
row.operator("bim.revert_clipping_plane_cut", icon="FILE_REFRESH", text="Revert Cut") row.operator("bim.revert_clipping_plane_cut", icon="FILE_REFRESH", text="Revert Cut")
props = context.scene.BIMProjectProperties props = tool.Project.get_project_props()
box = layout.box() box = layout.box()
header = box.row(align=True) header = box.row(align=True)
header.label(text="Clipping Planes") header.label(text="Clipping Planes")
@@ -247,7 +248,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
should_play_chaching_sound: BoolProperty(name="Play A Cha-Ching Sound When Project Costs Updates", default=False) should_play_chaching_sound: BoolProperty(name="Play A Cha-Ching Sound When Project Costs Updates", default=False)
tmp_dir: StringProperty( tmp_dir: StringProperty(
name="Temporary Directory", name="Temporary Directory",
description='Path to create and store temporary files. If left blank, a system default will be used.', description="Path to create and store temporary files. If left blank, a system default will be used.",
) )
spatial_elements_unselectable: BoolProperty( spatial_elements_unselectable: BoolProperty(
name="Make Spatial Elements Unselectable By Default", name="Make Spatial Elements Unselectable By Default",
@@ -302,7 +303,6 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
size=4, size=4,
description="Color of background overlays", description="Color of background overlays",
) )
opening_focus_opacity: bpy.props.IntProperty( opening_focus_opacity: bpy.props.IntProperty(
default=100, default=100,
min=0, min=0,
@@ -312,7 +312,29 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
description="When modifying openings, other elements of the model will display with some transparency.\n0 is fully transparent and 100 is fully opaque", description="When modifying openings, other elements of the model will display with some transparency.\n0 is fully transparent and 100 is fully opaque",
) )
def draw(self, context): if TYPE_CHECKING:
svg2pdf_command: str
svg2dxf_command: str
svg_command: str
layout_svg_command: str
pdf_command: str
spreadsheet_command: str
should_hide_empty_props: bool
should_setup_workspace: bool
activate_workspace: bool
should_setup_toolbar: bool
should_play_chaching_sound: bool
spatial_elements_unselectable: bool
tmp_dir: str
decorations_colour: tuple[float, float, float, float]
decorator_color_selected: tuple[float, float, float, float]
decorator_color_unselected: tuple[float, float, float, float]
decorator_color_special: tuple[float, float, float, float]
decorator_color_error: tuple[float, float, float, float]
decorator_color_background: tuple[float, float, float, float]
opening_focus_opacity: int
def draw(self, context: bpy.types.Context) -> None:
layout = self.layout layout = self.layout
row = layout.row() row = layout.row()
@@ -333,7 +355,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
bonsai.bim.helper.draw_expandable_panel(self.layout, context, "Drawing", self.draw_drawing_settings) bonsai.bim.helper.draw_expandable_panel(self.layout, context, "Drawing", self.draw_drawing_settings)
bonsai.bim.helper.draw_expandable_panel(self.layout, context, "Openings", self.draw_openings_settings) bonsai.bim.helper.draw_expandable_panel(self.layout, context, "Openings", self.draw_openings_settings)
def draw_commands(self, layout, context): def draw_commands(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
layout.prop(self, "svg2pdf_command") layout.prop(self, "svg2pdf_command")
layout.prop(self, "svg2dxf_command") layout.prop(self, "svg2dxf_command")
layout.prop(self, "svg_command") layout.prop(self, "svg_command")
@@ -341,15 +363,16 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
layout.prop(self, "pdf_command") layout.prop(self, "pdf_command")
layout.prop(self, "spreadsheet_command") layout.prop(self, "spreadsheet_command")
def draw_misc_settings(self, layout, context): def draw_misc_settings(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
layout.prop(self, "should_hide_empty_props") layout.prop(self, "should_hide_empty_props")
layout.prop(self, "should_setup_workspace") layout.prop(self, "should_setup_workspace")
layout.prop(self, "activate_workspace") layout.prop(self, "activate_workspace")
layout.prop(self, "should_setup_toolbar") layout.prop(self, "should_setup_toolbar")
layout.prop(self, "should_play_chaching_sound") layout.prop(self, "should_play_chaching_sound")
layout.prop(self, "spatial_elements_unselectable") layout.prop(self, "spatial_elements_unselectable")
layout.prop(context.scene.BIMProjectProperties, "should_disable_undo_on_save") props = tool.Project.get_project_props()
layout.prop(context.scene.BIMProjectProperties, "should_stream") layout.prop(props, "should_disable_undo_on_save")
layout.prop(props, "should_stream")
def draw_model_settings(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None: def draw_model_settings(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
props = tool.Model.get_model_props() props = tool.Model.get_model_props()
@@ -357,7 +380,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
if props.occurrence_name_style == "CUSTOM": if props.occurrence_name_style == "CUSTOM":
layout.prop(props, "occurrence_name_function") layout.prop(props, "occurrence_name_function")
def draw_directories(self, layout, context): def draw_directories(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
row = layout.row(align=True) row = layout.row(align=True)
row.prop(context.scene.BIMProperties, "data_dir") row.prop(context.scene.BIMProperties, "data_dir")
row.operator("bim.select_dir", icon="FILE_FOLDER", text="").data_path = "scene.BIMProperties.data_dir" row.operator("bim.select_dir", icon="FILE_FOLDER", text="").data_path = "scene.BIMProperties.data_dir"
@@ -370,25 +393,26 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
row.prop(self, "tmp_dir") row.prop(self, "tmp_dir")
row.operator("bim.select_dir", icon="FILE_FOLDER", text="").data_path = "preferences.tmp_dir" row.operator("bim.select_dir", icon="FILE_FOLDER", text="").data_path = "preferences.tmp_dir"
def draw_drawing_settings(self, layout, context): def draw_drawing_settings(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
layout.prop(context.scene.BIMProperties, "pset_dir") layout.prop(context.scene.BIMProperties, "pset_dir")
layout.prop(context.scene.DocProperties, "sheets_dir") dprops = tool.Drawing.get_document_props()
layout.prop(context.scene.DocProperties, "layouts_dir") layout.prop(dprops, "sheets_dir")
layout.prop(context.scene.DocProperties, "titleblocks_dir") layout.prop(dprops, "layouts_dir")
layout.prop(context.scene.DocProperties, "drawings_dir") layout.prop(dprops, "titleblocks_dir")
layout.prop(context.scene.DocProperties, "stylesheet_path") layout.prop(dprops, "drawings_dir")
layout.prop(context.scene.DocProperties, "schedules_stylesheet_path") layout.prop(dprops, "stylesheet_path")
layout.prop(context.scene.DocProperties, "markers_path") layout.prop(dprops, "schedules_stylesheet_path")
layout.prop(context.scene.DocProperties, "symbols_path") layout.prop(dprops, "markers_path")
layout.prop(context.scene.DocProperties, "patterns_path") layout.prop(dprops, "symbols_path")
layout.prop(context.scene.DocProperties, "shadingstyles_path") layout.prop(dprops, "patterns_path")
layout.prop(context.scene.DocProperties, "shadingstyle_default") layout.prop(dprops, "shadingstyles_path")
layout.prop(dprops, "shadingstyle_default")
row = layout.row() row = layout.row()
row.prop(context.scene.DocProperties, "drawing_font") row.prop(dprops, "drawing_font")
row.prop(context.scene.DocProperties, "magic_font_scale") row.prop(dprops, "magic_font_scale")
layout.prop(context.scene.DocProperties, "imperial_precision") layout.prop(dprops, "imperial_precision")
layout.prop(context.scene.DocProperties, "tolerance") layout.prop(dprops, "tolerance")
layout.prop(context.scene.DocProperties, "classes_to_wireframe") layout.prop(dprops, "classes_to_wireframe")
def draw_decorator_colors(self, layout, context): def draw_decorator_colors(self, layout, context):
layout.row().prop(self, "decorations_colour") layout.row().prop(self, "decorations_colour")
@@ -496,9 +520,10 @@ class BIM_PT_tabs(Panel):
op.uri = "https://docs.bonsaibim.org/guides/troubleshooting.html#saving-and-loading-blend-files" op.uri = "https://docs.bonsaibim.org/guides/troubleshooting.html#saving-and-loading-blend-files"
row.operator("bim.close_blend_warning", text="", icon="CANCEL") row.operator("bim.close_blend_warning", text="", icon="CANCEL")
if context.mode == "OBJECT" and context.scene.BIMGeometryProperties.mode in ("OBJECT", "ITEM"): gprops = tool.Geometry.get_geometry_props()
if context.mode == "OBJECT" and gprops.mode in ("OBJECT", "ITEM"):
pass pass
elif context.mode.startswith("EDIT") and context.scene.BIMGeometryProperties.mode == "EDIT": elif context.mode.startswith("EDIT") and gprops.mode == "EDIT":
pass pass
else: else:
box = self.layout.box() box = self.layout.box()
@@ -533,7 +558,7 @@ class BIM_PT_tab_new_project_wizard(Panel):
if not tool.Blender.is_tab(context, "PROJECT"): if not tool.Blender.is_tab(context, "PROJECT"):
return False return False
props = context.scene.BIMProperties props = context.scene.BIMProperties
pprops = context.scene.BIMProjectProperties pprops = tool.Project.get_project_props()
if pprops.is_loading: if pprops.is_loading:
return False return False
elif tool.Ifc.get() or props.ifc_file: elif tool.Ifc.get() or props.ifc_file:
@@ -555,7 +580,7 @@ class BIM_PT_tab_project_info(Panel):
if not tool.Blender.is_tab(context, "PROJECT"): if not tool.Blender.is_tab(context, "PROJECT"):
return False return False
props = context.scene.BIMProperties props = context.scene.BIMProperties
pprops = context.scene.BIMProjectProperties pprops = tool.Project.get_project_props()
if pprops.is_loading: if pprops.is_loading:
return True return True
elif tool.Ifc.get() or props.ifc_file: elif tool.Ifc.get() or props.ifc_file:
@@ -854,6 +879,7 @@ class BIM_PT_tab_object_metadata(Panel):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
props = tool.Project.get_project_props()
return ( return (
tool.Blender.is_tab(context, "OBJECT") tool.Blender.is_tab(context, "OBJECT")
and tool.Ifc.get() and tool.Ifc.get()
@@ -862,7 +888,7 @@ class BIM_PT_tab_object_metadata(Panel):
and ( and (
obj.type != "EMPTY" obj.type != "EMPTY"
or not obj.instance_collection or not obj.instance_collection
or not any(l.empty_handle == obj for l in context.scene.BIMProjectProperties.links) or not any(l.empty_handle == obj for l in props.links)
) )
) )
@@ -1231,7 +1257,7 @@ class BIM_PT_decorators_overlay(Panel):
view = context.space_data view = context.space_data
overlay = view.overlay overlay = view.overlay
georeference_props = bpy.context.scene.BIMGeoreferenceProperties georeference_props = tool.Georeference.get_georeference_props()
aggregate_props = bpy.context.scene.BIMAggregateProperties aggregate_props = bpy.context.scene.BIMAggregateProperties
nest_props = bpy.context.scene.BIMNestProperties nest_props = bpy.context.scene.BIMNestProperties
model_props = tool.Model.get_model_props() model_props = tool.Model.get_model_props()
+1 -1
View File
@@ -913,7 +913,7 @@ class Blender(bonsai.core.tool.Blender):
return False return False
if not (element := tool.Ifc.get_entity(obj)): if not (element := tool.Ifc.get_entity(obj)):
return True return True
if obj in bpy.context.scene.BIMProjectProperties.clipping_planes_objs: if obj in tool.Project.get_project_props().clipping_planes_objs:
return False return False
usage_type = tool.Model.get_usage_type(element) usage_type = tool.Model.get_usage_type(element)
if usage_type in ("LAYER1", "LAYER2"): if usage_type in ("LAYER1", "LAYER2"):
+9 -1
View File
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>. # along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import os import os
import json import json
import bpy import bpy
@@ -30,10 +31,17 @@ import bonsai.tool as tool
from bonsai.bim.ifc import IfcStore from bonsai.bim.ifc import IfcStore
from mathutils import Vector from mathutils import Vector
from collections import defaultdict from collections import defaultdict
from typing import Iterable, Literal from typing import Iterable, Literal, TYPE_CHECKING
if TYPE_CHECKING:
from bonsai.bim.module.debug.prop import BIMDebugProperties
class Debug(bonsai.core.tool.Debug): class Debug(bonsai.core.tool.Debug):
@classmethod
def get_debug_props(cls) -> BIMDebugProperties:
return bpy.context.scene.BIMDebugProperties
@classmethod @classmethod
def add_schema_identifier(cls, schema: W.schema_definition) -> None: def add_schema_identifier(cls, schema: W.schema_definition) -> None:
IfcStore.schema_identifiers.append(schema.name()) IfcStore.schema_identifiers.append(schema.name())
+35 -26
View File
@@ -346,19 +346,23 @@ class Drawing(bonsai.core.tool.Drawing):
@classmethod @classmethod
def disable_editing_drawings(cls) -> None: def disable_editing_drawings(cls) -> None:
bpy.context.scene.DocProperties.is_editing_drawings = False props = tool.Drawing.get_document_props()
props.is_editing_drawings = False
@classmethod @classmethod
def disable_editing_schedules(cls) -> None: def disable_editing_schedules(cls) -> None:
bpy.context.scene.DocProperties.is_editing_schedules = False props = tool.Drawing.get_document_props()
props.is_editing_schedules = False
@classmethod @classmethod
def disable_editing_references(cls) -> None: def disable_editing_references(cls) -> None:
bpy.context.scene.DocProperties.is_editing_references = False props = tool.Drawing.get_document_props()
props.is_editing_references = False
@classmethod @classmethod
def disable_editing_sheets(cls) -> None: def disable_editing_sheets(cls) -> None:
bpy.context.scene.DocProperties.is_editing_sheets = False props = tool.Drawing.get_document_props()
props.is_editing_sheets = False
@classmethod @classmethod
def disable_editing_text(cls, obj: bpy.types.Object) -> None: def disable_editing_text(cls, obj: bpy.types.Object) -> None:
@@ -383,19 +387,23 @@ class Drawing(bonsai.core.tool.Drawing):
@classmethod @classmethod
def enable_editing_drawings(cls) -> None: def enable_editing_drawings(cls) -> None:
bpy.context.scene.DocProperties.is_editing_drawings = True props = tool.Drawing.get_document_props()
props.is_editing_drawings = True
@classmethod @classmethod
def enable_editing_schedules(cls) -> None: def enable_editing_schedules(cls) -> None:
bpy.context.scene.DocProperties.is_editing_schedules = True props = tool.Drawing.get_document_props()
props.is_editing_schedules = True
@classmethod @classmethod
def enable_editing_references(cls) -> None: def enable_editing_references(cls) -> None:
bpy.context.scene.DocProperties.is_editing_references = True props = tool.Drawing.get_document_props()
props.is_editing_references = True
@classmethod @classmethod
def enable_editing_sheets(cls) -> None: def enable_editing_sheets(cls) -> None:
bpy.context.scene.DocProperties.is_editing_sheets = True props = tool.Drawing.get_document_props()
props.is_editing_sheets = True
@classmethod @classmethod
def enable_editing_text(cls, obj: bpy.types.Object) -> None: def enable_editing_text(cls, obj: bpy.types.Object) -> None:
@@ -616,7 +624,8 @@ class Drawing(bonsai.core.tool.Drawing):
@classmethod @classmethod
def is_editing_sheets(cls) -> bool: def is_editing_sheets(cls) -> bool:
return bpy.context.scene.DocProperties.is_editing_sheets props = tool.Drawing.get_document_props()
return props.is_editing_sheets
@classmethod @classmethod
def remove_literal_from_annotation(cls, obj: bpy.types.Object, literal: ifcopenshell.entity_instance) -> None: def remove_literal_from_annotation(cls, obj: bpy.types.Object, literal: ifcopenshell.entity_instance) -> None:
@@ -810,7 +819,7 @@ class Drawing(bonsai.core.tool.Drawing):
@classmethod @classmethod
def import_drawings(cls) -> None: def import_drawings(cls) -> None:
props = bpy.context.scene.DocProperties props = tool.Drawing.get_document_props()
expanded_target_views = {d.target_view for d in props.drawings if d.is_expanded} expanded_target_views = {d.target_view for d in props.drawings if d.is_expanded}
if not hasattr(cls, "drawing_selected_states"): if not hasattr(cls, "drawing_selected_states"):
cls.drawing_selected_states = {} cls.drawing_selected_states = {}
@@ -1048,7 +1057,8 @@ class Drawing(bonsai.core.tool.Drawing):
@classmethod @classmethod
def show_decorations(cls) -> None: def show_decorations(cls) -> None:
bpy.context.scene.DocProperties.should_draw_decorations = True props = tool.Drawing.get_document_props()
props.should_draw_decorations = True
@classmethod @classmethod
def update_text_value(cls, obj: bpy.types.Object) -> None: def update_text_value(cls, obj: bpy.types.Object) -> None:
@@ -1147,36 +1157,33 @@ class Drawing(bonsai.core.tool.Drawing):
@classmethod @classmethod
def get_default_layout_path(cls, identification: str, name: str) -> str: def get_default_layout_path(cls, identification: str, name: str) -> str:
project = tool.Ifc.get().by_type("IfcProject")[0] project = tool.Ifc.get().by_type("IfcProject")[0]
props = tool.Drawing.get_document_props()
layouts_dir = ( layouts_dir = (
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "LayoutsDir") ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "LayoutsDir") or props.layouts_dir
or bpy.context.scene.DocProperties.layouts_dir
) )
return os.path.join(layouts_dir, cls.sanitise_filename(f"{identification} - {name}.svg")).replace("\\", "/") return os.path.join(layouts_dir, cls.sanitise_filename(f"{identification} - {name}.svg")).replace("\\", "/")
@classmethod @classmethod
def get_default_sheet_path(cls, identification: str, name: str) -> str: def get_default_sheet_path(cls, identification: str, name: str) -> str:
project = tool.Ifc.get().by_type("IfcProject")[0] project = tool.Ifc.get().by_type("IfcProject")[0]
sheets_dir = ( props = tool.Drawing.get_document_props()
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "SheetsDir") sheets_dir = ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "SheetsDir") or props.sheets_dir
or bpy.context.scene.DocProperties.sheets_dir
)
return os.path.join(sheets_dir, cls.sanitise_filename(f"{identification} - {name}.svg")).replace("\\", "/") return os.path.join(sheets_dir, cls.sanitise_filename(f"{identification} - {name}.svg")).replace("\\", "/")
@classmethod @classmethod
def get_default_titleblock_path(cls, name: str) -> str: def get_default_titleblock_path(cls, name: str) -> str:
project = tool.Ifc.get().by_type("IfcProject")[0] project = tool.Ifc.get().by_type("IfcProject")[0]
titleblocks_dir = ( titleblocks_dir = (
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "TitleblocksDir") ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "TitleblocksDir") or props.titleblocks_dir
or bpy.context.scene.DocProperties.titleblocks_dir
) )
return os.path.join(titleblocks_dir, cls.sanitise_filename(f"{name}.svg")).replace("\\", "/") return os.path.join(titleblocks_dir, cls.sanitise_filename(f"{name}.svg")).replace("\\", "/")
@classmethod @classmethod
def get_default_drawing_path(cls, name: str) -> str: def get_default_drawing_path(cls, name: str) -> str:
project = tool.Ifc.get().by_type("IfcProject")[0] project = tool.Ifc.get().by_type("IfcProject")[0]
props = tool.Drawing.get_document_props()
drawings_dir = ( drawings_dir = (
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "DrawingsDir") ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "DrawingsDir") or props.drawings_dir
or bpy.context.scene.DocProperties.drawings_dir
) )
return os.path.join(drawings_dir, cls.sanitise_filename(f"{name}.svg")).replace("\\", "/") return os.path.join(drawings_dir, cls.sanitise_filename(f"{name}.svg")).replace("\\", "/")
@@ -1187,15 +1194,16 @@ class Drawing(bonsai.core.tool.Drawing):
@classmethod @classmethod
def get_default_drawing_resource_path(cls, resource: str) -> Union[str, None]: def get_default_drawing_resource_path(cls, resource: str) -> Union[str, None]:
project = tool.Ifc.get().by_type("IfcProject")[0] project = tool.Ifc.get().by_type("IfcProject")[0]
resource_path = ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", f"{resource}Path") or getattr( props = tool.Drawing.get_document_props()
bpy.context.scene.DocProperties, f"{resource.lower()}_path" resource_path = (
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", f"{resource}Path") or props.resource_path
) )
if resource_path: if resource_path:
return resource_path.replace("\\", "/") return resource_path.replace("\\", "/")
@classmethod @classmethod
def get_default_shading_style(cls) -> str: def get_default_shading_style(cls) -> str:
dprops = bpy.context.scene.DocProperties dprops = tool.Drawing.get_document_props()
return dprops.shadingstyle_default return dprops.shadingstyle_default
@classmethod @classmethod
@@ -1501,7 +1509,7 @@ class Drawing(bonsai.core.tool.Drawing):
dst.data = dst.data.copy() dst.data = dst.data.copy()
dst.name = dst.name.replace("IfcGridAxis/", "") dst.name = dst.name.replace("IfcGridAxis/", "")
dst.BIMObjectProperties.ifc_definition_id = 0 dst.BIMObjectProperties.ifc_definition_id = 0
dst.data.BIMMeshProperties.ifc_definition_id = 0 tool.Geometry.get_geometry_props(dst).ifc_definition_id = 0
return dst return dst
def disassemble(obj: bpy.types.Object) -> tuple[bpy.types.Object, bmesh.types.BMesh]: def disassemble(obj: bpy.types.Object) -> tuple[bpy.types.Object, bmesh.types.BMesh]:
@@ -1883,7 +1891,8 @@ class Drawing(bonsai.core.tool.Drawing):
@classmethod @classmethod
def is_active_drawing(cls, drawing: ifcopenshell.entity_instance) -> bool: def is_active_drawing(cls, drawing: ifcopenshell.entity_instance) -> bool:
return drawing.id() == bpy.context.scene.DocProperties.active_drawing_id props = tool.Drawing.get_document_props()
return drawing.id() == props.active_drawing_id
@classmethod @classmethod
def run_drawing_activate_model(cls) -> None: def run_drawing_activate_model(cls) -> None:
+9 -1
View File
@@ -16,17 +16,25 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>. # along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import bpy import bpy
import bonsai.core.tool import bonsai.core.tool
import bonsai.tool as tool import bonsai.tool as tool
import bonsai.bim.helper import bonsai.bim.helper
import ifcopenshell import ifcopenshell
from typing import Iterable from typing import Iterable, TYPE_CHECKING
if TYPE_CHECKING:
from bonsai.bim.module.void.prop import BIMBooleanProperties
class Feature(bonsai.core.tool.Feature): class Feature(bonsai.core.tool.Feature):
# TODO: consolidate module/model/opening and module/void into new module/feature # TODO: consolidate module/model/opening and module/void into new module/feature
@classmethod
def get_boolean_props(cls) -> BIMBooleanProperties:
return bpy.context.scene.BIMBooleanProperties
@classmethod @classmethod
def add_feature(cls, featured_obj: bpy.types.Object, feature_objs: Iterable[bpy.types.Object]) -> None: def add_feature(cls, featured_obj: bpy.types.Object, feature_objs: Iterable[bpy.types.Object]) -> None:
featured_element = tool.Ifc.get_entity(featured_obj) featured_element = tool.Ifc.get_entity(featured_obj)
+97 -52
View File
@@ -27,6 +27,7 @@ import numpy.typing as npt
import multiprocessing import multiprocessing
import ifcopenshell import ifcopenshell
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.api.geometry
import ifcopenshell.api.grid import ifcopenshell.api.grid
import ifcopenshell.api.profile import ifcopenshell.api.profile
import ifcopenshell.api.style import ifcopenshell.api.style
@@ -53,11 +54,23 @@ from math import radians, pi
from mathutils import Vector, Matrix from mathutils import Vector, Matrix
from mathutils.bvhtree import BVHTree from mathutils.bvhtree import BVHTree
from bonsai.bim.ifc import IfcStore from bonsai.bim.ifc import IfcStore
from typing import Union, Iterable, Optional, Literal, Iterator, List, TYPE_CHECKING, get_args, Generator, cast from typing import (
Union,
Iterable,
Optional,
Literal,
Iterator,
List,
TYPE_CHECKING,
get_args,
Generator,
cast,
TypeGuard,
)
from typing_extensions import TypeIs from typing_extensions import TypeIs
if TYPE_CHECKING: if TYPE_CHECKING:
from bonsai.bim.prop import Attribute from bonsai.bim.prop import Attribute, BIMMeshProperties
from bonsai.bim.module.geometry.prop import BIMObjectGeometryProperties, BIMGeometryProperties from bonsai.bim.module.geometry.prop import BIMObjectGeometryProperties, BIMGeometryProperties
@@ -70,6 +83,10 @@ class Geometry(bonsai.core.tool.Geometry):
def get_object_geometry_props(cls, object: bpy.types.Object) -> BIMObjectGeometryProperties: def get_object_geometry_props(cls, object: bpy.types.Object) -> BIMObjectGeometryProperties:
return object.BIMGeometryProperties return object.BIMGeometryProperties
@classmethod
def get_mesh_props(cls, mesh: TYPES_WITH_MESH_PROPERTIES) -> BIMMeshProperties:
return mesh.BIMMeshProperties
@classmethod @classmethod
def change_object_data(cls, obj: bpy.types.Object, data: bpy.types.ID, is_global: bool = False) -> None: def change_object_data(cls, obj: bpy.types.Object, data: bpy.types.ID, is_global: bool = False) -> None:
if is_global: if is_global:
@@ -182,7 +199,9 @@ class Geometry(bonsai.core.tool.Geometry):
if item_obj.obj == obj: if item_obj.obj == obj:
props.item_objs.remove(i) props.item_objs.remove(i)
break break
item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) mesh = obj.data
assert isinstance(mesh, bpy.types.Mesh)
item = tool.Ifc.get().by_id(tool.Geometry.get_mesh_props(mesh).ifc_definition_id)
cls.remove_representation_item(item) cls.remove_representation_item(item)
cls.reload_representation(props.representation_obj) cls.reload_representation(props.representation_obj)
bpy.data.objects.remove(obj) bpy.data.objects.remove(obj)
@@ -249,17 +268,18 @@ class Geometry(bonsai.core.tool.Geometry):
bonsai.core.system.remove_port(tool.Ifc, tool.System, port=port) bonsai.core.system.remove_port(tool.Ifc, tool.System, port=port)
ifcopenshell.api.run("root.remove_product", tool.Ifc.get(), product=element) ifcopenshell.api.run("root.remove_product", tool.Ifc.get(), product=element)
if isinstance(obj.data, bpy.types.Mesh) and not tool.Ifc.get_entity_by_id( mesh = obj.data
obj.data.BIMMeshProperties.ifc_definition_id assert isinstance(mesh, bpy.types.Mesh)
): if not tool.Ifc.get_entity_by_id(tool.Geometry.get_mesh_props(mesh).ifc_definition_id):
tool.Blender.remove_data_block(obj.data) tool.Blender.remove_data_block(mesh)
if is_spatial: if is_spatial:
bonsai.core.spatial.import_spatial_decomposition(tool.Spatial) bonsai.core.spatial.import_spatial_decomposition(tool.Spatial)
try: try:
obj.name obj.name
if bpy.context.scene.BIMGeometryProperties.representation_obj == obj: props = tool.Geometry.get_geometry_props()
bpy.context.scene.BIMGeometryProperties.representation_obj = None if props.representation_obj == obj:
props.representation_obj = None
bpy.data.objects.remove(obj) bpy.data.objects.remove(obj)
except: except:
pass pass
@@ -268,7 +288,9 @@ class Geometry(bonsai.core.tool.Geometry):
def dissolve_triangulated_edges(cls, obj: bpy.types.Object) -> None: def dissolve_triangulated_edges(cls, obj: bpy.types.Object) -> None:
# AdvancedBreps may contain non-faceted, curved faces (e.g. as part of # AdvancedBreps may contain non-faceted, curved faces (e.g. as part of
# a cylinder) so dissolving edges should not be allowed. # a cylinder) so dissolving edges should not be allowed.
mesh_element = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) mesh = obj.data
assert isinstance(mesh, Geometry.TYPES_WITH_MESH_PROPERTIES)
mesh_element = tool.Ifc.get().by_id(tool.Geometry.get_mesh_props(mesh).ifc_definition_id)
if ( if (
( (
mesh_element.is_a("IfcShapeRepresentation") mesh_element.is_a("IfcShapeRepresentation")
@@ -279,26 +301,30 @@ class Geometry(bonsai.core.tool.Geometry):
or not obj.data or not obj.data
): ):
return return
if hasattr(obj.data, "attributes") and (ios_edges_attribute := obj.data.attributes.get("ios_edges")):
if not isinstance(mesh, bpy.types.Mesh):
return
if hasattr(mesh, "attributes") and (ios_edges_attribute := mesh.attributes.get("ios_edges")):
# Edges from a forced triangulation are stored as True in a boolean attribute on the mesh # Edges from a forced triangulation are stored as True in a boolean attribute on the mesh
bm = bmesh.new() bm = bmesh.new()
bm.from_mesh(obj.data) bm.from_mesh(mesh)
edges_to_dissolve = [e for i, e in enumerate(bm.edges) if not ios_edges_attribute.data[i].value] edges_to_dissolve = [e for i, e in enumerate(bm.edges) if not ios_edges_attribute.data[i].value]
bmesh.ops.dissolve_edges(bm, edges=edges_to_dissolve) bmesh.ops.dissolve_edges(bm, edges=edges_to_dissolve)
bm.to_mesh(obj.data) bm.to_mesh(mesh)
bm.free() bm.free()
elif "ios_edges" in obj.data: elif "ios_edges" in mesh:
bm = bmesh.new() bm = bmesh.new()
bm.from_mesh(obj.data) bm.from_mesh(mesh)
edges_to_keep = set(map(frozenset, obj.data["ios_edges"])) edges_to_keep = set(map(frozenset, mesh["ios_edges"]))
edges_to_dissolve = [] edges_to_dissolve = []
for edge in bm.edges: for edge in bm.edges:
if frozenset([vert.index for vert in edge.verts]) not in edges_to_keep: if frozenset([vert.index for vert in edge.verts]) not in edges_to_keep:
edges_to_dissolve.append(edge) edges_to_dissolve.append(edge)
bmesh.ops.dissolve_edges(bm, edges=edges_to_dissolve) bmesh.ops.dissolve_edges(bm, edges=edges_to_dissolve)
bm.to_mesh(obj.data) bm.to_mesh(mesh)
bm.free() bm.free()
del obj.data["ios_edges"] del mesh["ios_edges"]
@classmethod @classmethod
def apply_item_ids_as_vertex_groups(cls, obj: bpy.types.Object) -> None: def apply_item_ids_as_vertex_groups(cls, obj: bpy.types.Object) -> None:
@@ -486,13 +512,19 @@ class Geometry(bonsai.core.tool.Geometry):
@classmethod @classmethod
def get_active_representation(cls, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]: def get_active_representation(cls, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
""":return: IfcRepresentation/IfcRepresentationItem or None""" """:return: IfcRepresentation/IfcRepresentationItem or None"""
if obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.ifc_definition_id: if (
return tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) (data := obj.data)
and isinstance(data, Geometry.TYPES_WITH_MESH_PROPERTIES)
and (ifc_id := tool.Geometry.get_mesh_props(data).ifc_definition_id)
):
return tool.Ifc.get().by_id(ifc_id)
@classmethod @classmethod
def get_data_representation(cls, data: bpy.types.Mesh) -> ifcopenshell.entity_instance | None: def get_data_representation(cls, data: bpy.types.ID) -> ifcopenshell.entity_instance | None:
if hasattr(data, "BIMMeshProperties") and data.BIMMeshProperties.ifc_definition_id: if isinstance(data, Geometry.TYPES_WITH_MESH_PROPERTIES) and (
return tool.Ifc.get().by_id(data.BIMMeshProperties.ifc_definition_id) ifc_id := tool.Geometry.get_mesh_props(data).ifc_definition_id
):
return tool.Ifc.get().by_id(ifc_id)
@classmethod @classmethod
def get_active_representation_context(cls, obj: bpy.types.Object) -> ifcopenshell.entity_instance: def get_active_representation_context(cls, obj: bpy.types.Object) -> ifcopenshell.entity_instance:
@@ -670,13 +702,13 @@ class Geometry(bonsai.core.tool.Geometry):
return data.users != 0 return data.users != 0
@classmethod @classmethod
def has_geometric_data(cls, obj: bpy.types.Object) -> bool: def is_geometric_data(cls, data: Union[bpy.types.ID, None]) -> TypeGuard[Union[bpy.types.Mesh, bpy.types.Curve]]:
if not obj.data: if not data:
return False return False
if isinstance(obj.data, bpy.types.Mesh): if isinstance(data, bpy.types.Mesh):
return bool(obj.data.vertices) return bool(data.vertices)
elif isinstance(obj.data, bpy.types.Curve): elif isinstance(data, bpy.types.Curve):
return bool(obj.data.splines) return bool(data.splines)
return False return False
@classmethod @classmethod
@@ -826,7 +858,8 @@ class Geometry(bonsai.core.tool.Geometry):
ifc_importer.material_creator.load_existing_materials() ifc_importer.material_creator.load_existing_materials()
shape_has_openings = cls.does_shape_has_openings(shape) shape_has_openings = cls.does_shape_has_openings(shape)
ifc_importer.material_creator.create(element, obj, mesh, shape_has_openings) ifc_importer.material_creator.create(element, obj, mesh, shape_has_openings)
mesh.BIMMeshProperties.has_openings_applied = apply_openings mprops = tool.Geometry.get_mesh_props(mesh)
mprops.has_openings_applied = apply_openings
if not shape_has_openings: if not shape_has_openings:
tool.Loader.load_indexed_colour_map(representation, mesh) tool.Loader.load_indexed_colour_map(representation, mesh)
tool.Loader.link_mesh(shape, mesh) tool.Loader.link_mesh(shape, mesh)
@@ -852,7 +885,8 @@ class Geometry(bonsai.core.tool.Geometry):
ifc_importer.material_creator.load_existing_materials() ifc_importer.material_creator.load_existing_materials()
shape_has_openings = False shape_has_openings = False
ifc_importer.material_creator.create(element, obj, mesh, shape_has_openings) ifc_importer.material_creator.create(element, obj, mesh, shape_has_openings)
mesh.BIMMeshProperties.has_openings_applied = apply_openings mprops = tool.Geometry.get_mesh_props(mesh)
mprops.has_openings_applied = apply_openings
if not shape_has_openings: if not shape_has_openings:
tool.Loader.load_indexed_colour_map(representation, mesh) tool.Loader.load_indexed_colour_map(representation, mesh)
meshes[mesh_name] = mesh meshes[mesh_name] = mesh
@@ -867,7 +901,7 @@ class Geometry(bonsai.core.tool.Geometry):
@classmethod @classmethod
def import_representation_parameters(cls, data: bpy.types.Mesh) -> None: def import_representation_parameters(cls, data: bpy.types.Mesh) -> None:
props = data.BIMMeshProperties props = tool.Geometry.get_mesh_props(data)
elements = tool.Ifc.get().traverse(tool.Ifc.get().by_id(props.ifc_definition_id)) elements = tool.Ifc.get().traverse(tool.Ifc.get().by_id(props.ifc_definition_id))
props.ifc_parameters.clear() props.ifc_parameters.clear()
for element in elements: for element in elements:
@@ -974,7 +1008,8 @@ class Geometry(bonsai.core.tool.Geometry):
@classmethod @classmethod
def is_profile_based(cls, data: bpy.types.Mesh) -> bool: def is_profile_based(cls, data: bpy.types.Mesh) -> bool:
return data.BIMMeshProperties.subshape_type == "PROFILE" props = tool.Geometry.get_mesh_props(data)
return props.subshape_type == "PROFILE"
@classmethod @classmethod
def is_profile_object_active(cls) -> bool: def is_profile_object_active(cls) -> bool:
@@ -992,7 +1027,7 @@ class Geometry(bonsai.core.tool.Geometry):
data = obj.data data = obj.data
if ( if (
isinstance(data, Geometry.TYPES_WITH_MESH_PROPERTIES) isinstance(data, Geometry.TYPES_WITH_MESH_PROPERTIES)
and (ifc_id := data.BIMMeshProperties.ifc_definition_id) and (ifc_id := tool.Geometry.get_mesh_props(data).ifc_definition_id)
and ((item := tool.Ifc.get().by_id(ifc_id)).is_a("IfcRepresentationItem")) and ((item := tool.Ifc.get().by_id(ifc_id)).is_a("IfcRepresentationItem"))
): ):
return item return item
@@ -1008,14 +1043,14 @@ class Geometry(bonsai.core.tool.Geometry):
if tool.Ifc.get_entity(obj): if tool.Ifc.get_entity(obj):
return obj return obj
elif tool.Geometry.is_representation_item(obj): elif tool.Geometry.is_representation_item(obj):
return bpy.context.scene.BIMGeometryProperties.representation_obj return tool.Geometry.get_geometry_props().representation_obj
@classmethod @classmethod
def is_boolean_operand(cls, obj: bpy.types.Object) -> bool: def is_boolean_operand(cls, obj: bpy.types.Object) -> bool:
return bool( return bool(
(data := obj.data) (data := obj.data)
and isinstance(data, Geometry.TYPES_WITH_MESH_PROPERTIES) and isinstance(data, Geometry.TYPES_WITH_MESH_PROPERTIES)
and (ifc_id := data.BIMMeshProperties.ifc_definition_id) and (ifc_id := tool.Geometry.get_mesh_props(data).ifc_definition_id)
and (item := tool.Ifc.get().by_id(ifc_id)) and (item := tool.Ifc.get().by_id(ifc_id))
and ( and (
item.is_a("IfcBooleanResult") item.is_a("IfcBooleanResult")
@@ -1041,7 +1076,8 @@ class Geometry(bonsai.core.tool.Geometry):
@classmethod @classmethod
def record_object_materials(cls, obj: bpy.types.Object) -> None: def record_object_materials(cls, obj: bpy.types.Object) -> None:
obj.data.BIMMeshProperties.material_checksum = cls.get_material_checksum(obj) props = tool.Geometry.get_mesh_props(obj.data)
props.material_checksum = cls.get_material_checksum(obj)
@classmethod @classmethod
def record_object_position(cls, obj: bpy.types.Object) -> None: def record_object_position(cls, obj: bpy.types.Object) -> None:
@@ -1146,11 +1182,13 @@ class Geometry(bonsai.core.tool.Geometry):
@classmethod @classmethod
def should_force_faceted_brep(cls) -> bool: def should_force_faceted_brep(cls) -> bool:
return bpy.context.scene.BIMGeometryProperties.should_force_faceted_brep props = tool.Geometry.get_geometry_props()
return props.should_force_faceted_brep
@classmethod @classmethod
def should_force_triangulation(cls) -> bool: def should_force_triangulation(cls) -> bool:
return bpy.context.scene.BIMGeometryProperties.should_force_triangulation props = tool.Geometry.get_geometry_props()
return props.should_force_triangulation
@classmethod @classmethod
def should_generate_uvs(cls, obj: bpy.types.Object) -> bool: def should_generate_uvs(cls, obj: bpy.types.Object) -> bool:
@@ -1167,7 +1205,8 @@ class Geometry(bonsai.core.tool.Geometry):
@classmethod @classmethod
def should_use_presentation_style_assignment(cls) -> bool: def should_use_presentation_style_assignment(cls) -> bool:
return bpy.context.scene.BIMGeometryProperties.should_use_presentation_style_assignment props = tool.Geometry.get_geometry_props()
return props.should_use_presentation_style_assignment
@classmethod @classmethod
def get_model_representations(cls) -> list[ifcopenshell.entity_instance]: def get_model_representations(cls) -> list[ifcopenshell.entity_instance]:
@@ -1238,7 +1277,8 @@ class Geometry(bonsai.core.tool.Geometry):
In the most cases just use reload_representation In the most cases just use reload_representation
as it will handle those complications by itself. as it will handle those complications by itself.
""" """
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) representation = cls.get_active_representation(obj)
assert representation
bonsai.core.geometry.switch_representation( bonsai.core.geometry.switch_representation(
tool.Ifc, tool.Ifc,
tool.Geometry, tool.Geometry,
@@ -1526,7 +1566,7 @@ class Geometry(bonsai.core.tool.Geometry):
@classmethod @classmethod
def get_blender_offset_type(cls, obj: bpy.types.Object) -> Optional[str]: def get_blender_offset_type(cls, obj: bpy.types.Object) -> Optional[str]:
props = bpy.context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
if props.has_blender_offset: if props.has_blender_offset:
if (result := obj.BIMObjectProperties.blender_offset_type) == "NONE": if (result := obj.BIMObjectProperties.blender_offset_type) == "NONE":
result = obj.BIMObjectProperties.blender_offset_type = "OBJECT_PLACEMENT" result = obj.BIMObjectProperties.blender_offset_type = "OBJECT_PLACEMENT"
@@ -1641,7 +1681,8 @@ class Geometry(bonsai.core.tool.Geometry):
for item_obj in props.item_objs: for item_obj in props.item_objs:
if not (obj := item_obj.obj) or not tool.Ifc.is_moved(obj): if not (obj := item_obj.obj) or not tool.Ifc.is_moved(obj):
continue continue
item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) item = cls.get_active_representation(obj)
assert item
if item.is_a("IfcSweptAreaSolid"): if item.is_a("IfcSweptAreaSolid"):
has_changed = True has_changed = True
old_position = item.Position old_position = item.Position
@@ -1683,7 +1724,7 @@ class Geometry(bonsai.core.tool.Geometry):
@classmethod @classmethod
def import_item_attributes(cls, obj: bpy.types.Object) -> None: def import_item_attributes(cls, obj: bpy.types.Object) -> None:
props = obj.data.BIMMeshProperties props = tool.Geometry.get_mesh_props(obj.data)
props.item_attributes.clear() props.item_attributes.clear()
item = tool.Ifc.get().by_id(props.ifc_definition_id) item = tool.Ifc.get().by_id(props.ifc_definition_id)
allowed_attributes = [ allowed_attributes = [
@@ -1710,10 +1751,10 @@ class Geometry(bonsai.core.tool.Geometry):
@classmethod @classmethod
def update_item_attributes(cls, obj: bpy.types.Object) -> None: def update_item_attributes(cls, obj: bpy.types.Object) -> None:
props = obj.data.BIMMeshProperties props = tool.Geometry.get_mesh_props(obj.data)
ifc_file = tool.Ifc.get() ifc_file = tool.Ifc.get()
item = tool.Ifc.get().by_id(props.ifc_definition_id) item = ifc_file.by_id(props.ifc_definition_id)
for attribute in props.item_attributes: for attribute in props.item_attributes:
setattr(item, attribute.name, attribute.get_value()) setattr(item, attribute.name, attribute.get_value())
@@ -1738,7 +1779,9 @@ class Geometry(bonsai.core.tool.Geometry):
tool.Loader.settings.contexts = ifcopenshell.util.representation.get_prioritised_contexts(tool.Ifc.get()) tool.Loader.settings.contexts = ifcopenshell.util.representation.get_prioritised_contexts(tool.Ifc.get())
tool.Loader.settings.context_settings = tool.Loader.create_settings() tool.Loader.settings.context_settings = tool.Loader.create_settings()
tool.Loader.settings.gross_context_settings = tool.Loader.create_settings(is_gross=True) tool.Loader.settings.gross_context_settings = tool.Loader.create_settings(is_gross=True)
item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) assert isinstance(obj.data, bpy.types.Mesh)
item = tool.Geometry.get_active_representation(obj)
assert item
obj.data.clear_geometry() obj.data.clear_geometry()
if item.is_a("IfcHalfSpaceSolid"): if item.is_a("IfcHalfSpaceSolid"):
@@ -1802,12 +1845,14 @@ class Geometry(bonsai.core.tool.Geometry):
props.mode = "OBJECT" props.mode = "OBJECT"
props.is_changing_mode = False props.is_changing_mode = False
props.representation_obj = None props.representation_obj = None
bpy.context.scene.BIMBooleanProperties.is_editing = False tool.Feature.get_boolean_props().is_editing = False
@classmethod @classmethod
def edit_meshlike_item(cls, obj: bpy.types.Object) -> None: def edit_meshlike_item(cls, obj: bpy.types.Object) -> None:
item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) item = tool.Geometry.get_active_representation(obj)
if obj.data.BIMMeshProperties.mesh_checksum == cls.get_mesh_checksum(obj.data): assert item
mprops = tool.Geometry.get_mesh_props(obj.data)
if mprops.mesh_checksum == cls.get_mesh_checksum(obj.data):
return return
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get()) builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
@@ -1830,8 +1875,8 @@ class Geometry(bonsai.core.tool.Geometry):
for inverse in tool.Ifc.get().get_inverse(item): for inverse in tool.Ifc.get().get_inverse(item):
ifcopenshell.util.element.replace_attribute(inverse, item, new_item) ifcopenshell.util.element.replace_attribute(inverse, item, new_item)
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), item) ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), item)
obj.data.BIMMeshProperties.ifc_definition_id = new_item.id() tool.Ifc.link(new_item, obj.data)
cls.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj) cls.reload_representation(props.representation_obj)
@classmethod @classmethod
def split_by_loose_parts(cls, obj: bpy.types.Object) -> List[bpy.types.Mesh]: def split_by_loose_parts(cls, obj: bpy.types.Object) -> List[bpy.types.Mesh]:
+44 -27
View File
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>. # along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import bpy import bpy
import json import json
import numpy as np import numpy as np
@@ -27,24 +28,34 @@ import ifcopenshell.util.unit
import bonsai.core.tool import bonsai.core.tool
import bonsai.tool as tool import bonsai.tool as tool
import bonsai.bim.helper import bonsai.bim.helper
from typing import Any, Union, Literal from typing import Any, Union, Literal, TYPE_CHECKING
if TYPE_CHECKING:
from bonsai.bim.module.georeference.prop import BIMGeoreferenceProperties
class Georeference(bonsai.core.tool.Georeference): class Georeference(bonsai.core.tool.Georeference):
COORDINATE_TYPE = Literal["blender", "local", "map"] COORDINATE_TYPE = Literal["blender", "local", "map"]
@classmethod
def get_georeference_props(cls) -> BIMGeoreferenceProperties:
return bpy.context.scene.BIMGeoreferenceProperties
@classmethod @classmethod
def add_georeferencing(cls) -> None: def add_georeferencing(cls) -> None:
props = cls.get_georeference_props()
tool.Ifc.run( tool.Ifc.run(
"georeference.add_georeferencing", "georeference.add_georeferencing",
ifc_class=bpy.context.scene.BIMGeoreferenceProperties.coordinate_operation_class, ifc_class=props.coordinate_operation_class,
) )
@classmethod @classmethod
def import_projected_crs(cls) -> None: def import_projected_crs(cls) -> None:
props = tool.Georeference.get_georeference_props()
def callback(name, prop, data): def callback(name, prop, data):
if name == "MapUnit": if name == "MapUnit":
new = bpy.context.scene.BIMGeoreferenceProperties.projected_crs.add() new = props.projected_crs.add()
new.name = name new.name = name
new.data_type = "enum" new.data_type = "enum"
new.is_null = data[name] is None new.is_null = data[name] is None
@@ -61,7 +72,6 @@ class Georeference(bonsai.core.tool.Georeference):
new.update = "tool.Georeference.update_map_unit" new.update = "tool.Georeference.update_map_unit"
return True return True
props = bpy.context.scene.BIMGeoreferenceProperties
props.projected_crs.clear() props.projected_crs.clear()
if tool.Ifc.get_schema() == "IFC2X3": if tool.Ifc.get_schema() == "IFC2X3":
@@ -84,7 +94,8 @@ class Georeference(bonsai.core.tool.Georeference):
result = 1.0 result = 1.0
else: else:
result = 1.0 result = 1.0
for attribute in bpy.context.scene.BIMGeoreferenceProperties.coordinate_operation: props = cls.get_georeference_props()
for attribute in props.coordinate_operation:
if attribute.name == "Scale": if attribute.name == "Scale":
attribute.set_value(str(result)) attribute.set_value(str(result))
@@ -92,7 +103,7 @@ class Georeference(bonsai.core.tool.Georeference):
def import_coordinate_operation(cls) -> None: def import_coordinate_operation(cls) -> None:
def callback(name, prop, data): def callback(name, prop, data):
if name in ("FirstCoordinate", "SecondCoordinate"): if name in ("FirstCoordinate", "SecondCoordinate"):
props = bpy.context.scene.BIMGeoreferenceProperties props = cls.get_georeference_props()
if name == "FirstCoordinate": if name == "FirstCoordinate":
new = props.coordinate_operation.add() new = props.coordinate_operation.add()
new.name = "Measure Type" new.name = "Measure Type"
@@ -110,7 +121,7 @@ class Georeference(bonsai.core.tool.Georeference):
prop.string_value = "" if prop.is_null else str(data[name].wrappedValue) prop.string_value = "" if prop.is_null else str(data[name].wrappedValue)
return True return True
elif name == "XAxisAbscissa": elif name == "XAxisAbscissa":
props = bpy.context.scene.BIMGeoreferenceProperties props = cls.get_georeference_props()
props.is_changing_angle = True props.is_changing_angle = True
if data["XAxisAbscissa"] is None or data["XAxisOrdinate"] is None: if data["XAxisAbscissa"] is None or data["XAxisOrdinate"] is None:
props.x_axis_is_null = True props.x_axis_is_null = True
@@ -132,7 +143,7 @@ class Georeference(bonsai.core.tool.Georeference):
prop.string_value = "" if prop.is_null else str(data[name]) prop.string_value = "" if prop.is_null else str(data[name])
return True return True
props = bpy.context.scene.BIMGeoreferenceProperties props = cls.get_georeference_props()
props.coordinate_operation.clear() props.coordinate_operation.clear()
if tool.Ifc.get_schema() == "IFC2X3": if tool.Ifc.get_schema() == "IFC2X3":
@@ -151,7 +162,7 @@ class Georeference(bonsai.core.tool.Georeference):
if tool.Ifc.get_schema() == "IFC2X3": if tool.Ifc.get_schema() == "IFC2X3":
return return
props = bpy.context.scene.BIMGeoreferenceProperties props = cls.get_georeference_props()
props.is_changing_angle = True props.is_changing_angle = True
props.true_north_abscissa = "0" props.true_north_abscissa = "0"
props.true_north_ordinate = "1" props.true_north_ordinate = "1"
@@ -175,7 +186,7 @@ class Georeference(bonsai.core.tool.Georeference):
attributes[prop.name] = tool.Ifc.get().by_id(int(prop.enum_value)) attributes[prop.name] = tool.Ifc.get().by_id(int(prop.enum_value))
return True return True
props = bpy.context.scene.BIMGeoreferenceProperties props = cls.get_georeference_props()
return bonsai.bim.helper.export_attributes(props.projected_crs, callback=callback) return bonsai.bim.helper.export_attributes(props.projected_crs, callback=callback)
@classmethod @classmethod
@@ -191,7 +202,7 @@ class Georeference(bonsai.core.tool.Georeference):
attributes[prop.name] = tool.Ifc.get().create_entity(measure_type, float(prop.string_value)) attributes[prop.name] = tool.Ifc.get().create_entity(measure_type, float(prop.string_value))
return True return True
elif prop.name == "XAxisAbscissa": elif prop.name == "XAxisAbscissa":
props = bpy.context.scene.BIMGeoreferenceProperties props = cls.get_georeference_props()
if props.x_axis_is_null: if props.x_axis_is_null:
attributes["XAxisAbscissa"] = None attributes["XAxisAbscissa"] = None
attributes["XAxisOrdinate"] = None attributes["XAxisOrdinate"] = None
@@ -206,12 +217,12 @@ class Georeference(bonsai.core.tool.Georeference):
attributes[prop.name] = float(prop.string_value) attributes[prop.name] = float(prop.string_value)
return True return True
props = bpy.context.scene.BIMGeoreferenceProperties props = cls.get_georeference_props()
return bonsai.bim.helper.export_attributes(props.coordinate_operation, callback=callback) return bonsai.bim.helper.export_attributes(props.coordinate_operation, callback=callback)
@classmethod @classmethod
def get_true_north_attributes(cls) -> Union[list[float], None]: def get_true_north_attributes(cls) -> Union[list[float], None]:
props = bpy.context.scene.BIMGeoreferenceProperties props = cls.get_georeference_props()
try: try:
return [float(props.true_north_abscissa), float(props.true_north_ordinate)] return [float(props.true_north_abscissa), float(props.true_north_ordinate)]
except ValueError: except ValueError:
@@ -219,36 +230,42 @@ class Georeference(bonsai.core.tool.Georeference):
@classmethod @classmethod
def enable_editing(cls) -> None: def enable_editing(cls) -> None:
bpy.context.scene.BIMGeoreferenceProperties.is_editing = True props = cls.get_georeference_props()
props.is_editing = True
@classmethod @classmethod
def disable_editing(cls) -> None: def disable_editing(cls) -> None:
bpy.context.scene.BIMGeoreferenceProperties.is_editing = False props = cls.get_georeference_props()
props.is_editing = False
@classmethod @classmethod
def enable_editing_wcs(cls) -> None: def enable_editing_wcs(cls) -> None:
bpy.context.scene.BIMGeoreferenceProperties.is_editing_wcs = True props = cls.get_georeference_props()
props.is_editing_wcs = True
@classmethod @classmethod
def disable_editing_wcs(cls) -> None: def disable_editing_wcs(cls) -> None:
bpy.context.scene.BIMGeoreferenceProperties.is_editing_wcs = False props = cls.get_georeference_props()
props.is_editing_wcs = False
@classmethod @classmethod
def enable_editing_true_north(cls) -> None: def enable_editing_true_north(cls) -> None:
bpy.context.scene.BIMGeoreferenceProperties.is_editing_true_north = True props = cls.get_georeference_props()
props.is_editing_true_north = True
@classmethod @classmethod
def disable_editing_true_north(cls) -> None: def disable_editing_true_north(cls) -> None:
bpy.context.scene.BIMGeoreferenceProperties.is_editing_true_north = False props = cls.get_georeference_props()
props.is_editing_true_north = False
@classmethod @classmethod
def set_coordinates(cls, io: COORDINATE_TYPE, coordinates: list[float]) -> None: def set_coordinates(cls, io: COORDINATE_TYPE, coordinates: list[float]) -> None:
props = bpy.context.scene.BIMGeoreferenceProperties props = cls.get_georeference_props()
setattr(props, f"{io}_coordinates", ",".join([str(o) for o in coordinates])) setattr(props, f"{io}_coordinates", ",".join([str(o) for o in coordinates]))
@classmethod @classmethod
def get_coordinates(cls, io: COORDINATE_TYPE) -> list[float]: def get_coordinates(cls, io: COORDINATE_TYPE) -> list[float]:
props = bpy.context.scene.BIMGeoreferenceProperties props = cls.get_georeference_props()
return [float(co) for co in getattr(props, f"{io}_coordinates").split(",")] return [float(co) for co in getattr(props, f"{io}_coordinates").split(",")]
@classmethod @classmethod
@@ -260,7 +277,7 @@ class Georeference(bonsai.core.tool.Georeference):
def xyz2enh( def xyz2enh(
cls, coordinates: tuple[float, float, float], should_return_in_map_units: bool = True cls, coordinates: tuple[float, float, float], should_return_in_map_units: bool = True
) -> tuple[float, float, float]: ) -> tuple[float, float, float]:
props = bpy.context.scene.BIMGeoreferenceProperties props = cls.get_georeference_props()
if props.has_blender_offset: if props.has_blender_offset:
coordinates = ifcopenshell.util.geolocation.xyz2enh( coordinates = ifcopenshell.util.geolocation.xyz2enh(
coordinates[0], coordinates[0],
@@ -279,7 +296,7 @@ class Georeference(bonsai.core.tool.Georeference):
@classmethod @classmethod
def enh2xyz(cls, coordinates: tuple[float, float, float]) -> tuple[float, float, float]: def enh2xyz(cls, coordinates: tuple[float, float, float]) -> tuple[float, float, float]:
coordinates = ifcopenshell.util.geolocation.auto_enh2xyz(tool.Ifc.get(), *coordinates) coordinates = ifcopenshell.util.geolocation.auto_enh2xyz(tool.Ifc.get(), *coordinates)
props = bpy.context.scene.BIMGeoreferenceProperties props = cls.get_georeference_props()
if props.has_blender_offset: if props.has_blender_offset:
coordinates = ifcopenshell.util.geolocation.enh2xyz( coordinates = ifcopenshell.util.geolocation.enh2xyz(
coordinates[0], coordinates[0],
@@ -329,7 +346,7 @@ class Georeference(bonsai.core.tool.Georeference):
@classmethod @classmethod
def import_wcs(cls) -> None: def import_wcs(cls) -> None:
props = bpy.context.scene.BIMGeoreferenceProperties props = cls.get_georeference_props()
wcs = None wcs = None
for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False): for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
wcs = context.WorldCoordinateSystem wcs = context.WorldCoordinateSystem
@@ -346,7 +363,7 @@ class Georeference(bonsai.core.tool.Georeference):
@classmethod @classmethod
def export_wcs(cls) -> dict[str, float]: def export_wcs(cls) -> dict[str, float]:
props = bpy.context.scene.BIMGeoreferenceProperties props = cls.get_georeference_props()
return { return {
"x": float(props.wcs_x), "x": float(props.wcs_x),
"y": float(props.wcs_y), "y": float(props.wcs_y),
@@ -361,7 +378,7 @@ class Georeference(bonsai.core.tool.Georeference):
@classmethod @classmethod
def set_model_origin(cls) -> None: def set_model_origin(cls) -> None:
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
gprops = bpy.context.scene.BIMGeoreferenceProperties gprops = tool.Georeference.get_georeference_props()
e, n, h = cls.xyz2enh((0, 0, 0), should_return_in_map_units=False) e, n, h = cls.xyz2enh((0, 0, 0), should_return_in_map_units=False)
gprops.model_origin = f"{e},{n},{h}" gprops.model_origin = f"{e},{n},{h}"
gprops.model_origin_si = f"{e * unit_scale},{n * unit_scale},{h * unit_scale}" gprops.model_origin_si = f"{e * unit_scale},{n * unit_scale},{h * unit_scale}"
@@ -375,4 +392,4 @@ class Georeference(bonsai.core.tool.Georeference):
@classmethod @classmethod
def has_blender_offset(cls) -> bool: def has_blender_offset(cls) -> bool:
return bpy.context.scene.BIMGeoreferenceProperties.has_blender_offset return tool.Georeference.get_georeference_props().has_blender_offset
+4 -4
View File
@@ -111,7 +111,7 @@ class Ifc(bonsai.core.tool.Ifc):
elif isinstance(obj, bpy.types.Material): elif isinstance(obj, bpy.types.Material):
props = obj.BIMStyleProperties props = obj.BIMStyleProperties
else: else:
props = obj.BIMMeshProperties props = tool.Geometry.get_mesh_props(obj)
if props and (ifc_definition_id := props.ifc_definition_id): if props and (ifc_definition_id := props.ifc_definition_id):
try: try:
@@ -180,7 +180,7 @@ class Ifc(bonsai.core.tool.Ifc):
cls.setup_listeners(obj) cls.setup_listeners(obj)
IfcStore.edited_objs = set() IfcStore.edited_objs = set()
edited_objs = bpy.context.scene.BIMProjectProperties.edited_objs edited_objs = tool.Project.get_project_props().edited_objs
for i in range(len(edited_objs))[::-1]: for i in range(len(edited_objs))[::-1]:
obj = edited_objs[i].obj obj = edited_objs[i].obj
if obj: if obj:
@@ -220,7 +220,7 @@ class Ifc(bonsai.core.tool.Ifc):
""" """
if obj in IfcStore.edited_objs: if obj in IfcStore.edited_objs:
return return
edited_objs = bpy.context.scene.BIMProjectProperties.edited_objs edited_objs = tool.Project.get_project_props().edited_objs
edited_objs.add().obj = obj edited_objs.add().obj = obj
IfcStore.edited_objs.add(obj) IfcStore.edited_objs.add(obj)
IfcStore.history_edit_object(obj, finish_editing=False) IfcStore.history_edit_object(obj, finish_editing=False)
@@ -233,7 +233,7 @@ class Ifc(bonsai.core.tool.Ifc):
""" """
if obj not in IfcStore.edited_objs: if obj not in IfcStore.edited_objs:
return return
edited_objs = bpy.context.scene.BIMProjectProperties.edited_objs edited_objs = tool.Project.get_project_props().edited_objs
edited_objs.remove(next(i for i, o in enumerate(edited_objs) if o.obj == obj)) edited_objs.remove(next(i for i, o in enumerate(edited_objs) if o.obj == obj))
IfcStore.edited_objs.discard(obj) IfcStore.edited_objs.discard(obj)
IfcStore.history_edit_object(obj, finish_editing=True) IfcStore.history_edit_object(obj, finish_editing=True)
+5 -5
View File
@@ -89,7 +89,7 @@ class Loader(bonsai.core.tool.Loader):
@classmethod @classmethod
def get_mesh_name(cls, representation: ifcopenshell.entity_instance) -> str: def get_mesh_name(cls, representation: ifcopenshell.entity_instance) -> str:
context_id = representation.ContextOfItems.id() if hasattr(representation, "ContextOfItems") else 0 context_id = context.id() if (context := getattr(representation, "ContextOfItems", None)) else 0
return "{}/{}".format(context_id, representation.id()) return "{}/{}".format(context_id, representation.id())
@classmethod @classmethod
@@ -105,7 +105,7 @@ class Loader(bonsai.core.tool.Loader):
mesh: tool.Geometry.TYPES_WITH_MESH_PROPERTIES, mesh: tool.Geometry.TYPES_WITH_MESH_PROPERTIES,
) -> None: ) -> None:
geometry = shape.geometry if hasattr(shape, "geometry") else shape geometry = shape.geometry if hasattr(shape, "geometry") else shape
mesh.BIMMeshProperties.ifc_definition_id = int(geometry.id.split("-")[0]) tool.Geometry.get_mesh_props(mesh).ifc_definition_id = int(geometry.id.split("-")[0])
@classmethod @classmethod
def create_surface_style_shading( def create_surface_style_shading(
@@ -698,7 +698,7 @@ class Loader(bonsai.core.tool.Loader):
project_north = 0 project_north = 0
if has_offset or has_rotation: if has_offset or has_rotation:
props = bpy.context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
props.blender_offset_x = str(model_offset[0]) props.blender_offset_x = str(model_offset[0])
props.blender_offset_y = str(model_offset[1]) props.blender_offset_y = str(model_offset[1])
props.blender_offset_z = str(model_offset[2]) props.blender_offset_z = str(model_offset[2])
@@ -747,7 +747,7 @@ class Loader(bonsai.core.tool.Loader):
cls, element: ifcopenshell.entity_instance, is_gross: bool = False cls, element: ifcopenshell.entity_instance, is_gross: bool = False
) -> Union[ifcopenshell.geom.ShapeElementType, None]: ) -> Union[ifcopenshell.geom.ShapeElementType, None]:
context_settings = cls.settings.gross_context_settings if is_gross else cls.settings.context_settings context_settings = cls.settings.gross_context_settings if is_gross else cls.settings.context_settings
geometry_library = bpy.context.scene.BIMProjectProperties.geometry_library geometry_library = tool.Project.get_project_props().geometry_library
for settings in context_settings: for settings in context_settings:
try: try:
result = ifcopenshell.geom.create_shape(settings, element, geometry_library=geometry_library) result = ifcopenshell.geom.create_shape(settings, element, geometry_library=geometry_library)
@@ -952,7 +952,7 @@ class Loader(bonsai.core.tool.Loader):
matrix[1][3] = offset_xyz[1] matrix[1][3] = offset_xyz[1]
matrix[2][3] = offset_xyz[2] matrix[2][3] = offset_xyz[2]
props = bpy.context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
if props.has_blender_offset: if props.has_blender_offset:
if obj.BIMObjectProperties.blender_offset_type == "NONE": if obj.BIMObjectProperties.blender_offset_type == "NONE":
obj.BIMObjectProperties.blender_offset_type = "OBJECT_PLACEMENT" obj.BIMObjectProperties.blender_offset_type = "OBJECT_PLACEMENT"
+3 -2
View File
@@ -113,10 +113,11 @@ class Misc(bonsai.core.tool.Misc):
new_objs = [] new_objs = []
for obj in objs: for obj in objs:
if obj.type != "MESH" or obj == cutter: mesh = obj.data
if not isinstance(mesh, bpy.types.Mesh) or obj == cutter:
continue continue
new_obj = obj.copy() new_obj = obj.copy()
new_obj.data = obj.data.copy() new_obj.data = mesh.copy()
for collection in obj.users_collection: for collection in obj.users_collection:
collection.objects.link(new_obj) collection.objects.link(new_obj)
+12 -7
View File
@@ -277,7 +277,7 @@ class Model(bonsai.core.tool.Model):
mesh = bpy.data.meshes.new("Axis") mesh = bpy.data.meshes.new("Axis")
mesh.from_pydata(cls.vertices, cls.edges, []) mesh.from_pydata(cls.vertices, cls.edges, [])
mesh.BIMMeshProperties.subshape_type = "AXIS" tool.Geometry.get_mesh_props(mesh).subshape_type = "AXIS"
if obj is None: if obj is None:
obj = bpy.data.objects.new("Axis", mesh) obj = bpy.data.objects.new("Axis", mesh)
@@ -334,7 +334,7 @@ class Model(bonsai.core.tool.Model):
mesh = bpy.data.meshes.new("Profile") mesh = bpy.data.meshes.new("Profile")
mesh.from_pydata(cls.vertices, cls.edges, []) mesh.from_pydata(cls.vertices, cls.edges, [])
mesh.BIMMeshProperties.subshape_type = "PROFILE" tool.Geometry.get_mesh_props(mesh).subshape_type = "PROFILE"
if obj is None: if obj is None:
obj = bpy.data.objects.new("Profile", mesh) obj = bpy.data.objects.new("Profile", mesh)
@@ -376,7 +376,7 @@ class Model(bonsai.core.tool.Model):
mesh = bpy.data.meshes.new("Curve") mesh = bpy.data.meshes.new("Curve")
mesh.from_pydata(cls.vertices, cls.edges, []) mesh.from_pydata(cls.vertices, cls.edges, [])
mesh.BIMMeshProperties.subshape_type = "PROFILE" tool.Geometry.get_mesh_props(mesh).subshape_type = "PROFILE"
if obj is None: if obj is None:
obj = bpy.data.objects.new("Curve", mesh) obj = bpy.data.objects.new("Curve", mesh)
@@ -417,7 +417,7 @@ class Model(bonsai.core.tool.Model):
mesh = bpy.data.meshes.new("Surface") mesh = bpy.data.meshes.new("Surface")
mesh.from_pydata(cls.vertices, cls.edges, []) mesh.from_pydata(cls.vertices, cls.edges, [])
mesh.BIMMeshProperties.subshape_type = "PROFILE" tool.Geometry.get_mesh_props(mesh).subshape_type = "PROFILE"
if obj is None: if obj is None:
obj = bpy.data.objects.new("Surface", mesh) obj = bpy.data.objects.new("Surface", mesh)
@@ -569,7 +569,10 @@ class Model(bonsai.core.tool.Model):
element: Optional[ifcopenshell.entity_instance] = None, element: Optional[ifcopenshell.entity_instance] = None,
representation: Optional[ifcopenshell.entity_instance] = None, representation: Optional[ifcopenshell.entity_instance] = None,
) -> list[ifcopenshell.entity_instance]: ) -> list[ifcopenshell.entity_instance]:
"""Either element or representation must be provided."""
assert element or representation, "Either element or representation must be provided."
if representation is None: if representation is None:
assert element
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW") representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if not representation: if not representation:
return [] return []
@@ -1461,9 +1464,9 @@ class Model(bonsai.core.tool.Model):
after material assignment or material unassignment. after material assignment or material unassignment.
""" """
for element in elements: for element in elements:
if not (obj := tool.Ifc.get_object(element)) or not obj.data: if not (obj := tool.Ifc.get_object(element)) or not (data := obj.data):
continue continue
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) representation = tool.Ifc.get().by_id(tool.Geometry.get_mesh_props(data).ifc_definition_id)
bonsai.core.geometry.switch_representation( bonsai.core.geometry.switch_representation(
tool.Ifc, tool.Ifc,
tool.Geometry, tool.Geometry,
@@ -1931,7 +1934,9 @@ class Model(bonsai.core.tool.Model):
or it's not referring to an object (e.g. potential boolean object).""" or it's not referring to an object (e.g. potential boolean object)."""
if obj.type != "MESH": if obj.type != "MESH":
return return
return obj.data.BIMMeshProperties.obj mesh = obj.data
assert isinstance(mesh, bpy.types.Mesh)
return tool.Geometry.get_mesh_props(mesh).obj
@classmethod @classmethod
def get_tracked_opening_type(cls, obj: bpy.types.Object) -> Union[Literal["OPENING", "BOOLEAN"], None]: def get_tracked_opening_type(cls, obj: bpy.types.Object) -> Union[Literal["OPENING", "BOOLEAN"], None]:
+3 -1
View File
@@ -20,6 +20,7 @@ import bpy
import bmesh import bmesh
import math import math
import ifcopenshell import ifcopenshell
import ifcopenshell.util.unit
import bonsai.core.tool import bonsai.core.tool
import bonsai.tool as tool import bonsai.tool as tool
from bonsai.bim.module.drawing.helper import format_distance from bonsai.bim.module.drawing.helper import format_distance
@@ -452,7 +453,8 @@ class Polyline(bonsai.core.tool.Polyline):
def format_input_ui_units(cls, value: float, is_area: bool = False) -> str: def format_input_ui_units(cls, value: float, is_area: bool = False) -> str:
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
if bpy.context.scene.unit_settings.system == "IMPERIAL": if bpy.context.scene.unit_settings.system == "IMPERIAL":
precision = bpy.context.scene.DocProperties.imperial_precision dprops = tool.Drawing.get_document_props()
precision = dprops.imperial_precision
if is_area: if is_area:
area_unit = bpy.context.scene.BIMProperties.area_unit area_unit = bpy.context.scene.BIMProperties.area_unit
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get(), unit_type=area_unit) unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get(), unit_type=area_unit)
+2 -2
View File
@@ -235,7 +235,7 @@ class Project(bonsai.core.tool.Project):
@classmethod @classmethod
def load_linked_models_from_ifc(cls) -> None: def load_linked_models_from_ifc(cls) -> None:
links = bpy.context.scene.BIMProjectProperties.links links = tool.Project.get_project_props().links
links.clear() links.clear()
links_document = cls.get_linked_models_document() links_document = cls.get_linked_models_document()
if not links_document: if not links_document:
@@ -252,7 +252,7 @@ class Project(bonsai.core.tool.Project):
@classmethod @classmethod
def save_linked_models_to_ifc(cls) -> None: def save_linked_models_to_ifc(cls) -> None:
ifc_file = tool.Ifc.get() ifc_file = tool.Ifc.get()
links = bpy.context.scene.BIMProjectProperties.links links = tool.Project.get_project_props().links
filepaths: set[Path] = set() filepaths: set[Path] = set()
for link in links: for link in links:
filepaths.add(Path(link.name)) filepaths.add(Path(link.name))
+10 -9
View File
@@ -19,6 +19,7 @@
import bpy import bpy
import ifcopenshell import ifcopenshell
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.api.style
import ifcopenshell.util.representation import ifcopenshell.util.representation
import ifcopenshell.util.element import ifcopenshell.util.element
import ifcopenshell.util.placement import ifcopenshell.util.placement
@@ -49,12 +50,12 @@ class Root(bonsai.core.tool.Root):
tool.Geometry.run_style_add_style(obj=mat) tool.Geometry.run_style_add_style(obj=mat)
for mat in tool.Geometry.get_object_materials_without_styles(obj) for mat in tool.Geometry.get_object_materials_without_styles(obj)
] ]
ifcopenshell.api.run( props = tool.Geometry.get_geometry_props()
"style.assign_representation_styles", ifcopenshell.api.style.assign_representation_styles(
tool.Ifc.get(), tool.Ifc.get(),
shape_representation=body, shape_representation=body,
styles=tool.Geometry.get_styles(obj), styles=tool.Geometry.get_styles(obj),
should_use_presentation_style_assignment=bpy.context.scene.BIMGeometryProperties.should_use_presentation_style_assignment, should_use_presentation_style_assignment=props.should_use_presentation_style_assignment,
) )
@classmethod @classmethod
@@ -169,8 +170,8 @@ class Root(bonsai.core.tool.Root):
@classmethod @classmethod
def get_object_representation(cls, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]: def get_object_representation(cls, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
if obj.data and obj.data.BIMMeshProperties.ifc_definition_id: if obj.data and (mesh_props := tool.Geometry.get_mesh_props(obj.data)).ifc_definition_id:
return tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) return tool.Ifc.get().by_id(mesh_props.ifc_definition_id)
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
if element.is_a("IfcTypeProduct"): if element.is_a("IfcTypeProduct"):
if element.RepresentationMaps: if element.RepresentationMaps:
@@ -302,8 +303,8 @@ class Root(bonsai.core.tool.Root):
voided_objs.append(subobj) voided_objs.append(subobj)
for voided_obj in voided_objs: for voided_obj in voided_objs:
if voided_obj.data: if data := voided_obj.data:
representation = tool.Ifc.get().by_id(voided_obj.data.BIMMeshProperties.ifc_definition_id) representation = tool.Ifc.get().by_id(tool.Geometry.get_mesh_props(data).ifc_definition_id)
bonsai.core.geometry.switch_representation( bonsai.core.geometry.switch_representation(
tool.Ifc, tool.Ifc,
tool.Geometry, tool.Geometry,
@@ -421,8 +422,8 @@ class Root(bonsai.core.tool.Root):
to unlink them. to unlink them.
""" """
tool.Ifc.unlink(obj=obj) tool.Ifc.unlink(obj=obj)
if hasattr(obj.data, "BIMMeshProperties"): if tool.Geometry.has_mesh_properties((data := obj.data)):
obj.data.BIMMeshProperties.ifc_definition_id = 0 tool.Geometry.get_mesh_props(mesh).ifc_definition_id = 0
for material_slot in obj.material_slots: for material_slot in obj.material_slots:
if material := material_slot.material: if material := material_slot.material:
tool.Ifc.unlink(obj=material) tool.Ifc.unlink(obj=material)
+7 -2
View File
@@ -25,6 +25,7 @@ import math
import mathutils import mathutils
from mathutils import Matrix, Vector from mathutils import Matrix, Vector
from lark import Lark, Transformer from lark import Lark, Transformer
from typing import Union
class Snap(bonsai.core.tool.Snap): class Snap(bonsai.core.tool.Snap):
@@ -313,7 +314,9 @@ class Snap(bonsai.core.tool.Snap):
plane_normal = tool.Polyline.use_transform_orientations(plane_normal) plane_normal = tool.Polyline.use_transform_orientations(plane_normal)
return plane_origin, plane_normal return plane_origin, plane_normal
def cast_rays_to_single_object(obj, mouse_pos): def cast_rays_to_single_object(
obj: bpy.types.Object, mouse_pos: tuple[int, int]
) -> Union[tuple[bpy.types.Object, Vector, int], tuple[None, None, None]]:
if obj.type != "MESH": if obj.type != "MESH":
return None, None, None return None, None, None
hit, normal, face_index = tool.Raycast.obj_ray_cast(context, event, obj) hit, normal, face_index = tool.Raycast.obj_ray_cast(context, event, obj)
@@ -332,7 +335,9 @@ class Snap(bonsai.core.tool.Snap):
else: else:
return None, None, None return None, None, None
def cast_rays_and_get_best_object(objs_to_raycast, mouse_pos): def cast_rays_and_get_best_object(
objs_to_raycast: list[bpy.types.Object], mouse_pos: tuple[int, int]
) -> Union[tuple[bpy.types.Object, Vector, int], tuple[None, None, None]]:
best_length_squared = 1.0 best_length_squared = 1.0
best_obj = None best_obj = None
best_hit = None best_hit = None
+3 -2
View File
@@ -699,9 +699,10 @@ class Spatial(bonsai.core.tool.Spatial):
for obj in bpy.context.visible_objects: for obj in bpy.context.visible_objects:
visible_element = tool.Ifc.get_entity(obj) visible_element = tool.Ifc.get_entity(obj)
old_mesh = obj.data
if ( if (
not visible_element not visible_element
or obj.type != "MESH" or not isinstance(old_mesh, bpy.types.Mesh)
or not cls.is_bounding_class(visible_element) or not cls.is_bounding_class(visible_element)
or not tool.Drawing.is_intersecting_plane(obj, cut_point, cut_normal) or not tool.Drawing.is_intersecting_plane(obj, cut_point, cut_normal)
): ):
@@ -959,7 +960,7 @@ class Spatial(bonsai.core.tool.Spatial):
old_mesh = active_obj.data old_mesh = active_obj.data
old_mesh_name = old_mesh.name old_mesh_name = old_mesh.name
assert active_obj and isinstance(old_mesh, bpy.types.Mesh) assert active_obj and isinstance(old_mesh, bpy.types.Mesh)
mesh.BIMMeshProperties.ifc_definition_id = old_mesh.BIMMeshProperties.ifc_definition_id tool.Geometry.get_mesh_props(mesh).ifc_definition_id = tool.Geometry.get_mesh_props(old_mesh).ifc_definition_id
tool.Geometry.change_object_data(active_obj, mesh, is_global=True) tool.Geometry.change_object_data(active_obj, mesh, is_global=True)
tool.Ifc.edit(active_obj) tool.Ifc.edit(active_obj)
tool.Blender.remove_data_block(old_mesh) tool.Blender.remove_data_block(old_mesh)
+1 -1
View File
@@ -32,7 +32,7 @@ class Surveyor(bonsai.core.tool.Surveyor):
def get_absolute_matrix(cls, obj: bpy.types.Object) -> npt.NDArray[np.float64]: def get_absolute_matrix(cls, obj: bpy.types.Object) -> npt.NDArray[np.float64]:
M_TRANSLATION = (slice(0, 3), 3) M_TRANSLATION = (slice(0, 3), 3)
matrix = np.array(obj.matrix_world) matrix = np.array(obj.matrix_world)
props = bpy.context.scene.BIMGeoreferenceProperties 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 obj.BIMObjectProperties.blender_offset_type != "NOT_APPLICABLE":
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
coordinate_offset = tool.Geometry.get_cartesian_point_offset(obj) coordinate_offset = tool.Geometry.get_cartesian_point_offset(obj)
+3 -2
View File
@@ -1,12 +1,13 @@
# This can be run using `blender -b -P headless_import.py` # This can be run using `blender -b -P headless_import.py`
import bpy import bpy
import bonsai.tool as tool
from bonsai.bim.ifc import IfcStore from bonsai.bim.ifc import IfcStore
# When federating, you may wish to manually specify the origin to ensure models # When federating, you may wish to manually specify the origin to ensure models
# with different or arbitrary origin conventions will turn up in the right spot. # with different or arbitrary origin conventions will turn up in the right spot.
props = bpy.context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
# A good idea it to test import a portion of the model (or grids only) and check # A good idea it to test import a portion of the model (or grids only) and check
# georeferencing coordinates in the IFC Georeferencing panel before filling out # georeferencing coordinates in the IFC Georeferencing panel before filling out
@@ -19,7 +20,7 @@ props = bpy.context.scene.BIMGeoreferenceProperties
# props.blender_x_axis_ordinate = '0.989063862448262' # props.blender_x_axis_ordinate = '0.989063862448262'
# props.has_blender_offset = True # props.has_blender_offset = True
props = bpy.context.scene.BIMProjectProperties props = tool.Project.get_project_props()
# Generally recommended to disable caching for stability right now # Generally recommended to disable caching for stability right now
props.should_cache = False props.should_cache = False
+3 -1
View File
@@ -23,6 +23,7 @@ import bpy
import pytest import pytest
import webbrowser import webbrowser
import bonsai.bim.handler import bonsai.bim.handler
import bonsai.tool as tool
import ifcopenshell import ifcopenshell
import ifcopenshell.util.element import ifcopenshell.util.element
import ifcopenshell.util.representation import ifcopenshell.util.representation
@@ -66,7 +67,8 @@ class NewIfc4X3:
bpy.data.batch_remove(bpy.data.objects) bpy.data.batch_remove(bpy.data.objects)
bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True) bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)
bonsai.bim.handler.load_post(None) bonsai.bim.handler.load_post(None)
bpy.context.scene.BIMProjectProperties.export_schema = "IFC4X3_ADD2" props = tool.Project.get_project_props()
props.export_schema = "IFC4X3_ADD2"
bpy.ops.bim.create_project() bpy.ops.bim.create_project()
+7 -5
View File
@@ -39,7 +39,7 @@ scenarios("feature")
variables = { variables = {
"cwd": Path.cwd().as_posix(), "cwd": Path.cwd().as_posix(),
"ifc": "IfcStore.get_file()", "ifc": "tool.Ifc.get()",
"pset_ifc": "IfcStore.pset_template_file", "pset_ifc": "IfcStore.pset_template_file",
"classification_ifc": "IfcStore.classification_file", "classification_ifc": "IfcStore.classification_file",
} }
@@ -190,7 +190,8 @@ def an_empty_blender_session():
# default project settings # default project settings
bpy.context.scene.unit_settings.system = "METRIC" bpy.context.scene.unit_settings.system = "METRIC"
bpy.context.scene.unit_settings.length_unit = "MILLIMETERS" bpy.context.scene.unit_settings.length_unit = "MILLIMETERS"
bpy.context.scene.BIMProjectProperties.template_file = "0" props = tool.Project.get_project_props()
props.template_file = "0"
tool.Blender.get_addon_preferences().should_play_chaching_sound = False tool.Blender.get_addon_preferences().should_play_chaching_sound = False
@@ -203,7 +204,8 @@ def an_empty_ifc_project():
@given("an empty IFC2X3 project") @given("an empty IFC2X3 project")
def an_empty_ifc_2x3_project(): def an_empty_ifc_2x3_project():
an_empty_blender_session() an_empty_blender_session()
bpy.context.scene.BIMProjectProperties.export_schema = "IFC2X3" props = tool.Project.get_project_props()
props.export_schema = "IFC2X3"
bpy.ops.bim.create_project() bpy.ops.bim.create_project()
@@ -742,7 +744,7 @@ def the_object_name_has_a_representation_type_of_context(name, type, context):
def the_object_name_data_is_a_type_representation_of_context(name, type, context): def the_object_name_data_is_a_type_representation_of_context(name, type, context):
ifc = an_ifc_file_exists() ifc = an_ifc_file_exists()
context, subcontext, target_view = context.split("/") context, subcontext, target_view = context.split("/")
rep = ifc.by_id(the_object_name_exists(name).data.BIMMeshProperties.ifc_definition_id) rep = ifc.by_id(tool.Geometry.get_mesh_props(the_object_name_exists(name).data).ifc_definition_id)
assert rep assert rep
assert rep.RepresentationType == type, f"The object {name} is not a {type} representation" assert rep.RepresentationType == type, f"The object {name} is not a {type} representation"
assert rep.ContextOfItems.ContextType == context assert rep.ContextOfItems.ContextType == context
@@ -888,7 +890,7 @@ def the_object_name_has_no_data(name):
@then(parsers.parse('the object "{name}" has data which is an IFC representation')) @then(parsers.parse('the object "{name}" has data which is an IFC representation'))
def the_object_name_has_ifc_representation_data(name): def the_object_name_has_ifc_representation_data(name):
id = the_object_name_exists(name).data.BIMMeshProperties.ifc_definition_id id = tool.Geometry.get_mesh_props(the_object_name_exists(name).data).ifc_definition_id
assert id != 0, f"The ID is {id}" assert id != 0, f"The ID is {id}"
+40 -29
View File
@@ -113,30 +113,34 @@ class TestDeleteDrawingElements(NewFile):
class TestDisableEditingDrawings(NewFile): class TestDisableEditingDrawings(NewFile):
def test_run(self): def test_run(self):
bpy.context.scene.DocProperties.is_editing_drawings = True props = tool.Drawing.get_document_props()
props.is_editing_drawings = True
subject.disable_editing_drawings() subject.disable_editing_drawings()
assert bpy.context.scene.DocProperties.is_editing_drawings == False assert props.is_editing_drawings == False
class TestDisableEditingSchedules(NewFile): class TestDisableEditingSchedules(NewFile):
def test_run(self): def test_run(self):
bpy.context.scene.DocProperties.is_editing_schedules = True props = tool.Drawing.get_document_props()
props.is_editing_schedules = True
subject.disable_editing_schedules() subject.disable_editing_schedules()
assert bpy.context.scene.DocProperties.is_editing_schedules == False assert props.is_editing_schedules == False
class TestDisableEditingReferences(NewFile): class TestDisableEditingReferences(NewFile):
def test_run(self): def test_run(self):
bpy.context.scene.DocProperties.is_editing_references = True props = tool.Drawing.get_document_props()
props.is_editing_references = True
subject.disable_editing_references() subject.disable_editing_references()
assert bpy.context.scene.DocProperties.is_editing_references == False assert props.is_editing_references == False
class TestDisableEditingSheets(NewFile): class TestDisableEditingSheets(NewFile):
def test_run(self): def test_run(self):
bpy.context.scene.DocProperties.is_editing_sheets = True props = tool.Drawing.get_document_props()
props.is_editing_sheets = True
subject.disable_editing_sheets() subject.disable_editing_sheets()
assert bpy.context.scene.DocProperties.is_editing_sheets == False assert props.is_editing_sheets == False
class TestDisableEditingText(NewFile): class TestDisableEditingText(NewFile):
@@ -166,30 +170,34 @@ class TestEnableEditing(NewFile):
class TestEnableEditingDrawings(NewFile): class TestEnableEditingDrawings(NewFile):
def test_run(self): def test_run(self):
bpy.context.scene.DocProperties.is_editing_drawings = False props = tool.Drawing.get_document_props()
props.is_editing_drawings = False
subject.enable_editing_drawings() subject.enable_editing_drawings()
assert bpy.context.scene.DocProperties.is_editing_drawings == True assert props.is_editing_drawings == True
class TestEnableEditingSchedules(NewFile): class TestEnableEditingSchedules(NewFile):
def test_run(self): def test_run(self):
bpy.context.scene.DocProperties.is_editing_schedules = False props = tool.Drawing.get_document_props()
props.is_editing_schedules = False
subject.enable_editing_schedules() subject.enable_editing_schedules()
assert bpy.context.scene.DocProperties.is_editing_schedules == True assert props.is_editing_schedules == True
class TestEnableEditingReferences(NewFile): class TestEnableEditingReferences(NewFile):
def test_run(self): def test_run(self):
bpy.context.scene.DocProperties.is_editing_references = False props = tool.Drawing.get_document_props()
props.is_editing_references = False
subject.enable_editing_references() subject.enable_editing_references()
assert bpy.context.scene.DocProperties.is_editing_references == True assert props.is_editing_references == True
class TestEnableEditingSheets(NewFile): class TestEnableEditingSheets(NewFile):
def test_run(self): def test_run(self):
bpy.context.scene.DocProperties.is_editing_sheets = False props = tool.Drawing.get_document_props()
props.is_editing_sheets = False
subject.enable_editing_sheets() subject.enable_editing_sheets()
assert bpy.context.scene.DocProperties.is_editing_sheets == True assert props.is_editing_sheets == True
class TestEnableEditingText(NewFile): class TestEnableEditingText(NewFile):
@@ -492,7 +500,7 @@ class TestImportDrawings(NewFile):
pset = ifcopenshell.api.run("pset.add_pset", ifc, product=drawing, name="EPset_Drawing") pset = ifcopenshell.api.run("pset.add_pset", ifc, product=drawing, name="EPset_Drawing")
ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"TargetView": "PLAN_VIEW"}) ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"TargetView": "PLAN_VIEW"})
subject.import_drawings() subject.import_drawings()
props = bpy.context.scene.DocProperties props = tool.Drawing.get_document_props()
for d in props.drawings: for d in props.drawings:
d.is_expanded = True d.is_expanded = True
subject.import_drawings() subject.import_drawings()
@@ -508,7 +516,7 @@ class TestImportSchedules(NewFile):
ifc.createIfcDocumentInformation(Identification="Y", Name="FOOBAZ") ifc.createIfcDocumentInformation(Identification="Y", Name="FOOBAZ")
document = ifc.createIfcDocumentInformation(Identification="X", Name="FOOBAR", Scope="SCHEDULE") document = ifc.createIfcDocumentInformation(Identification="X", Name="FOOBAR", Scope="SCHEDULE")
subject.import_documents("SCHEDULE") subject.import_documents("SCHEDULE")
props = bpy.context.scene.DocProperties props = tool.Drawing.get_document_props()
assert props.schedules[0].ifc_definition_id == document.id() assert props.schedules[0].ifc_definition_id == document.id()
assert props.schedules[0].identification == "X" assert props.schedules[0].identification == "X"
assert props.schedules[0].name == "FOOBAR" assert props.schedules[0].name == "FOOBAR"
@@ -519,7 +527,7 @@ class TestImportSchedules(NewFile):
ifc.createIfcDocumentInformation(DocumentId="Y", Name="FOOBAZ") ifc.createIfcDocumentInformation(DocumentId="Y", Name="FOOBAZ")
document = ifc.createIfcDocumentInformation(DocumentId="X", Name="FOOBAR", Scope="SCHEDULE") document = ifc.createIfcDocumentInformation(DocumentId="X", Name="FOOBAR", Scope="SCHEDULE")
subject.import_documents("SCHEDULE") subject.import_documents("SCHEDULE")
props = bpy.context.scene.DocProperties props = tool.Drawing.get_document_props()
assert props.schedules[0].ifc_definition_id == document.id() assert props.schedules[0].ifc_definition_id == document.id()
assert props.schedules[0].identification == "X" assert props.schedules[0].identification == "X"
assert props.schedules[0].name == "FOOBAR" assert props.schedules[0].name == "FOOBAR"
@@ -532,7 +540,7 @@ class TestImportReferences(NewFile):
ifc.createIfcDocumentInformation(Identification="Y", Name="FOOBAZ") ifc.createIfcDocumentInformation(Identification="Y", Name="FOOBAZ")
document = ifc.createIfcDocumentInformation(Identification="X", Name="FOOBAR", Scope="REFERENCE") document = ifc.createIfcDocumentInformation(Identification="X", Name="FOOBAR", Scope="REFERENCE")
subject.import_documents("REFERENCE") subject.import_documents("REFERENCE")
props = bpy.context.scene.DocProperties props = tool.Drawing.get_document_props()
assert props.references[0].ifc_definition_id == document.id() assert props.references[0].ifc_definition_id == document.id()
assert props.references[0].identification == "X" assert props.references[0].identification == "X"
assert props.references[0].name == "FOOBAR" assert props.references[0].name == "FOOBAR"
@@ -543,7 +551,7 @@ class TestImportReferences(NewFile):
ifc.createIfcDocumentInformation(DocumentId="Y", Name="FOOBAZ") ifc.createIfcDocumentInformation(DocumentId="Y", Name="FOOBAZ")
document = ifc.createIfcDocumentInformation(DocumentId="X", Name="FOOBAR", Scope="REFERENCE") document = ifc.createIfcDocumentInformation(DocumentId="X", Name="FOOBAR", Scope="REFERENCE")
subject.import_documents("REFERENCE") subject.import_documents("REFERENCE")
props = bpy.context.scene.DocProperties props = tool.Drawing.get_document_props()
assert props.references[0].ifc_definition_id == document.id() assert props.references[0].ifc_definition_id == document.id()
assert props.references[0].identification == "X" assert props.references[0].identification == "X"
assert props.references[0].name == "FOOBAR" assert props.references[0].name == "FOOBAR"
@@ -556,7 +564,7 @@ class TestImportSheets(NewFile):
ifc.createIfcDocumentInformation(Identification="Y", Name="FOOBAZ") ifc.createIfcDocumentInformation(Identification="Y", Name="FOOBAZ")
document = ifc.createIfcDocumentInformation(Identification="X", Name="FOOBAR", Scope="SHEET") document = ifc.createIfcDocumentInformation(Identification="X", Name="FOOBAR", Scope="SHEET")
subject.import_sheets() subject.import_sheets()
props = bpy.context.scene.DocProperties props = tool.Drawing.get_document_props()
assert props.sheets[0].ifc_definition_id == document.id() assert props.sheets[0].ifc_definition_id == document.id()
assert props.sheets[0].identification == "X" assert props.sheets[0].identification == "X"
assert props.sheets[0].name == "FOOBAR" assert props.sheets[0].name == "FOOBAR"
@@ -567,7 +575,7 @@ class TestImportSheets(NewFile):
ifc.createIfcDocumentInformation(DocumentId="Y", Name="FOOBAZ") ifc.createIfcDocumentInformation(DocumentId="Y", Name="FOOBAZ")
document = ifc.createIfcDocumentInformation(DocumentId="X", Name="FOOBAR", Scope="SHEET") document = ifc.createIfcDocumentInformation(DocumentId="X", Name="FOOBAR", Scope="SHEET")
subject.import_sheets() subject.import_sheets()
props = bpy.context.scene.DocProperties props = tool.Drawing.get_document_props()
assert props.sheets[0].ifc_definition_id == document.id() assert props.sheets[0].ifc_definition_id == document.id()
assert props.sheets[0].identification == "X" assert props.sheets[0].identification == "X"
assert props.sheets[0].name == "FOOBAR" assert props.sheets[0].name == "FOOBAR"
@@ -657,9 +665,10 @@ class TestSetName(NewFile):
class TestShowDecorations(NewFile): class TestShowDecorations(NewFile):
def test_run(self): def test_run(self):
bpy.context.scene.DocProperties.should_draw_decorations = False props = tool.Drawing.get_document_props()
props.should_draw_decorations = False
subject.show_decorations() subject.show_decorations()
assert bpy.context.scene.DocProperties.should_draw_decorations is True assert props.should_draw_decorations is True
class TestDrawingMaintainingSheetPosition(NewFile): class TestDrawingMaintainingSheetPosition(NewFile):
@@ -680,7 +689,7 @@ class TestDrawingMaintainingSheetPosition(NewFile):
return drawing_data return drawing_data
def test_run(self): def test_run(self):
props = bpy.context.scene.DocProperties props = tool.Drawing.get_document_props()
bpy.ops.bim.create_project() bpy.ops.bim.create_project()
ifc = tool.Ifc.get() ifc = tool.Ifc.get()
sheet_path = Path.cwd() / "layouts" / "A00 - UNTITLED.svg" sheet_path = Path.cwd() / "layouts" / "A00 - UNTITLED.svg"
@@ -845,10 +854,11 @@ class TestDrawingStyles(NewFile):
ifc = tool.Ifc.get() ifc = tool.Ifc.get()
drawing = ifc.by_type("IfcAnnotation")[0] drawing = ifc.by_type("IfcAnnotation")[0]
bpy.ops.bim.expand_target_view(target_view="PLAN_VIEW") bpy.ops.bim.expand_target_view(target_view="PLAN_VIEW")
props = bpy.context.scene.DocProperties props = tool.Drawing.get_document_props()
props.active_drawing_index = 2 props.active_drawing_index = 2
bpy.ops.bim.activate_drawing(drawing=drawing.id()) bpy.ops.bim.activate_drawing(drawing=drawing.id())
self.drawing_styles = bpy.context.scene.DocProperties.drawing_styles props = tool.Drawing.get_document_props()
self.drawing_styles = props.drawing_styles
def test_drawing_styles_not_loaded_if_underlay_is_inactive(self): def test_drawing_styles_not_loaded_if_underlay_is_inactive(self):
self.setup_project_with_drawing() self.setup_project_with_drawing()
@@ -867,7 +877,8 @@ class TestDrawingStyles(NewFile):
class TestAddReferenceImage(NewFile): class TestAddReferenceImage(NewFile):
def test_run(self): def test_run(self):
bpy.context.scene.BIMProjectProperties.template_file = "0" props = tool.Project.get_project_props()
props.template_file = "0"
bpy.ops.bim.create_project() bpy.ops.bim.create_project()
ifc_path = Path("test/files/temp/test.ifc").absolute() ifc_path = Path("test/files/temp/test.ifc").absolute()
bpy.ops.bim.save_project(filepath=str(ifc_path), should_save_as=True) bpy.ops.bim.save_project(filepath=str(ifc_path), should_save_as=True)
+13 -12
View File
@@ -171,14 +171,14 @@ class TestGetCartesianPointCoordinateOffset(NewFile):
def test_run(self): def test_run(self):
obj = bpy.data.objects.new("Object", None) obj = bpy.data.objects.new("Object", None)
obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT" obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT"
props = bpy.context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
props.has_blender_offset = True props.has_blender_offset = True
obj.BIMObjectProperties.cartesian_point_offset = "1,2,3" obj.BIMObjectProperties.cartesian_point_offset = "1,2,3"
assert np.allclose(subject.get_cartesian_point_offset(obj), np.array((1.0, 2.0, 3.0))) 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): def test_get_null_if_not_a_cartesian_point_offset_type(self):
obj = bpy.data.objects.new("Object", None) obj = bpy.data.objects.new("Object", None)
props = bpy.context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
props.has_blender_offset = True props.has_blender_offset = True
obj.BIMObjectProperties.cartesian_point_offset = "1,2,3" obj.BIMObjectProperties.cartesian_point_offset = "1,2,3"
assert subject.get_cartesian_point_offset(obj) is None assert subject.get_cartesian_point_offset(obj) is None
@@ -186,7 +186,7 @@ class TestGetCartesianPointCoordinateOffset(NewFile):
def test_get_null_if_no_blender_offset(self): def test_get_null_if_no_blender_offset(self):
obj = bpy.data.objects.new("Object", None) obj = bpy.data.objects.new("Object", None)
obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT" obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT"
props = bpy.context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
props.has_blender_offset = False props.has_blender_offset = False
assert subject.get_cartesian_point_offset(obj) is None assert subject.get_cartesian_point_offset(obj) is None
@@ -237,14 +237,15 @@ class TestImportRepresentationParameters(NewFile):
item = ifc.createIfcExtrudedAreaSolid(SweptArea=swept_area, Depth=2) item = ifc.createIfcExtrudedAreaSolid(SweptArea=swept_area, Depth=2)
representation = ifc.createIfcShapeRepresentation(Items=[item]) representation = ifc.createIfcShapeRepresentation(Items=[item])
data = bpy.data.meshes.new("Mesh") data = bpy.data.meshes.new("Mesh")
data.BIMMeshProperties.ifc_definition_id = representation.id() mprops = tool.Geometry.get_mesh_props(data)
mprops.ifc_definition_id = representation.id()
subject.import_representation_parameters(data) subject.import_representation_parameters(data)
assert data.BIMMeshProperties.ifc_parameters[0].name == "IfcExtrudedAreaSolid/Depth" assert mprops.ifc_parameters[0].name == "IfcExtrudedAreaSolid/Depth"
assert data.BIMMeshProperties.ifc_parameters[0].step_id == item.id() assert mprops.ifc_parameters[0].step_id == item.id()
assert data.BIMMeshProperties.ifc_parameters[0].index == 3 assert mprops.ifc_parameters[0].index == 3
assert data.BIMMeshProperties.ifc_parameters[1].name == "IfcCircleProfileDef/Radius" assert mprops.ifc_parameters[1].name == "IfcCircleProfileDef/Radius"
assert data.BIMMeshProperties.ifc_parameters[1].step_id == swept_area.id() assert mprops.ifc_parameters[1].step_id == swept_area.id()
assert data.BIMMeshProperties.ifc_parameters[1].index == 3 assert mprops.ifc_parameters[1].index == 3
class TestIsBodyRepresentation(NewFile): class TestIsBodyRepresentation(NewFile):
@@ -293,7 +294,7 @@ class TestLink(NewFile):
element = ifc.createIfcShapeRepresentation() element = ifc.createIfcShapeRepresentation()
obj = bpy.data.meshes.new("Mesh") obj = bpy.data.meshes.new("Mesh")
subject.link(element, obj) subject.link(element, obj)
assert obj.BIMMeshProperties.ifc_definition_id == element.id() assert tool.Geometry.get_mesh_props(obj).ifc_definition_id == element.id()
class TestRecordObjectMaterials(NewFile): class TestRecordObjectMaterials(NewFile):
@@ -306,7 +307,7 @@ class TestRecordObjectMaterials(NewFile):
material.BIMStyleProperties.ifc_definition_id = style.id() material.BIMStyleProperties.ifc_definition_id = style.id()
obj.data.materials.append(material) obj.data.materials.append(material)
subject.record_object_materials(obj) subject.record_object_materials(obj)
assert obj.data.BIMMeshProperties.material_checksum == str([style.id()]) assert tool.Geometry.get_mesh_props(obj).material_checksum == str([style.id()])
class TestRecordObjectPosition(NewFile): class TestRecordObjectPosition(NewFile):
+26 -22
View File
@@ -39,7 +39,7 @@ class TestImportProjectedCRS(NewFile):
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject") ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
ifcopenshell.api.run("context.add_context", ifc, context_type="Model") ifcopenshell.api.run("context.add_context", ifc, context_type="Model")
subject.import_projected_crs() subject.import_projected_crs()
props = bpy.context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
assert len(props.projected_crs) == 0 assert len(props.projected_crs) == 0
def test_importing_projected_crs(self): def test_importing_projected_crs(self):
@@ -58,7 +58,7 @@ class TestImportProjectedCRS(NewFile):
unit = ifcopenshell.api.run("unit.add_si_unit", ifc, unit_type="LENGTHUNIT") unit = ifcopenshell.api.run("unit.add_si_unit", ifc, unit_type="LENGTHUNIT")
projected_crs.MapUnit = unit projected_crs.MapUnit = unit
subject.import_projected_crs() subject.import_projected_crs()
props = bpy.context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
assert props.projected_crs.get("Name").string_value == "Name" assert props.projected_crs.get("Name").string_value == "Name"
assert props.projected_crs.get("Description").string_value == "Description" assert props.projected_crs.get("Description").string_value == "Description"
assert props.projected_crs.get("GeodeticDatum").string_value == "GeodeticDatum" assert props.projected_crs.get("GeodeticDatum").string_value == "GeodeticDatum"
@@ -71,7 +71,7 @@ class TestImportProjectedCRS(NewFile):
ifc = ifcopenshell.file(schema="IFC2X3") ifc = ifcopenshell.file(schema="IFC2X3")
tool.Ifc.set(ifc) tool.Ifc.set(ifc)
subject.import_projected_crs() subject.import_projected_crs()
props = bpy.context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
assert len(props.projected_crs) == 0 assert len(props.projected_crs) == 0
@@ -82,7 +82,7 @@ class TestImportCoordinateOperation(NewFile):
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject") ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
ifcopenshell.api.run("context.add_context", ifc, context_type="Model") ifcopenshell.api.run("context.add_context", ifc, context_type="Model")
subject.import_coordinate_operation() subject.import_coordinate_operation()
props = bpy.context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
assert len(props.coordinate_operation) == 0 assert len(props.coordinate_operation) == 0
def test_importing_coordinate_operation(self): def test_importing_coordinate_operation(self):
@@ -99,7 +99,7 @@ class TestImportCoordinateOperation(NewFile):
map_conversion.XAxisOrdinate = 5 map_conversion.XAxisOrdinate = 5
map_conversion.Scale = 6 map_conversion.Scale = 6
subject.import_coordinate_operation() subject.import_coordinate_operation()
props = bpy.context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
assert props.coordinate_operation.get("Eastings").string_value == "1.0" assert props.coordinate_operation.get("Eastings").string_value == "1.0"
assert props.coordinate_operation.get("Northings").string_value == "2.0" assert props.coordinate_operation.get("Northings").string_value == "2.0"
assert props.coordinate_operation.get("OrthogonalHeight").string_value == "3.0" assert props.coordinate_operation.get("OrthogonalHeight").string_value == "3.0"
@@ -112,7 +112,7 @@ class TestImportCoordinateOperation(NewFile):
ifc = ifcopenshell.file(schema="IFC2X3") ifc = ifcopenshell.file(schema="IFC2X3")
tool.Ifc.set(ifc) tool.Ifc.set(ifc)
subject.import_coordinate_operation() subject.import_coordinate_operation()
props = bpy.context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
assert len(props.coordinate_operation) == 0 assert len(props.coordinate_operation) == 0
@@ -123,7 +123,7 @@ class TestImportTrueNorth(NewFile):
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject") ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
ifcopenshell.api.run("context.add_context", ifc, context_type="Model") ifcopenshell.api.run("context.add_context", ifc, context_type="Model")
subject.import_true_north() subject.import_true_north()
props = bpy.context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
assert props.true_north_abscissa == "0" assert props.true_north_abscissa == "0"
assert props.true_north_ordinate == "1" assert props.true_north_ordinate == "1"
assert props.true_north_angle == "0" assert props.true_north_angle == "0"
@@ -135,7 +135,7 @@ class TestImportTrueNorth(NewFile):
context = ifcopenshell.api.run("context.add_context", ifc, context_type="Model") context = ifcopenshell.api.run("context.add_context", ifc, context_type="Model")
context.TrueNorth = ifc.createIfcDirection((1.0, 2.0, 0.0)) context.TrueNorth = ifc.createIfcDirection((1.0, 2.0, 0.0))
subject.import_true_north() subject.import_true_north()
props = bpy.context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
assert props.true_north_abscissa == "1.0" assert props.true_north_abscissa == "1.0"
assert props.true_north_ordinate == "2.0" assert props.true_north_ordinate == "2.0"
assert props.true_north_angle == "-26.5650512" assert props.true_north_angle == "-26.5650512"
@@ -176,35 +176,39 @@ class TestGetTrueNorthAttributes(NewFile):
class TestEnableEditing(NewFile): class TestEnableEditing(NewFile):
def test_run(self): def test_run(self):
bpy.context.scene.BIMGeoreferenceProperties.is_editing = False props = tool.Georeference.get_georeference_props()
props.is_editing = False
subject.enable_editing() subject.enable_editing()
assert bpy.context.scene.BIMGeoreferenceProperties.is_editing is True assert props.is_editing is True
class TestDisableEditing(NewFile): class TestDisableEditing(NewFile):
def test_run(self): def test_run(self):
bpy.context.scene.BIMGeoreferenceProperties.is_editing = True props = tool.Georeference.get_georeference_props()
props.is_editing = True
subject.disable_editing() subject.disable_editing()
assert bpy.context.scene.BIMGeoreferenceProperties.is_editing is False assert props.is_editing is False
class TestSetCoordinates(NewFile): class TestSetCoordinates(NewFile):
def test_run(self): def test_run(self):
props = tool.Georeference.get_georeference_props()
subject.set_coordinates("local", [1.0, 2.0, 3.0]) subject.set_coordinates("local", [1.0, 2.0, 3.0])
assert bpy.context.scene.BIMGeoreferenceProperties.local_coordinates == "1.0,2.0,3.0" assert props.local_coordinates == "1.0,2.0,3.0"
subject.set_coordinates("blender", [4.0, 5.0, 6.0]) subject.set_coordinates("blender", [4.0, 5.0, 6.0])
assert bpy.context.scene.BIMGeoreferenceProperties.blender_coordinates == "4.0,5.0,6.0" assert props.blender_coordinates == "4.0,5.0,6.0"
subject.set_coordinates("map", [7.0, 8.0, 9.0]) subject.set_coordinates("map", [7.0, 8.0, 9.0])
assert bpy.context.scene.BIMGeoreferenceProperties.map_coordinates == "7.0,8.0,9.0" assert props.map_coordinates == "7.0,8.0,9.0"
class TestGetCoordinates(NewFile): class TestGetCoordinates(NewFile):
def test_run(self): def test_run(self):
bpy.context.scene.BIMGeoreferenceProperties.local_coordinates = "1.0,2.0,3.0" props = tool.Georeference.get_georeference_props()
props.local_coordinates = "1.0,2.0,3.0"
assert subject.get_coordinates("local") == [1.0, 2.0, 3.0] assert subject.get_coordinates("local") == [1.0, 2.0, 3.0]
bpy.context.scene.BIMGeoreferenceProperties.blender_coordinates = "4.0,5.0,6.0" props.blender_coordinates = "4.0,5.0,6.0"
assert subject.get_coordinates("blender") == [4.0, 5.0, 6.0] assert subject.get_coordinates("blender") == [4.0, 5.0, 6.0]
bpy.context.scene.BIMGeoreferenceProperties.map_coordinates = "7.0,8.0,9.0" props.map_coordinates = "7.0,8.0,9.0"
assert subject.get_coordinates("map") == [7.0, 8.0, 9.0] assert subject.get_coordinates("map") == [7.0, 8.0, 9.0]
@@ -231,7 +235,7 @@ class TestXyz2Enh(NewFile):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject") ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
tool.Ifc.set(ifc) tool.Ifc.set(ifc)
props = bpy.context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
props.has_blender_offset = True props.has_blender_offset = True
props.blender_offset_x = "1.0" props.blender_offset_x = "1.0"
assert subject.xyz2enh([0.0, 0.0, 0.0]) == (1.0, 0.0, 0.0) assert subject.xyz2enh([0.0, 0.0, 0.0]) == (1.0, 0.0, 0.0)
@@ -247,7 +251,7 @@ class TestXyz2Enh(NewFile):
assert subject.xyz2enh([0.0, 0.0, 0.0]) == (1.0, 0.0, 0.0) assert subject.xyz2enh([0.0, 0.0, 0.0]) == (1.0, 0.0, 0.0)
def test_applying_both_blender_offset_and_map_conversion(self): def test_applying_both_blender_offset_and_map_conversion(self):
props = bpy.context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
props.has_blender_offset = True props.has_blender_offset = True
props.blender_offset_x = "1.0" props.blender_offset_x = "1.0"
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
@@ -271,7 +275,7 @@ class TestEnh2Xyz(NewFile):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject") ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
tool.Ifc.set(ifc) tool.Ifc.set(ifc)
props = bpy.context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
props.has_blender_offset = True props.has_blender_offset = True
props.blender_offset_x = "1.0" props.blender_offset_x = "1.0"
assert subject.enh2xyz([0.0, 0.0, 0.0]) == (-1.0, 0.0, 0.0) assert subject.enh2xyz([0.0, 0.0, 0.0]) == (-1.0, 0.0, 0.0)
@@ -287,7 +291,7 @@ class TestEnh2Xyz(NewFile):
assert subject.enh2xyz([0.0, 0.0, 0.0]) == (-1.0, 0.0, 0.0) assert subject.enh2xyz([0.0, 0.0, 0.0]) == (-1.0, 0.0, 0.0)
def test_applying_both_blender_offset_and_map_conversion(self): def test_applying_both_blender_offset_and_map_conversion(self):
props = bpy.context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
props.has_blender_offset = True props.has_blender_offset = True
props.blender_offset_x = "1.0" props.blender_offset_x = "1.0"
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
+1 -1
View File
@@ -189,7 +189,7 @@ class TestLink(test.bim.bootstrap.NewFile):
element = ifc.create_entity("IfcShapeRepresentation") element = ifc.create_entity("IfcShapeRepresentation")
obj = bpy.data.meshes.new("Material") obj = bpy.data.meshes.new("Material")
subject.link(element, obj) subject.link(element, obj)
assert obj.BIMMeshProperties.ifc_definition_id == element.id() assert tool.Geometry.get_mesh_props(obj).ifc_definition_id == element.id()
class TestUnlink(test.bim.bootstrap.NewFile): class TestUnlink(test.bim.bootstrap.NewFile):
+2 -1
View File
@@ -520,7 +520,8 @@ class TestLoadingIndexedMap(NewFile):
class TestSetupActiveBsddClassification(NewFile): class TestSetupActiveBsddClassification(NewFile):
def run_test(self, schema: ifcopenshell.util.schema.IFC_SCHEMA) -> None: def run_test(self, schema: ifcopenshell.util.schema.IFC_SCHEMA) -> None:
schema_ = "IFC4X3_ADD2" if schema == "IFC4X3" else schema schema_ = "IFC4X3_ADD2" if schema == "IFC4X3" else schema
bpy.context.scene.BIMProjectProperties.export_schema = schema_ props = tool.Project.get_project_props()
props.export_schema = schema_
bpy.ops.bim.create_project() bpy.ops.bim.create_project()
ifc_file = tool.Ifc.get() ifc_file = tool.Ifc.get()
name = "CCI Construction" name = "CCI Construction"
+3 -2
View File
@@ -378,7 +378,7 @@ class TestGenerateStair2DProfile(NewFile):
class TestUsingArrays(NewFile): class TestUsingArrays(NewFile):
def setup_array(self, add_second_layer=False, sync_children=False): def setup_array(self, add_second_layer=False, sync_children=False):
bpy.context.scene.BIMProjectProperties.template_file = "0" tool.Project.get_project_props().template_file = "0"
bpy.ops.bim.create_project() bpy.ops.bim.create_project()
bpy.ops.mesh.primitive_cube_add() bpy.ops.mesh.primitive_cube_add()
@@ -467,7 +467,8 @@ class TestApplyIfcMaterialChanges(NewFile):
return mesh return mesh
def setup_test(self, and_elements: bool = True) -> None: def setup_test(self, and_elements: bool = True) -> None:
bpy.context.scene.BIMProjectProperties.template_file = "0" props = tool.Project.get_project_props()
props.template_file = "0"
bpy.context.scene.unit_settings.length_unit = "MILLIMETERS" bpy.context.scene.unit_settings.length_unit = "MILLIMETERS"
bpy.ops.bim.create_project() bpy.ops.bim.create_project()
ifc_file = tool.Ifc.get() ifc_file = tool.Ifc.get()
+11 -11
View File
@@ -244,25 +244,25 @@ class TestLoadProject(NewFile):
class TestLoadLinkedModels(NewFile): class TestLoadLinkedModels(NewFile):
def test_load_linked_models_no_document(self): def test_load_linked_models_no_document(self):
links = bpy.context.scene.BIMProjectProperties.links props = tool.Project.get_project_props()
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
tool.Ifc.set(ifc) tool.Ifc.set(ifc)
subject.load_linked_models_from_ifc() subject.load_linked_models_from_ifc()
assert len(links) == 0 assert len(props.links) == 0
def test_load_linked_models_document_no_references(self): def test_load_linked_models_document_no_references(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
links = bpy.context.scene.BIMProjectProperties.links props = tool.Project.get_project_props()
ifcopenshell.api.root.create_entity(ifc, "IfcProject") ifcopenshell.api.root.create_entity(ifc, "IfcProject")
document = ifcopenshell.api.document.add_information(ifc) document = ifcopenshell.api.document.add_information(ifc)
document.Name = "BBIM_Linked_Models" document.Name = "BBIM_Linked_Models"
tool.Ifc.set(ifc) tool.Ifc.set(ifc)
subject.load_linked_models_from_ifc() subject.load_linked_models_from_ifc()
assert len(links) == 0 assert len(props.links) == 0
def test_load_linked_models_document_with_references(self): def test_load_linked_models_document_with_references(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
links = bpy.context.scene.BIMProjectProperties.links props = tool.Project.get_project_props()
ifcopenshell.api.root.create_entity(ifc, "IfcProject") ifcopenshell.api.root.create_entity(ifc, "IfcProject")
document = ifcopenshell.api.document.add_information(ifc) document = ifcopenshell.api.document.add_information(ifc)
document.Name = "BBIM_Linked_Models" document.Name = "BBIM_Linked_Models"
@@ -271,8 +271,8 @@ class TestLoadLinkedModels(NewFile):
reference.Location = linked_model_path reference.Location = linked_model_path
tool.Ifc.set(ifc) tool.Ifc.set(ifc)
subject.load_linked_models_from_ifc() subject.load_linked_models_from_ifc()
assert len(links) == 1 assert len(props.links) == 1
assert links[0].name == linked_model_path assert props.links[0].name == linked_model_path
class TestSaveLinkedModelsToIfc(NewFile): class TestSaveLinkedModelsToIfc(NewFile):
@@ -286,8 +286,8 @@ class TestSaveLinkedModelsToIfc(NewFile):
def test_save_linked_models_to_ifc_paths_to_add(self): def test_save_linked_models_to_ifc_paths_to_add(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
ifcopenshell.api.root.create_entity(ifc, "IfcProject") ifcopenshell.api.root.create_entity(ifc, "IfcProject")
links = bpy.context.scene.BIMProjectProperties.links props = tool.Project.get_project_props()
link = links.add() link = props.links.add()
linked_model_path = "test.ifc" linked_model_path = "test.ifc"
link.name = linked_model_path link.name = linked_model_path
tool.Ifc.set(ifc) tool.Ifc.set(ifc)
@@ -299,7 +299,7 @@ class TestSaveLinkedModelsToIfc(NewFile):
def test_save_linked_models_to_ifc_already_created_references(self): def test_save_linked_models_to_ifc_already_created_references(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
links = bpy.context.scene.BIMProjectProperties.links links = tool.Project.get_project_props().links
ifcopenshell.api.root.create_entity(ifc, "IfcProject") ifcopenshell.api.root.create_entity(ifc, "IfcProject")
document = ifcopenshell.api.document.add_information(ifc) document = ifcopenshell.api.document.add_information(ifc)
@@ -326,7 +326,7 @@ class TestSaveLinkedModelsToIfc(NewFile):
def test_save_linked_models_to_ifc_references_to_remove(self): def test_save_linked_models_to_ifc_references_to_remove(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
links = bpy.context.scene.BIMProjectProperties.links links = tool.Project.get_project_props().links
ifcopenshell.api.root.create_entity(ifc, "IfcProject") ifcopenshell.api.root.create_entity(ifc, "IfcProject")
document = ifcopenshell.api.document.add_information(ifc) document = ifcopenshell.api.document.add_information(ifc)
+3 -3
View File
@@ -120,8 +120,8 @@ class TestGetObjectRepresentation(NewFile):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
tool.Ifc.set(ifc) tool.Ifc.set(ifc)
representation = ifc.createIfcShapeRepresentation() representation = ifc.createIfcShapeRepresentation()
obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh")) obj = bpy.data.objects.new("Object", (mesh := bpy.data.meshes.new("Mesh")))
obj.data.BIMMeshProperties.ifc_definition_id = representation.id() tool.Geometry.get_mesh_props(mesh).ifc_definition_id = representation.id()
assert subject.get_object_representation(obj) == representation assert subject.get_object_representation(obj) == representation
@@ -175,7 +175,7 @@ class TestSetObjectName(NewFile):
class TestReassignClass(NewFile): class TestReassignClass(NewFile):
def test_reassigning_multiple_occurrences_of_the_same_type(self): def test_reassigning_multiple_occurrences_of_the_same_type(self):
bpy.context.scene.BIMProjectProperties.template_file = "IFC4 Demo Template.ifc" tool.Project.get_project_props().template_file = "IFC4 Demo Template.ifc"
bpy.ops.bim.create_project() bpy.ops.bim.create_project()
ifc_file = tool.Ifc.get() ifc_file = tool.Ifc.get()
context = bpy.context context = bpy.context
+3 -2
View File
@@ -20,6 +20,7 @@ import bpy
import numpy as np import numpy as np
import ifcopenshell import ifcopenshell
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.util.geolocation
import test.bim.bootstrap import test.bim.bootstrap
import bonsai.core.tool import bonsai.core.tool
@@ -34,7 +35,7 @@ class TestImplementsTool(test.bim.bootstrap.NewFile):
class TestGetGlobalMatrix(test.bim.bootstrap.NewFile): class TestGetGlobalMatrix(test.bim.bootstrap.NewFile):
def test_getting_an_absolute_matrix_if_no_blender_offset(self): def test_getting_an_absolute_matrix_if_no_blender_offset(self):
props = bpy.context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
props.has_blender_offset = False props.has_blender_offset = False
obj = bpy.data.objects.new("Object", None) obj = bpy.data.objects.new("Object", None)
assert (subject.get_absolute_matrix(obj) == np.array(obj.matrix_world)).all() assert (subject.get_absolute_matrix(obj) == np.array(obj.matrix_world)).all()
@@ -45,7 +46,7 @@ class TestGetGlobalMatrix(test.bim.bootstrap.NewFile):
unit = ifcopenshell.api.run("unit.add_si_unit", ifc, unit_type="LENGTHUNIT", prefix="MILLI") unit = ifcopenshell.api.run("unit.add_si_unit", ifc, unit_type="LENGTHUNIT", prefix="MILLI")
ifcopenshell.api.run("unit.assign_unit", ifc, units=[unit]) ifcopenshell.api.run("unit.assign_unit", ifc, units=[unit])
tool.Ifc.set(ifc) tool.Ifc.set(ifc)
props = bpy.context.scene.BIMGeoreferenceProperties props = tool.Georeference.get_georeference_props()
props.has_blender_offset = True props.has_blender_offset = True
props.blender_offset_x = "1000" props.blender_offset_x = "1000"
props.blender_offset_y = "2000" props.blender_offset_y = "2000"
@@ -30,11 +30,8 @@ def copy_cost_item_values(
parametrically linked, so if one value changes, the other will not. parametrically linked, so if one value changes, the other will not.
:param source: The IfcCostItem to copy cost values from :param source: The IfcCostItem to copy cost values from
:type source: ifcopenshell.entity_instance
:param destination: The IfcCostItem to copy cost values from :param destination: The IfcCostItem to copy cost values from
:type destination: ifcopenshell.entity_instance
:return: None :return: None
:rtype: None
Example: Example:
@@ -53,11 +50,9 @@ def copy_cost_item_values(
# Let's copy the value from one item to another # Let's copy the value from one item to another
ifcopenshell.api.cost.copy_cost_item_values(model, source=item1, destination=item2) ifcopenshell.api.cost.copy_cost_item_values(model, source=item1, destination=item2)
""" """
settings = {"source": source, "destination": destination} for cost_value in destination.CostValues or []:
for cost_value in settings["destination"].CostValues or []:
ifcopenshell.api.cost.remove_cost_item_value(file, cost_value=cost_value) ifcopenshell.api.cost.remove_cost_item_value(file, cost_value=cost_value)
copied_cost_values = [] copied_cost_values = []
for cost_value in settings["source"].CostValues or []: for cost_value in source.CostValues or []:
copied_cost_values.append(ifcopenshell.util.element.copy_deep(file, cost_value)) copied_cost_values.append(ifcopenshell.util.element.copy_deep(file, cost_value))
settings["destination"].CostValues = copied_cost_values destination.CostValues = copied_cost_values
@@ -62,15 +62,10 @@ def assign_layer(
# only one item) to the layer. # only one item) to the layer.
ifcopenshell.api.layer.assign_layer(model, items=[representation.Items[0]], layer=layer) ifcopenshell.api.layer.assign_layer(model, items=[representation.Items[0]], layer=layer)
""" """
settings = {
"items": items,
"layer": layer,
}
# support AssignedItems == None since layer might just got created # support AssignedItems == None since layer might just got created
layer = settings["layer"] assigned_items: set[ifcopenshell.entity_instance]
assigned_items = set(layer.AssignedItems or []) assigned_items = set(layer.AssignedItems or [])
items = set(settings["items"]) items_set = set(items)
if items.issubset(assigned_items): if items_set.issubset(assigned_items):
return return
layer.AssignedItems = list(assigned_items | items) layer.AssignedItems = list(assigned_items | items_set)
@@ -91,8 +91,7 @@ def edit_profile_usage(
usecase = Usecase() usecase = Usecase()
usecase.file = file usecase.file = file
usecase.settings = {"usage": usage, "attributes": attributes} return usecase.execute(usage, attributes)
return usecase.execute()
class Usecase: class Usecase:
@@ -30,17 +30,15 @@ def get_application(ifc: ifcopenshell.file) -> Union[ifcopenshell.entity_instanc
IfcApplication. See ifcopenshell.api.owner.create_owner_history for details. IfcApplication. See ifcopenshell.api.owner.create_owner_history for details.
:param ifc: The IFC file object that is being edited. :param ifc: The IFC file object that is being edited.
:type ifc: ifcopenshell.file
:return: The IfcApplication with metadata of the authoring software. :return: The IfcApplication with metadata of the authoring software.
:rtype: ifcopenshell.entity_instance
""" """
app = ifc.by_type("IfcApplication") app = next(iter(ifc.by_type("IfcApplication")), None)
if not app and ifc.schema == "IFC2X3": if not app and ifc.schema == "IFC2X3":
raise Exception( raise Exception(
"Please create an application to continue. See the owner.create_owner_history docs for more info." "Please create an application to continue. See the owner.create_owner_history docs for more info."
"https://docs.ifcopenshell.org/autoapi/ifcopenshell/api/owner/create_owner_history/index.html" "https://docs.ifcopenshell.org/autoapi/ifcopenshell/api/owner/create_owner_history/index.html"
) )
return (app or [None])[0] return app
def get_user(ifc: ifcopenshell.file) -> Union[ifcopenshell.entity_instance, None]: def get_user(ifc: ifcopenshell.file) -> Union[ifcopenshell.entity_instance, None]:
@@ -50,17 +48,15 @@ def get_user(ifc: ifcopenshell.file) -> Union[ifcopenshell.entity_instance, None
IfcApplication. See ifcopenshell.api.owner.create_owner_history for details. IfcApplication. See ifcopenshell.api.owner.create_owner_history for details.
:param ifc: The IFC file object that is being edited. :param ifc: The IFC file object that is being edited.
:type ifc: ifcopenshell.file
:return: The IfcPersonAndOrganization with metadata of the authoring user. :return: The IfcPersonAndOrganization with metadata of the authoring user.
:rtype: ifcopenshell.entity_instance
""" """
pao = ifc.by_type("IfcPersonAndOrganization") pao = next(iter(ifc.by_type("IfcPersonAndOrganization")), None)
if not pao and ifc.schema == "IFC2X3": if not pao and ifc.schema == "IFC2X3":
raise Exception( raise Exception(
"Please create a user to continue. See the owner.create_owner_history docs for more info." "Please create a user to continue. See the owner.create_owner_history docs for more info."
"https://docs.ifcopenshell.org/autoapi/ifcopenshell/api/owner/create_owner_history/index.html" "https://docs.ifcopenshell.org/autoapi/ifcopenshell/api/owner/create_owner_history/index.html"
) )
return (pao or [None])[0] return pao
get_application_factory = get_application get_application_factory = get_application
@@ -19,6 +19,8 @@
import datetime import datetime
import ifcopenshell.util.date import ifcopenshell.util.date
import ifcopenshell.util.sequence import ifcopenshell.util.sequence
from ifcopenshell.util.sequence import DURATION_TYPE
from typing import Union, Optional
def cascade_schedule(file: ifcopenshell.file, task: ifcopenshell.entity_instance) -> None: def cascade_schedule(file: ifcopenshell.file, task: ifcopenshell.entity_instance) -> None:
@@ -41,9 +43,7 @@ def cascade_schedule(file: ifcopenshell.file, task: ifcopenshell.entity_instance
be equivalent to be Tuesday 8am, for instance. be equivalent to be Tuesday 8am, for instance.
:param task: The start task to begin cascading from. :param task: The start task to begin cascading from.
:type task: ifcopenshell.entity_instance
:return: None :return: None
:rtype: None
Example: Example:
@@ -103,16 +103,22 @@ def cascade_schedule(file: ifcopenshell.file, task: ifcopenshell.entity_instance
""" """
usecase = Usecase() usecase = Usecase()
usecase.file = file usecase.file = file
usecase.settings = {"task": task} return usecase.execute(task)
return usecase.execute()
class Usecase: class Usecase:
def execute(self): file: ifcopenshell.file
self.calendar_cache = {}
self.cascade_task(self.settings["task"], is_first_task=True)
def cascade_task(self, task, is_first_task=False, task_sequence=None): def execute(self, task: ifcopenshell.entity_instance):
self.calendar_cache = {}
self.cascade_task(task, is_first_task=True)
def cascade_task(
self,
task: ifcopenshell.entity_instance,
is_first_task: bool = False,
task_sequence: Optional[list[ifcopenshell.entity_instance]] = None,
) -> None:
if task_sequence is None: if task_sequence is None:
task_sequence = [] task_sequence = []
@@ -316,18 +322,22 @@ class Usecase:
for nested_task in rel.RelatedObjects or [] for nested_task in rel.RelatedObjects or []
] ]
def get_lag_time_days(self, lag_time): def get_lag_time_days(self, lag_time: ifcopenshell.entity_instance) -> int:
return ifcopenshell.util.date.ifc2datetime(lag_time.LagValue.wrappedValue).days return ifcopenshell.util.date.ifc2datetime(lag_time.LagValue.wrappedValue).days
def get_calendar(self, task): def get_calendar(self, task: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
if task.id() not in self.calendar_cache: if task.id() not in self.calendar_cache:
self.calendar_cache[task.id()] = ifcopenshell.util.sequence.derive_calendar(task) self.calendar_cache[task.id()] = ifcopenshell.util.sequence.derive_calendar(task)
return self.calendar_cache[task.id()] return self.calendar_cache[task.id()]
def offset_date(self, date, days, duration_type, calendar): def offset_date(
self, date: datetime.datetime, days: int, duration_type: DURATION_TYPE, calendar: ifcopenshell.entity_instance
) -> datetime.datetime:
return ifcopenshell.util.sequence.offset_date(date, datetime.timedelta(days=days), duration_type, calendar) return ifcopenshell.util.sequence.offset_date(date, datetime.timedelta(days=days), duration_type, calendar)
def get_task_time_attribute(self, task, attribute): def get_task_time_attribute(
self, task: ifcopenshell.entity_instance, attribute: str
) -> Union[datetime.datetime, None]:
if task.TaskTime: if task.TaskTime:
value = getattr(task.TaskTime, attribute) value = getattr(task.TaskTime, attribute)
if value: if value:
@@ -75,7 +75,9 @@ class Usecase:
baseline_work_schedule.Name = name baseline_work_schedule.Name = name
self.create_baseline_reference(work_schedule, baseline_work_schedule) self.create_baseline_reference(work_schedule, baseline_work_schedule)
for summary_task in ifcopenshell.util.sequence.get_root_tasks(work_schedule): for summary_task in ifcopenshell.util.sequence.get_root_tasks(work_schedule):
current, duplicate = ifcopenshell.api.sequence.duplicate_task(self.file, task=summary_task) res = ifcopenshell.api.sequence.duplicate_task(self.file, task=summary_task)
assert isinstance(res, list)
current, duplicate = res
ifcopenshell.api.control.assign_control( ifcopenshell.api.control.assign_control(
self.file, relating_control=baseline_work_schedule, related_object=duplicate[0] self.file, relating_control=baseline_work_schedule, related_object=duplicate[0]
) )
@@ -16,42 +16,33 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell import ifcopenshell
from ifcopenshell.util.shape_builder import VectorType, ifc_safe_vector_type
def edit_structural_connection_cs( def edit_structural_connection_cs(
file: ifcopenshell.file, file: ifcopenshell.file,
structural_item: ifcopenshell.entity_instance, structural_item: ifcopenshell.entity_instance,
axis: tuple[float, float, float] = (0.0, 0.0, 1.0), axis: VectorType = (0.0, 0.0, 1.0),
ref_direction: tuple[float, float, float] = (1.0, 0.0, 0.0), ref_direction: VectorType = (1.0, 0.0, 0.0),
) -> None: ) -> None:
"""Edits the coordinate system of a structural connection """Edits the coordinate system of a structural connection
:param structural_item: The IfcStructuralItem you want to modify. :param structural_item: The IfcStructuralItem you want to modify.
:type structural_item: ifcopenshell.entity_instance
:param axis: The unit Z axis vector defined as a list of 3 floats. :param axis: The unit Z axis vector defined as a list of 3 floats.
Defaults to (0., 0., 1.). Defaults to (0., 0., 1.).
:type axis: tuple[float, float, float]
:param ref_direction: The unit X axis vector defined as a list of 3 :param ref_direction: The unit X axis vector defined as a list of 3
floats. Defaults to (1., 0., 0.). floats. Defaults to (1., 0., 0.).
:type ref_direction: tuple[float, float, float]
:return: None :return: None
:rtype: None
""" """
settings = { if structural_item.ConditionCoordinateSystem is None:
"structural_item": structural_item,
"axis": axis,
"ref_direction": ref_direction,
}
if settings["structural_item"].ConditionCoordinateSystem is None:
point = file.createIfcCartesianPoint((0.0, 0.0, 0.0)) point = file.createIfcCartesianPoint((0.0, 0.0, 0.0))
ccs = file.createIfcAxis2Placement3D(point, None, None) ccs = file.createIfcAxis2Placement3D(point, None, None)
settings["structural_item"].ConditionCoordinateSystem = ccs structural_item.ConditionCoordinateSystem = ccs
ccs = settings["structural_item"].ConditionCoordinateSystem ccs = structural_item.ConditionCoordinateSystem
if ccs.Axis and len(file.get_inverse(ccs.Axis)) == 1: if ccs.Axis and len(file.get_inverse(ccs.Axis)) == 1:
file.remove(ccs.Axis) file.remove(ccs.Axis)
ccs.Axis = file.createIfcDirection(settings["axis"]) ccs.Axis = file.create_entity("IfcDirection", ifc_safe_vector_type(axis))
if ccs.RefDirection and len(file.get_inverse(ccs.RefDirection)) == 1: if (prev_ref_direction := ccs.RefDirection) and len(file.get_inverse(prev_ref_direction)) == 1:
file.remove(ccs.RefDirection) file.remove(prev_ref_direction)
ccs.RefDirection = file.createIfcDirection(settings["ref_direction"]) ccs.RefDirection = file.create_entity("IfcDirection", ifc_safe_vector_type(ref_direction))
@@ -84,7 +84,8 @@ class Patcher:
import bonsai.tool as tool import bonsai.tool as tool
from math import degrees from math import degrees
bpy.context.scene.BIMProjectProperties.should_use_native_meshes = True props = tool.Project.get_project_props()
props.should_use_native_meshes = True
bpy.ops.bim.load_project(filepath=self.filepath) bpy.ops.bim.load_project(filepath=self.filepath)
old_history_size = tool.Ifc.get().history_size old_history_size = tool.Ifc.get().history_size