diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index 83c476b160..6eec4e5a6d 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -6,7 +6,7 @@ bpy = sys.modules.get("bpy") if bpy is not None: import bpy import importlib - from . import ui, prop, operator + from . import handler, ui, prop, operator modules = { "project": None, @@ -155,15 +155,16 @@ if bpy is not None: self.layout.operator(operator.ImportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcxml)") def on_register(scene): - prop.setDefaultProperties(scene) + handler.setDefaultProperties(scene) bpy.app.handlers.depsgraph_update_post.remove(on_register) def register(): for cls in classes: bpy.utils.register_class(cls) bpy.app.handlers.depsgraph_update_post.append(on_register) - bpy.app.handlers.load_post.append(prop.setDefaultProperties) - bpy.app.handlers.load_post.append(prop.clearIfcStore) + bpy.app.handlers.load_post.append(handler.setDefaultProperties) + bpy.app.handlers.load_post.append(handler.loadIfcStore) + bpy.app.handlers.save_pre.append(handler.storeIdMap) bpy.types.TOPBAR_MT_file_export.append(menu_func_export) bpy.types.TOPBAR_MT_file_import.append(menu_func_import) bpy.types.Scene.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties) @@ -181,13 +182,14 @@ if bpy is not None: module.register() bpy.app.handlers.depsgraph_update_pre.append(operator.depsgraph_update_pre_handler) - bpy.app.handlers.load_post.append(prop.toggleDecorationsOnLoad) + bpy.app.handlers.load_post.append(handler.toggleDecorationsOnLoad) def unregister(): for cls in reversed(classes): bpy.utils.unregister_class(cls) - bpy.app.handlers.load_post.remove(prop.setDefaultProperties) - bpy.app.handlers.load_post.remove(prop.clearIfcStore) + bpy.app.handlers.load_post.remove(handler.setDefaultProperties) + bpy.app.handlers.load_post.remove(handler.loadIfcStore) + bpy.app.handlers.save_pre.remove(handler.storeIdMap) bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) del bpy.types.Scene.BIMProperties @@ -205,3 +207,4 @@ if bpy is not None: module.unregister() bpy.app.handlers.depsgraph_update_pre.remove(operator.depsgraph_update_pre_handler) + bpy.app.handlers.load_post.remove(handler.toggleDecorationsOnLoad) diff --git a/src/ifcblenderexport/blenderbim/bim/handler.py b/src/ifcblenderexport/blenderbim/bim/handler.py new file mode 100644 index 0000000000..a6e32857ad --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/handler.py @@ -0,0 +1,102 @@ +import bpy +import json +import blenderbim.bim.decoration as decoration +from bpy.app.handlers import persistent +from blenderbim.bim.ifc import IfcStore + + +@persistent +def loadIfcStore(scene): + IfcStore.file = None + IfcStore.schema = None + props = bpy.context.scene.BIMProperties + IfcStore.id_map = {int(k): bpy.data.objects.get(v) for k, v in json.loads(props.id_map).items()} if props.id_map else {} + IfcStore.guid_map = ( + {k: bpy.data.objects.get(v) for k, v in json.loads(props.guid_map).items()} if props.id_map else {} + ) + + +@persistent +def storeIdMap(scene): + bpy.context.scene.BIMProperties.id_map = json.dumps({k: v.name for k, v in IfcStore.id_map.items()}) + bpy.context.scene.BIMProperties.guid_map = json.dumps({k: v.name for k, v in IfcStore.guid_map.items()}) + + +@persistent +def setDefaultProperties(scene): + if len(bpy.context.scene.DocProperties.drawing_styles) == 0: + drawing_style = bpy.context.scene.DocProperties.drawing_styles.add() + drawing_style.name = "Technical" + drawing_style.render_type = "VIEWPORT" + drawing_style.raster_style = json.dumps( + { + "bpy.data.worlds[0].color": (1, 1, 1), + "bpy.context.scene.render.engine": "BLENDER_WORKBENCH", + "bpy.context.scene.render.film_transparent": False, + "bpy.context.scene.display.shading.show_object_outline": True, + "bpy.context.scene.display.shading.show_cavity": False, + "bpy.context.scene.display.shading.cavity_type": "BOTH", + "bpy.context.scene.display.shading.curvature_ridge_factor": 1, + "bpy.context.scene.display.shading.curvature_valley_factor": 1, + "bpy.context.scene.view_settings.view_transform": "Standard", + "bpy.context.scene.display.shading.light": "FLAT", + "bpy.context.scene.display.shading.color_type": "SINGLE", + "bpy.context.scene.display.shading.single_color": (1, 1, 1), + "bpy.context.scene.display.shading.show_shadows": False, + "bpy.context.scene.display.shading.shadow_intensity": 0.5, + "bpy.context.scene.display.light_direction": (0.5, 0.5, 0.5), + "bpy.context.scene.view_settings.use_curve_mapping": False, + "space.overlay.show_wireframes": True, + "space.overlay.wireframe_threshold": 0, + "space.overlay.show_floor": False, + "space.overlay.show_axis_x": False, + "space.overlay.show_axis_y": False, + "space.overlay.show_axis_z": False, + "space.overlay.show_object_origins": False, + "space.overlay.show_relationship_lines": False, + } + ) + drawing_style = bpy.context.scene.DocProperties.drawing_styles.add() + drawing_style.name = "Shaded" + drawing_style.render_type = "VIEWPORT" + drawing_style.raster_style = json.dumps( + { + "bpy.data.worlds[0].color": (1, 1, 1), + "bpy.context.scene.render.engine": "BLENDER_WORKBENCH", + "bpy.context.scene.render.film_transparent": False, + "bpy.context.scene.display.shading.show_object_outline": True, + "bpy.context.scene.display.shading.show_cavity": True, + "bpy.context.scene.display.shading.cavity_type": "BOTH", + "bpy.context.scene.display.shading.curvature_ridge_factor": 1, + "bpy.context.scene.display.shading.curvature_valley_factor": 1, + "bpy.context.scene.view_settings.view_transform": "Standard", + "bpy.context.scene.display.shading.light": "STUDIO", + "bpy.context.scene.display.shading.color_type": "MATERIAL", + "bpy.context.scene.display.shading.single_color": (1, 1, 1), + "bpy.context.scene.display.shading.show_shadows": True, + "bpy.context.scene.display.shading.shadow_intensity": 0.5, + "bpy.context.scene.display.light_direction": (0.5, 0.5, 0.5), + "bpy.context.scene.view_settings.use_curve_mapping": False, + "space.overlay.show_wireframes": True, + "space.overlay.wireframe_threshold": 0, + "space.overlay.show_floor": False, + "space.overlay.show_axis_x": False, + "space.overlay.show_axis_y": False, + "space.overlay.show_axis_z": False, + "space.overlay.show_object_origins": False, + "space.overlay.show_relationship_lines": False, + } + ) + drawing_style = bpy.context.scene.DocProperties.drawing_styles.add() + drawing_style.name = "Blender Default" + drawing_style.render_type = "DEFAULT" + bpy.ops.bim.save_drawing_style(index="2") + + +@persistent +def toggleDecorationsOnLoad(*args): + toggle = bpy.context.scene.DocProperties.should_draw_decorations + if toggle: + decoration.DecorationsHandler.install(bpy.context) + else: + decoration.DecorationsHandler.uninstall() diff --git a/src/ifcblenderexport/blenderbim/bim/ifc.py b/src/ifcblenderexport/blenderbim/bim/ifc.py index 43ae6873f3..f542bb1531 100644 --- a/src/ifcblenderexport/blenderbim/bim/ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/ifc.py @@ -6,6 +6,8 @@ class IfcStore: path = "" file = None schema = None + id_map = {} + guid_map = {} pset_template_path = "" pset_template_file = None @@ -24,3 +26,16 @@ class IfcStore: elif IfcStore.schema is None: IfcStore.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(IfcStore.file.schema) return IfcStore.schema + + @staticmethod + def link_element(element, obj): + IfcStore.id_map[element.id()] = obj + IfcStore.guid_map[element.GlobalId] = obj + obj.BIMObjectProperties.ifc_definition_id = element.id() + + @staticmethod + def unlink_element(element, obj=None): + del IfcStore.id_map[element.id()] + del IfcStore.guid_map[element.GlobalId] + if obj: + obj.BIMObjectProperties.ifc_definition_id = 0 diff --git a/src/ifcblenderexport/blenderbim/bim/import_ifc.py b/src/ifcblenderexport/blenderbim/bim/import_ifc.py index f7c67a801b..686e7f7766 100644 --- a/src/ifcblenderexport/blenderbim/bim/import_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/import_ifc.py @@ -21,7 +21,7 @@ import numpy as np from pathlib import Path from itertools import cycle from datetime import datetime -from . import ifc +from blenderbim.bim.ifc import IfcStore from . import schema @@ -300,7 +300,6 @@ class IfcImporter: self.mesh_shapes = {} self.time = 0 self.unit_scale = 1 - self.added_data = {} self.native_elements = {} self.native_data = {} self.aggregates = {} @@ -752,7 +751,7 @@ class IfcImporter: mesh = None obj = bpy.data.objects.new(self.get_name(element), mesh) - obj.BIMObjectProperties.ifc_definition_id = element.id() + IfcStore.link_element(element, obj) if shape: m = shape.transformation.matrix.data @@ -766,7 +765,6 @@ class IfcImporter: obj.matrix_world = self.apply_blender_offset_to_matrix(self.get_element_matrix(element)) self.add_opening_relation(element, obj) - self.added_data[element.GlobalId] = obj if element.is_a("IfcOpeningElement"): obj.display_type = "WIRE" @@ -996,7 +994,7 @@ class IfcImporter: def merge_by_class(self): merge_set = {} - for obj in self.added_data.values(): + for obj in IfcStore.id_map.values(): if "/" not in obj.name or "IfcRelAggregates" in obj.users_collection[0].name: continue merge_set.setdefault(obj.name.split("/")[0], []).append(obj) @@ -1004,7 +1002,7 @@ class IfcImporter: def merge_by_material(self): merge_set = {} - for obj in self.added_data.values(): + for obj in IfcStore.id_map.values(): if "/" not in obj.name or "IfcRelAggregates" in obj.users_collection[0].name: continue if not obj.material_slots: @@ -1030,7 +1028,7 @@ class IfcImporter: cleaned_material["material"] = bpy.data.materials.new("Merged Material") cleaned_material["material"].diffuse_color = cleaned_material["diffuse_color"] - for obj in self.added_data.values(): + for obj in IfcStore.id_map.values(): if not hasattr(obj, "material_slots") or not obj.material_slots: continue for slot in obj.material_slots: @@ -1054,7 +1052,7 @@ class IfcImporter: def clean_mesh(self): obj = None last_obj = None - for obj in self.added_data.values(): + for obj in IfcStore.id_map.values(): if obj.type == "MESH": obj.select_set(True) last_obj = obj @@ -1101,11 +1099,11 @@ class IfcImporter: ) elif extension.lower() == "ifc": self.file = ifcopenshell.open(self.ifc_import_settings.input_file) - ifc.IfcStore.file = self.file + IfcStore.file = self.file def set_ifc_file(self): bpy.context.scene.BIMProperties.ifc_file = self.ifc_import_settings.input_file - ifc.IfcStore.path = "self.ifc_import_settings.input_file" + IfcStore.path = "self.ifc_import_settings.input_file" def calculate_unit_scale(self): units = self.file.by_type("IfcUnitAssignment")[0] @@ -1156,7 +1154,6 @@ class IfcImporter: obj = self.create_product(self.project["ifc"]) if obj: self.project["blender"].objects.link(obj) - del self.added_data[self.project["ifc"].GlobalId] def create_spatial_hierarchy(self): if self.project["ifc"].IsDecomposedBy: @@ -1179,7 +1176,6 @@ class IfcImporter: if obj: self.spatial_structure_elements[global_id]["blender_obj"] = obj collection.objects.link(obj) - del self.added_data[element.GlobalId] if element.IsDecomposedBy: for rel_aggregate in element.IsDecomposedBy: self.add_related_objects(collection, rel_aggregate.RelatedObjects) @@ -1205,7 +1201,7 @@ class IfcImporter: element = rel_aggregate.RelatingObject obj = bpy.data.objects.new("{}/{}".format(element.is_a(), element.Name), None) - obj.BIMObjectProperties.ifc_definition_id = element.id() + IfcStore.link_element(element, obj) self.place_object_in_spatial_tree(element, obj) collection = bpy.data.collections.new(obj.name) @@ -1246,11 +1242,15 @@ class IfcImporter: modifier.object = opening def place_objects_in_spatial_tree(self): - for global_id, obj in self.added_data.items(): - self.place_object_in_spatial_tree(self.file.by_guid(global_id), obj) + for ifc_definition_id, obj in IfcStore.id_map.items(): + self.place_object_in_spatial_tree(self.file.by_id(ifc_definition_id), obj) def place_object_in_spatial_tree(self, element, obj): - if ( + if element.is_a("IfcProject"): + return + elif element.GlobalId in self.spatial_structure_elements: + return + elif ( hasattr(element, "ContainedInStructure") and element.ContainedInStructure and element.ContainedInStructure[0].RelatingStructure @@ -1413,9 +1413,7 @@ class IfcImporter: if self.ifc_import_settings.should_offset_model: # Potentially, there is a smarter way to do this. See #1047 v_index = cycle((0, 1, 2)) - verts = [ - v + self.ifc_import_settings.model_offset_coordinates[next(v_index)] for v in verts - ] + verts = [v + self.ifc_import_settings.model_offset_coordinates[next(v_index)] for v in verts] mesh.vertices.foreach_set("co", verts) else: mesh.vertices.foreach_set("co", verts) @@ -1570,7 +1568,6 @@ class IfcImportSettings: self.ifc_import_filter = "NONE" self.ifc_selector = "" - @staticmethod def factory(context, input_file, logger): scene_bim = context.scene.BIMProperties diff --git a/src/ifcblenderexport/blenderbim/bim/module/root/operator.py b/src/ifcblenderexport/blenderbim/bim/module/root/operator.py index e65b38b983..513a5fdc6e 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/root/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/module/root/operator.py @@ -66,7 +66,7 @@ class ReassignClass(bpy.types.Operator): }, ).execute() obj.name = "{}/{}".format(product.is_a(), "/".join(obj.name.split("/")[1:])) - obj.BIMObjectProperties.ifc_definition_id = int(product.id()) + IfcStore.link_element(product, obj) bpy.context.active_object.BIMObjectProperties.is_reassigning_class = False return {"FINISHED"} @@ -108,7 +108,7 @@ class AssignClass(bpy.types.Operator): }, ).execute() obj.name = "{}/{}".format(product.is_a(), obj.name) - obj.BIMObjectProperties.ifc_definition_id = int(product.id()) + IfcStore.link_element(product, obj) if obj.data: bpy.ops.bim.add_representation(obj=obj.name, context_id=self.context_id) @@ -176,12 +176,13 @@ class UnassignClass(bpy.types.Operator): for obj in objects: if not obj.BIMObjectProperties.ifc_definition_id: continue + product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) + IfcStore.unlink_element(product, obj) usecase = remove_product.Usecase( self.file, - {"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)}, + {"product": product}, ) usecase.execute() - obj.BIMObjectProperties.ifc_definition_id = 0 if "/" in obj.name and obj.name[0:3] == "Ifc": obj.name = "/".join(obj.name.split("/")[1:]) return {"FINISHED"} @@ -221,7 +222,7 @@ class CopyClass(bpy.types.Operator): result = copy_class.Usecase(self.file, { "product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) }).execute() - obj.BIMObjectProperties.ifc_definition_id = result.id() + IfcStore.link_element(result, obj) if obj.data.users == 1: bpy.ops.bim.add_representation(obj=obj.name) else: diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 3e240a9709..e24bea6240 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -11,7 +11,6 @@ from . import decoration import bpy from blenderbim.bim.ifc import IfcStore from bpy.types import PropertyGroup -from bpy.app.handlers import persistent from bpy.props import ( PointerProperty, StringProperty, @@ -35,83 +34,6 @@ sheets_enum = [] vector_styles_enum = [] -@persistent -def clearIfcStore(scene): - IfcStore.file = None - IfcStore.schema = None - - -@persistent -def setDefaultProperties(scene): - if len(bpy.context.scene.DocProperties.drawing_styles) == 0: - drawing_style = bpy.context.scene.DocProperties.drawing_styles.add() - drawing_style.name = "Technical" - drawing_style.render_type = "VIEWPORT" - drawing_style.raster_style = json.dumps( - { - "bpy.data.worlds[0].color": (1, 1, 1), - "bpy.context.scene.render.engine": "BLENDER_WORKBENCH", - "bpy.context.scene.render.film_transparent": False, - "bpy.context.scene.display.shading.show_object_outline": True, - "bpy.context.scene.display.shading.show_cavity": False, - "bpy.context.scene.display.shading.cavity_type": "BOTH", - "bpy.context.scene.display.shading.curvature_ridge_factor": 1, - "bpy.context.scene.display.shading.curvature_valley_factor": 1, - "bpy.context.scene.view_settings.view_transform": "Standard", - "bpy.context.scene.display.shading.light": "FLAT", - "bpy.context.scene.display.shading.color_type": "SINGLE", - "bpy.context.scene.display.shading.single_color": (1, 1, 1), - "bpy.context.scene.display.shading.show_shadows": False, - "bpy.context.scene.display.shading.shadow_intensity": 0.5, - "bpy.context.scene.display.light_direction": (0.5, 0.5, 0.5), - "bpy.context.scene.view_settings.use_curve_mapping": False, - "space.overlay.show_wireframes": True, - "space.overlay.wireframe_threshold": 0, - "space.overlay.show_floor": False, - "space.overlay.show_axis_x": False, - "space.overlay.show_axis_y": False, - "space.overlay.show_axis_z": False, - "space.overlay.show_object_origins": False, - "space.overlay.show_relationship_lines": False, - } - ) - drawing_style = bpy.context.scene.DocProperties.drawing_styles.add() - drawing_style.name = "Shaded" - drawing_style.render_type = "VIEWPORT" - drawing_style.raster_style = json.dumps( - { - "bpy.data.worlds[0].color": (1, 1, 1), - "bpy.context.scene.render.engine": "BLENDER_WORKBENCH", - "bpy.context.scene.render.film_transparent": False, - "bpy.context.scene.display.shading.show_object_outline": True, - "bpy.context.scene.display.shading.show_cavity": True, - "bpy.context.scene.display.shading.cavity_type": "BOTH", - "bpy.context.scene.display.shading.curvature_ridge_factor": 1, - "bpy.context.scene.display.shading.curvature_valley_factor": 1, - "bpy.context.scene.view_settings.view_transform": "Standard", - "bpy.context.scene.display.shading.light": "STUDIO", - "bpy.context.scene.display.shading.color_type": "MATERIAL", - "bpy.context.scene.display.shading.single_color": (1, 1, 1), - "bpy.context.scene.display.shading.show_shadows": True, - "bpy.context.scene.display.shading.shadow_intensity": 0.5, - "bpy.context.scene.display.light_direction": (0.5, 0.5, 0.5), - "bpy.context.scene.view_settings.use_curve_mapping": False, - "space.overlay.show_wireframes": True, - "space.overlay.wireframe_threshold": 0, - "space.overlay.show_floor": False, - "space.overlay.show_axis_x": False, - "space.overlay.show_axis_y": False, - "space.overlay.show_axis_z": False, - "space.overlay.show_object_origins": False, - "space.overlay.show_relationship_lines": False, - } - ) - drawing_style = bpy.context.scene.DocProperties.drawing_styles.add() - drawing_style.name = "Blender Default" - drawing_style.render_type = "DEFAULT" - bpy.ops.bim.save_drawing_style(index="2") - - def getDiagramScales(self, context): global diagram_scales_enum if ( @@ -238,15 +160,6 @@ def toggleDecorations(self, context): decoration.DecorationsHandler.uninstall() -@persistent -def toggleDecorationsOnLoad(*args): - toggle = bpy.context.scene.DocProperties.should_draw_decorations - if toggle: - decoration.DecorationsHandler.install(bpy.context) - else: - decoration.DecorationsHandler.uninstall() - - def getMaterialPsetNames(self, context): global materialpsetnames_enum materialpsetnames_enum.clear() @@ -479,6 +392,8 @@ class BIMProperties(PropertyGroup): schema_dir: StringProperty(default=os.path.join(cwd, "schema") + os.path.sep, name="Schema Directory") data_dir: StringProperty(default=os.path.join(cwd, "data") + os.path.sep, name="Data Directory") ifc_file: StringProperty(name="IFC File") + id_map: StringProperty(name="ID Map") + guid_map: StringProperty(name="GUID Map") export_schema: EnumProperty(items=[("IFC4", "IFC4", ""), ("IFC2X3", "IFC2X3", "")], name="IFC Schema") contexts: EnumProperty(items=getContexts, name="Contexts") available_contexts: EnumProperty(items=[("Model", "Model", ""), ("Plan", "Plan", "")], name="Available Contexts")