# BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # # This file is part of BlenderBIM Add-on. # # BlenderBIM Add-on is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # BlenderBIM Add-on is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . import os import bpy import time import logging import tempfile import ifcopenshell import ifcopenshell.api import ifcopenshell.util.selector import ifcopenshell.util.representation import blenderbim.bim.handler import blenderbim.bim.schema import blenderbim.tool as tool import blenderbim.core.project as core import blenderbim.core.context import blenderbim.core.owner from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ui import IFCFileSelector from blenderbim.bim import import_ifc from blenderbim.bim import export_ifc from pathlib import Path from bpy.app.handlers import persistent class NewProject(bpy.types.Operator): bl_idname = "bim.new_project" bl_label = "New Project" bl_options = {"REGISTER", "UNDO"} bl_description = "Start a new IFC project in a fresh session" preset: bpy.props.StringProperty() def execute(self, context): bpy.ops.wm.read_homefile() if self.preset == "metric_m": bpy.context.scene.BIMProjectProperties.export_schema = "IFC4" bpy.context.scene.unit_settings.system = "METRIC" bpy.context.scene.unit_settings.length_unit = "METERS" bpy.context.scene.BIMProperties.area_unit = "SQUARE_METRE" bpy.context.scene.BIMProperties.volume_unit = "CUBIC_METRE" bpy.context.scene.BIMProjectProperties.template_file = "0" elif self.preset == "metric_mm": bpy.context.scene.BIMProjectProperties.export_schema = "IFC4" bpy.context.scene.unit_settings.system = "METRIC" bpy.context.scene.unit_settings.length_unit = "MILLIMETERS" bpy.context.scene.BIMProperties.area_unit = "SQUARE_METRE" bpy.context.scene.BIMProperties.volume_unit = "CUBIC_METRE" bpy.context.scene.BIMProjectProperties.template_file = "0" elif self.preset == "imperial_ft": bpy.context.scene.BIMProjectProperties.export_schema = "IFC4" bpy.context.scene.unit_settings.system = "IMPERIAL" bpy.context.scene.unit_settings.length_unit = "FEET" bpy.context.scene.BIMProperties.area_unit = "square foot" bpy.context.scene.BIMProperties.volume_unit = "cubic foot" bpy.context.scene.BIMProjectProperties.template_file = "0" elif self.preset == "demo": bpy.context.scene.BIMProjectProperties.export_schema = "IFC4" bpy.context.scene.unit_settings.system = "METRIC" bpy.context.scene.unit_settings.length_unit = "MILLIMETERS" bpy.context.scene.BIMProperties.area_unit = "SQUARE_METRE" bpy.context.scene.BIMProperties.volume_unit = "CUBIC_METRE" bpy.context.scene.BIMProjectProperties.template_file = "IFC4 Demo Library.ifc" if self.preset != "wizard": bpy.ops.bim.create_project() return {"FINISHED"} class CreateProject(bpy.types.Operator): bl_idname = "bim.create_project" bl_label = "Create Project" bl_options = {"REGISTER", "UNDO"} bl_description = "Create a new IFC project" def execute(self, context): IfcStore.begin_transaction(self) IfcStore.add_transaction_operation(self, rollback=self.rollback, commit=lambda data: True) self._execute(context) self.transaction_data = {"file": tool.Ifc.get()} IfcStore.add_transaction_operation(self, rollback=lambda data: True, commit=self.commit) IfcStore.end_transaction(self) return {"FINISHED"} def _execute(self, context): props = context.scene.BIMProjectProperties template = None if props.template_file == "0" else props.template_file blenderbim.bim.schema.reload(props.export_schema) if tool.Blender.is_default_scene(): for obj in bpy.data.objects: bpy.data.objects.remove(obj) core.create_project(tool.Ifc, tool.Project, schema=props.export_schema, template=template) def rollback(self, data): IfcStore.file = None def commit(self, data): IfcStore.file = data["file"] class SelectLibraryFile(bpy.types.Operator, IFCFileSelector): bl_idname = "bim.select_library_file" bl_label = "Select Library File" bl_options = {"REGISTER", "UNDO"} bl_description = "Select an IFC file that can be used as a library" filepath: bpy.props.StringProperty(subtype="FILE_PATH") filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"}) append_all: bpy.props.BoolProperty(default=False) use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False) def execute(self, context): IfcStore.begin_transaction(self) old_filepath = IfcStore.library_path result = self._execute(context) self.transaction_data = {"old_filepath": old_filepath, "filepath": self.get_filepath()} IfcStore.add_transaction_operation(self) IfcStore.end_transaction(self) return result def _execute(self, context): filepath = self.get_filepath() IfcStore.library_path = filepath IfcStore.library_file = ifcopenshell.open(filepath) bpy.ops.bim.refresh_library() if context.area: context.area.tag_redraw() if self.append_all: bpy.ops.bim.append_entire_library() return {"FINISHED"} def invoke(self, context, event): context.window_manager.fileselect_add(self) return {"RUNNING_MODAL"} def rollback(self, data): if data["old_filepath"]: IfcStore.library_path = data["old_filepath"] IfcStore.library_file = ifcopenshell.open(data["old_filepath"]) else: IfcStore.library_path = "" IfcStore.library_file = None def commit(self, data): IfcStore.library_path = data["filepath"] IfcStore.library_file = ifcopenshell.open(data["filepath"]) def draw(self, context): self.layout.prop(self, "append_all", text="Append Entire Library") class RefreshLibrary(bpy.types.Operator): bl_idname = "bim.refresh_library" bl_label = "Refresh Library" def execute(self, context): self.props = context.scene.BIMProjectProperties self.props.library_elements.clear() self.props.library_breadcrumb.clear() self.props.active_library_element = "" types = IfcStore.library_file.wrapped_data.types_with_super() for importable_type in sorted(["IfcTypeProduct", "IfcMaterial", "IfcCostSchedule", "IfcProfileDef"]): if importable_type in types: new = self.props.library_elements.add() new.name = importable_type return {"FINISHED"} class ChangeLibraryElement(bpy.types.Operator): bl_idname = "bim.change_library_element" bl_label = "Change Library Element" bl_options = {"REGISTER", "UNDO"} element_name: bpy.props.StringProperty() def execute(self, context): self.props = context.scene.BIMProjectProperties self.file = IfcStore.get_file() self.library_file = IfcStore.library_file ifc_classes = set() self.props.active_library_element = self.element_name crumb = self.props.library_breadcrumb.add() crumb.name = self.element_name elements = self.library_file.by_type(self.element_name) [ifc_classes.add(e.is_a()) for e in elements] self.props.library_elements.clear() if len(ifc_classes) == 1 and list(ifc_classes)[0] == self.element_name: for name, ifc_definition_id in sorted([(self.get_name(e), e.id()) for e in elements]): self.add_library_asset(name, ifc_definition_id) else: for ifc_class in sorted(ifc_classes): if ifc_class == self.element_name: continue new = self.props.library_elements.add() new.name = ifc_class for name, ifc_definition_id, ifc_class in sorted([(self.get_name(e), e.id(), e.is_a()) for e in elements]): if ifc_class == self.element_name: self.add_library_asset(name, ifc_definition_id) return {"FINISHED"} def get_name(self, element): if element.is_a("IfcProfileDef"): return element.ProfileName or "Unnamed" return element.Name or "Unnamed" def add_library_asset(self, name, ifc_definition_id): new = self.props.library_elements.add() new.name = name new.ifc_definition_id = ifc_definition_id element = self.library_file.by_id(ifc_definition_id) if self.library_file.schema == "IFC2X3" or not self.library_file.by_type("IfcProjectLibrary"): new.is_declared = False elif getattr(element, "HasContext", None) and element.HasContext[0].RelatingContext.is_a("IfcProjectLibrary"): new.is_declared = True try: if element.is_a("IfcMaterial"): next(e for e in self.file.by_type("IfcMaterial") if e.Name == name) elif element.is_a("IfcProfileDef"): next(e for e in self.file.by_type("IfcProfileDef") if e.ProfileName == name) else: self.file.by_guid(element.GlobalId) new.is_appended = True except (AttributeError, RuntimeError, StopIteration): new.is_appended = False class RewindLibrary(bpy.types.Operator): bl_idname = "bim.rewind_library" bl_label = "Rewind Library" bl_options = {"REGISTER", "UNDO"} def execute(self, context): self.props = context.scene.BIMProjectProperties total_breadcrumbs = len(self.props.library_breadcrumb) if total_breadcrumbs < 2: bpy.ops.bim.refresh_library() return {"FINISHED"} element_name = self.props.library_breadcrumb[total_breadcrumbs - 2].name self.props.library_breadcrumb.remove(total_breadcrumbs - 1) self.props.library_breadcrumb.remove(total_breadcrumbs - 2) bpy.ops.bim.change_library_element(element_name=element_name) return {"FINISHED"} class AssignLibraryDeclaration(bpy.types.Operator): bl_idname = "bim.assign_library_declaration" bl_label = "Assign Library Declaration" bl_options = {"REGISTER", "UNDO"} definition: bpy.props.IntProperty() def execute(self, context): IfcStore.begin_transaction(self) IfcStore.library_file.begin_transaction() result = self._execute(context) IfcStore.library_file.end_transaction() IfcStore.add_transaction_operation(self) IfcStore.end_transaction(self) return result def _execute(self, context): self.props = context.scene.BIMProjectProperties self.file = IfcStore.library_file ifcopenshell.api.run( "project.assign_declaration", self.file, definition=self.file.by_id(self.definition), relating_context=self.file.by_type("IfcProjectLibrary")[0], ) element_name = self.props.active_library_element bpy.ops.bim.rewind_library() bpy.ops.bim.change_library_element(element_name=element_name) return {"FINISHED"} def rollback(self, data): IfcStore.library_file.undo() def commit(self, data): IfcStore.library_file.redo() class UnassignLibraryDeclaration(bpy.types.Operator): bl_idname = "bim.unassign_library_declaration" bl_label = "Unassign Library Declaration" bl_options = {"REGISTER", "UNDO"} definition: bpy.props.IntProperty() def execute(self, context): IfcStore.begin_transaction(self) IfcStore.library_file.begin_transaction() result = self._execute(context) IfcStore.library_file.end_transaction() IfcStore.add_transaction_operation(self) IfcStore.end_transaction(self) return result def _execute(self, context): self.props = context.scene.BIMProjectProperties self.file = IfcStore.library_file ifcopenshell.api.run( "project.unassign_declaration", self.file, definition=self.file.by_id(self.definition), relating_context=self.file.by_type("IfcProjectLibrary")[0], ) element_name = self.props.active_library_element bpy.ops.bim.rewind_library() bpy.ops.bim.change_library_element(element_name=element_name) return {"FINISHED"} def rollback(self, data): IfcStore.library_file.undo() def commit(self, data): IfcStore.library_file.redo() class SaveLibraryFile(bpy.types.Operator): bl_idname = "bim.save_library_file" bl_label = "Save Library File" def execute(self, context): IfcStore.library_file.write(IfcStore.library_path) return {"FINISHED"} class AppendEntireLibrary(bpy.types.Operator): bl_idname = "bim.append_entire_library" bl_label = "Append Entire Library" @classmethod def poll(cls, context): return IfcStore.get_file() def execute(self, context): return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): self.file = IfcStore.get_file() self.library = IfcStore.library_file lib_elements = ifcopenshell.util.selector.filter_elements( self.library, "IfcTypeProduct, IfcMaterial, IfcCostSchedule, IfcProfileDef" ) for element in lib_elements: bpy.ops.bim.append_library_element(definition=element.id()) return {"FINISHED"} class AppendLibraryElementByQuery(bpy.types.Operator): bl_idname = "bim.append_library_element_by_query" bl_label = "Append Library Element By Query" query: bpy.props.StringProperty(name="Query") @classmethod def poll(cls, context): return IfcStore.get_file() def execute(self, context): return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): self.file = IfcStore.get_file() self.library = IfcStore.library_file for element in ifcopenshell.util.selector.filter_elements(self.library, self.query): bpy.ops.bim.append_library_element(definition=element.id()) return {"FINISHED"} def invoke(self, context, event): return context.window_manager.invoke_props_dialog(self) def draw(self, context): self.layout.prop(self, "query") class AppendLibraryElement(bpy.types.Operator): bl_idname = "bim.append_library_element" bl_label = "Append Library Element" bl_options = {"REGISTER", "UNDO"} bl_description = "Append element to the current project" definition: bpy.props.IntProperty() prop_index: bpy.props.IntProperty() @classmethod def poll(cls, context): poll = bool(IfcStore.get_file()) if bpy.app.version > (3, 0, 0) and not poll: cls.poll_message_set("Please create or load a project first.") return poll def execute(self, context): return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): self.file = IfcStore.get_file() element = ifcopenshell.api.run( "project.append_asset", self.file, library=IfcStore.library_file, element=IfcStore.library_file.by_id(self.definition), ) if not element: return {"FINISHED"} if element.is_a("IfcTypeProduct"): self.import_type_from_ifc(element, context) elif element.is_a("IfcProduct"): self.import_product_from_ifc(element, context) elif element.is_a("IfcMaterial"): self.import_material_from_ifc(element, context) try: context.scene.BIMProjectProperties.library_elements[self.prop_index].is_appended = True except: # TODO Remove this terrible code when I refactor this into the core pass blenderbim.bim.handler.refresh_ui_data() return {"FINISHED"} def import_material_from_ifc(self, element, context): self.file = IfcStore.get_file() logger = logging.getLogger("ImportIFC") ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger) ifc_importer = import_ifc.IfcImporter(ifc_import_settings) ifc_importer.file = self.file blender_material = ifc_importer.create_material(element) self.import_material_styles(blender_material, element, ifc_importer) def import_product_from_ifc(self, element, context): self.file = IfcStore.get_file() logger = logging.getLogger("ImportIFC") ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger) ifc_importer = import_ifc.IfcImporter(ifc_import_settings) ifc_importer.file = self.file ifc_importer.process_context_filter() ifc_importer.material_creator.load_existing_materials() self.import_materials(element, ifc_importer) self.import_styles(element, ifc_importer) ifc_importer.create_generic_elements({element}) ifc_importer.place_objects_in_collections() def import_type_from_ifc(self, element, context): self.file = IfcStore.get_file() logger = logging.getLogger("ImportIFC") ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger) type_collection = bpy.data.collections.get("Types") if not type_collection: type_collection = bpy.data.collections.new("Types") for collection in bpy.context.view_layer.layer_collection.children: collection_obj = collection.collection.BIMCollectionProperties.obj if collection_obj and tool.Ifc.get_entity(collection_obj).is_a("IfcProject"): collection.collection.children.link(type_collection) collection.children["Types"].hide_viewport = True break ifc_importer = import_ifc.IfcImporter(ifc_import_settings) ifc_importer.file = self.file ifc_importer.type_collection = type_collection ifc_importer.process_context_filter() ifc_importer.material_creator.load_existing_materials() self.import_materials(element, ifc_importer) self.import_styles(element, ifc_importer) ifc_importer.create_element_type(element) ifc_importer.place_objects_in_collections() def import_materials(self, element, ifc_importer): for material in ifcopenshell.util.element.get_materials(element): if IfcStore.get_element(material.id()): continue blender_material = ifc_importer.create_material(material) self.import_material_styles(blender_material, material, ifc_importer) def import_styles(self, element, ifc_importer): if element.is_a("IfcTypeProduct"): representations = element.RepresentationMaps or [] elif element.is_a("IfcProduct"): representations = [element.Representation] if element.Representation else [] for representation in representations or []: for element in self.file.traverse(representation): if not element.is_a("IfcRepresentationItem") or not element.StyledByItem: continue for element2 in self.file.traverse(element.StyledByItem[0]): if element2.is_a("IfcSurfaceStyle") and not IfcStore.get_element(element2.id()): ifc_importer.create_style(element2) def import_material_styles(self, blender_material, material, ifc_importer): if not material.HasRepresentation: return for element in self.file.traverse(material.HasRepresentation[0]): if element.is_a("IfcSurfaceStyle") and not IfcStore.get_element(element.id()): ifc_importer.create_style(element, blender_material) class EnableEditingHeader(bpy.types.Operator): bl_idname = "bim.enable_editing_header" bl_label = "Enable Editing Header" bl_options = {"REGISTER", "UNDO"} bl_description = "Edit the IFC header file such as Author, Organization, ..." @classmethod def poll(cls, context): return IfcStore.get_file() def execute(self, context): self.file = IfcStore.get_file() props = context.scene.BIMProjectProperties props.is_editing = True mvd = "".join(IfcStore.get_file().wrapped_data.header.file_description.description) if "[" in mvd: props.mvd = mvd.split("[")[1][0:-1] else: props.mvd = "" author = self.file.wrapped_data.header.file_name.author if author: props.author_name = author[0] if len(author) > 1: props.author_email = author[1] organisation = self.file.wrapped_data.header.file_name.organization if organisation: props.organisation_name = organisation[0] if len(organisation) > 1: props.organisation_email = organisation[1] props.authorisation = self.file.wrapped_data.header.file_name.authorization return {"FINISHED"} class EditHeader(bpy.types.Operator): bl_idname = "bim.edit_header" bl_label = "Edit Header" bl_options = {"REGISTER", "UNDO"} bl_description = "Save header information" @classmethod def poll(cls, context): return IfcStore.get_file() def execute(self, context): IfcStore.begin_transaction(self) self.transaction_data = {} self.transaction_data["old"] = self.record_state() result = self._execute(context) self.transaction_data["new"] = self.record_state() IfcStore.add_transaction_operation(self) IfcStore.end_transaction(self) return result def _execute(self, context): self.file = IfcStore.get_file() props = context.scene.BIMProjectProperties props.is_editing = True self.file.wrapped_data.header.file_description.description = (f"ViewDefinition[{props.mvd}]",) self.file.wrapped_data.header.file_name.author = (props.author_name, props.author_email) self.file.wrapped_data.header.file_name.organization = (props.organisation_name, props.organisation_email) self.file.wrapped_data.header.file_name.authorization = props.authorisation bpy.ops.bim.disable_editing_header() return {"FINISHED"} def record_state(self): self.file = IfcStore.get_file() return { "description": self.file.wrapped_data.header.file_description.description, "author": self.file.wrapped_data.header.file_name.author, "organisation": self.file.wrapped_data.header.file_name.organization, "authorisation": self.file.wrapped_data.header.file_name.authorization, } def rollback(self, data): file = IfcStore.get_file() file.wrapped_data.header.file_description.description = data["old"]["description"] file.wrapped_data.header.file_name.author = data["old"]["author"] file.wrapped_data.header.file_name.organization = data["old"]["organisation"] file.wrapped_data.header.file_name.authorization = data["old"]["authorisation"] def commit(self, data): file = IfcStore.get_file() file.wrapped_data.header.file_description.description = data["new"]["description"] file.wrapped_data.header.file_name.author = data["new"]["author"] file.wrapped_data.header.file_name.organization = data["new"]["organisation"] file.wrapped_data.header.file_name.authorization = data["new"]["authorisation"] class DisableEditingHeader(bpy.types.Operator): bl_idname = "bim.disable_editing_header" bl_label = "Disable Editing Header" bl_options = {"REGISTER", "UNDO"} bl_description = "Cancel unsaved header information" def execute(self, context): context.scene.BIMProjectProperties.is_editing = False return {"FINISHED"} class LoadProject(bpy.types.Operator, IFCFileSelector): bl_idname = "bim.load_project" bl_label = "Load Project" bl_options = {"REGISTER", "UNDO"} bl_description = "Load an existing IFC project" filepath: bpy.props.StringProperty(subtype="FILE_PATH") filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml;*.ifcsqlite", options={"HIDDEN"}) is_advanced: bpy.props.BoolProperty(name="Enable Advanced Mode", default=False) use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False) should_start_fresh_session: bpy.props.BoolProperty(name="Should Start Fresh Session", default=True) def execute(self, context): @persistent def load_handler(*args): bpy.app.handlers.load_post.remove(load_handler) self.finish_loading_project(context) if self.should_start_fresh_session: # WARNING: wm.read_homefile clears context which could lead to some # operators to fail: # https://blender.stackexchange.com/a/282558/135166 # So we continue using the load_post handler thats triggered when # context is already restored bpy.app.handlers.load_post.append(load_handler) bpy.ops.wm.read_homefile() return {"FINISHED"} else: return self.finish_loading_project(context) def finish_loading_project(self, context): if not self.is_existing_ifc_file(): return {"FINISHED"} if tool.Blender.is_default_scene(): for obj in bpy.data.objects: bpy.data.objects.remove(obj) context.scene.BIMProperties.ifc_file = self.get_filepath() context.scene.BIMProjectProperties.is_loading = True context.scene.BIMProjectProperties.total_elements = len(tool.Ifc.get().by_type("IfcElement")) if not self.is_advanced: bpy.ops.bim.load_project_elements() return {"FINISHED"} def invoke(self, context, event): context.window_manager.fileselect_add(self) return {"RUNNING_MODAL"} def draw(self, context): self.layout.prop(self, "is_advanced") IFCFileSelector.draw(self, context) class UnloadProject(bpy.types.Operator): bl_idname = "bim.unload_project" bl_label = "Unload Project" bl_options = {"REGISTER", "UNDO"} bl_description = "Unload the IFC project" def execute(self, context): IfcStore.purge() context.scene.BIMProperties.ifc_file = "" context.scene.BIMProjectProperties.is_loading = False return {"FINISHED"} class RevertProject(bpy.types.Operator, IFCFileSelector): bl_idname = "bim.revert_project" bl_label = "Revert IFC Project" bl_options = {"REGISTER", "UNDO"} bl_description = "Reload currently opened IFC project discarding all unsaved changes" @classmethod def poll(cls, context): if not context.scene.BIMProperties.ifc_file: cls.poll_message_set("IFC project need to be loaded and saved on the disk.") return False return True def execute(self, context): bpy.ops.bim.load_project(should_start_fresh_session=True, filepath=context.scene.BIMProperties.ifc_file) return {"FINISHED"} class LoadProjectElements(bpy.types.Operator): bl_idname = "bim.load_project_elements" bl_label = "Load Project Elements" bl_options = {"REGISTER", "UNDO"} def execute(self, context): self.props = context.scene.BIMProjectProperties self.file = IfcStore.get_file() blenderbim.bim.schema.reload(self.file.schema) start = time.time() logger = logging.getLogger("ImportIFC") path_log = os.path.join(context.scene.BIMProperties.data_dir, "process.log") if not os.access(context.scene.BIMProperties.data_dir, os.W_OK): path_log = os.path.join(tempfile.mkdtemp(), "process.log") logging.basicConfig( filename=path_log, filemode="a", level=logging.DEBUG, ) settings = import_ifc.IfcImportSettings.factory(context, context.scene.BIMProperties.ifc_file, logger) settings.has_filter = self.props.filter_mode != "NONE" settings.should_filter_spatial_elements = self.props.should_filter_spatial_elements if self.props.filter_mode == "DECOMPOSITION": settings.elements = self.get_decomposition_elements() elif self.props.filter_mode == "IFC_CLASS": settings.elements = self.get_ifc_class_elements() elif self.props.filter_mode == "IFC_TYPE": settings.elements = self.get_ifc_type_elements() elif self.props.filter_mode == "WHITELIST": settings.elements = self.get_whitelist_elements() elif self.props.filter_mode == "BLACKLIST": settings.elements = self.get_blacklist_elements() settings.logger.info("Starting import") ifc_importer = import_ifc.IfcImporter(settings) ifc_importer.execute() settings.logger.info("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 tool.Project.load_pset_templates() tool.Project.load_default_thumbnails() tool.Project.set_default_context() tool.Project.set_default_modeling_dimensions() return {"FINISHED"} def get_decomposition_elements(self): containers = set() for filter_category in self.props.filter_categories: if not filter_category.is_selected: continue container = self.file.by_id(filter_category.ifc_definition_id) while container: containers.add(container) container = ifcopenshell.util.element.get_aggregate(container) if self.file.schema == "IFC2X3" and container.is_a("IfcProject"): container = None elif self.file.schema != "IFC2X3" and container.is_a("IfcContext"): container = None elements = set() for container in containers: for rel in container.ContainsElements: elements.update(rel.RelatedElements) self.append_decomposed_elements(elements) return elements def append_decomposed_elements(self, elements): decomposed_elements = set() for element in elements: if element.IsDecomposedBy: for subelement in element.IsDecomposedBy[0].RelatedObjects: decomposed_elements.add(subelement) if decomposed_elements: self.append_decomposed_elements(decomposed_elements) elements.update(decomposed_elements) def get_ifc_class_elements(self): elements = set() for filter_category in self.props.filter_categories: if not filter_category.is_selected: continue elements.update(self.file.by_type(filter_category.name, include_subtypes=False)) return elements def get_ifc_type_elements(self): elements = set() for filter_category in self.props.filter_categories: if not filter_category.is_selected: continue elements.update(ifcopenshell.util.element.get_types(self.file.by_id(filter_category.ifc_definition_id))) return elements def get_whitelist_elements(self): return set(ifcopenshell.util.selector.filter_elements(self.file, self.props.filter_query)) def get_blacklist_elements(self): return set(self.file.by_type("IfcElement")) - set( ifcopenshell.util.selector.filter_elements(self.file, self.props.filter_query) ) class ToggleFilterCategories(bpy.types.Operator): bl_idname = "bim.toggle_filter_categories" bl_label = "Toggle Filter Categories" bl_options = {"REGISTER", "UNDO"} should_select: bpy.props.BoolProperty(name="Should Select", default=True) def execute(self, context): for filter_category in context.scene.BIMProjectProperties.filter_categories: filter_category.is_selected = self.should_select return {"FINISHED"} class LinkIfc(bpy.types.Operator): bl_idname = "bim.link_ifc" bl_label = "Link IFC" bl_options = {"REGISTER", "UNDO"} bl_description = "Link a Blender file" filepath: bpy.props.StringProperty(subtype="FILE_PATH") files: bpy.props.CollectionProperty(name="Files", type=bpy.types.OperatorFileListElement) directory: bpy.props.StringProperty(subtype="DIR_PATH") filter_glob: bpy.props.StringProperty(default="*.blend;*.blend1", options={"HIDDEN"}) use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False) def execute(self, context): files = [f.name for f in self.files] if self.files else [self.filepath] for filename in files: filepath = os.path.join(self.directory, filename) if bpy.data.filepath and Path(filepath).samefile(bpy.data.filepath): self.report({"INFO"}, "Can't link the current .blend file") continue new = context.scene.BIMProjectProperties.links.add() if self.use_relative_path: filepath = os.path.relpath(filepath, bpy.path.abspath("//")) new.name = filepath bpy.ops.bim.load_link(filepath=filepath) return {"FINISHED"} def invoke(self, context, event): context.window_manager.fileselect_add(self) return {"RUNNING_MODAL"} class UnlinkIfc(bpy.types.Operator): bl_idname = "bim.unlink_ifc" bl_label = "UnLink IFC" bl_options = {"REGISTER", "UNDO"} bl_description = "Remove the selected file from the link list" filepath: bpy.props.StringProperty() def execute(self, context): bpy.ops.bim.unload_link(filepath=self.filepath) index = context.scene.BIMProjectProperties.links.find(self.filepath) if index != -1: context.scene.BIMProjectProperties.links.remove(index) return {"FINISHED"} class UnloadLink(bpy.types.Operator): bl_idname = "bim.unload_link" bl_label = "Unload Link" bl_options = {"REGISTER", "UNDO"} bl_description = "Unload the selected linked file" filepath: bpy.props.StringProperty() def execute(self, context): filepath = self.filepath if not os.path.isabs(filepath): filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), filepath)) for collection in context.scene.collection.children: if collection.library and collection.library.filepath == filepath: context.scene.collection.children.unlink(collection) for scene in bpy.data.scenes: if scene.library and scene.library.filepath == filepath: bpy.data.scenes.remove(scene) link = context.scene.BIMProjectProperties.links.get(self.filepath) link.is_loaded = False return {"FINISHED"} class LoadLink(bpy.types.Operator): bl_idname = "bim.load_link" bl_label = "Load Link" bl_options = {"REGISTER", "UNDO"} bl_description = "Load the selected file" filepath: bpy.props.StringProperty() def execute(self, context): filepath = self.filepath if not os.path.isabs(filepath): filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), filepath)) with bpy.data.libraries.load(filepath, link=True) as (data_from, data_to): data_to.scenes = data_from.scenes for scene in bpy.data.scenes: if not scene.library or scene.library.filepath != filepath: continue for child in scene.collection.children: if "IfcProject" not in child.name: continue bpy.data.scenes[0].collection.children.link(child) link = context.scene.BIMProjectProperties.links.get(self.filepath) link.is_loaded = True return {"FINISHED"} class ReloadLink(bpy.types.Operator): bl_idname = "bim.reload_link" bl_label = "Reload Link" bl_options = {"REGISTER", "UNDO"} bl_description = "Reload the selected file" filepath: bpy.props.StringProperty() def execute(self, context): def get_linked_ifcs(): selected_filename = os.path.basename(self.filepath) return [ c.library for c in bpy.data.collections if "IfcProject" in c.name and c.library and os.path.basename(c.library.filepath) == selected_filename ] for library in get_linked_ifcs() or []: library.reload() return {"FINISHED"} class ToggleLinkSelectability(bpy.types.Operator): bl_idname = "bim.toggle_link_selectability" bl_label = "Toggle Link Selectability" bl_options = {"REGISTER", "UNDO"} bl_description = "Toggle selectability" link: bpy.props.StringProperty() def execute(self, context): props = context.scene.BIMProjectProperties link = props.links.get(self.link) self.filepath = self.link if not os.path.isabs(self.filepath): self.filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), self.filepath)) for collection in self.get_linked_collections(): collection.hide_select = not collection.hide_select link.is_selectable = not collection.hide_select return {"FINISHED"} def get_linked_collections(self): return [ c for c in bpy.data.collections if "IfcProject" in c.name and c.library and c.library.filepath == self.filepath ] class ToggleLinkVisibility(bpy.types.Operator): bl_idname = "bim.toggle_link_visibility" bl_label = "Toggle Link Visibility" bl_options = {"REGISTER", "UNDO"} bl_description = "Toggle visibility between SOLID and WIREFRAME" link: bpy.props.StringProperty() mode: bpy.props.StringProperty() def execute(self, context): props = context.scene.BIMProjectProperties link = props.links.get(self.link) self.filepath = self.link if not os.path.isabs(self.filepath): self.filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), self.filepath)) if self.mode == "WIREFRAME": self.toggle_wireframe(link) elif self.mode == "VISIBLE": self.toggle_visibility(link) return {"FINISHED"} def toggle_wireframe(self, link): for collection in self.get_linked_collections(): objs = filter(lambda obj: "IfcOpeningElement" not in obj.name, collection.all_objects) for i, obj in enumerate(objs): if i == 0: if obj.display_type == "WIRE": display_type = "TEXTURED" else: display_type = "WIRE" obj.display_type = display_type link.is_wireframe = display_type == "WIRE" def toggle_visibility(self, link): linked_collections = self.get_linked_collections() queue = [bpy.context.view_layer.layer_collection] layer_collection = None while queue: layer = queue.pop() if layer.collection in linked_collections: layer_collection = layer break queue.extend(list(layer.children)) if layer_collection: layer_collection.exclude = not layer_collection.exclude link.is_hidden = layer_collection.exclude def get_linked_collections(self): return [ c for c in bpy.data.collections if "IfcProject" in c.name and c.library and c.library.filepath == self.filepath ] class ExportIFC(bpy.types.Operator): bl_idname = "export_ifc.bim" bl_label = "Save IFC" bl_options = {"REGISTER", "UNDO"} filename_ext = ".ifc" filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml;*.ifcjson", options={"HIDDEN"}) filepath: bpy.props.StringProperty(subtype="FILE_PATH") json_version: bpy.props.EnumProperty(items=[("4", "4", ""), ("5a", "5a", "")], name="IFC JSON Version") json_compact: bpy.props.BoolProperty(name="Export Compact IFCJSON", default=False) should_save_as: bpy.props.BoolProperty(name="Should Save As", default=False, options={"HIDDEN"}) use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False) save_as_invoked: bpy.props.BoolProperty(name="Save As Dialog Was Invoked", default=False, options={"HIDDEN"}) @classmethod def poll(cls, context): return tool.Ifc.get() def draw(self, context): layout = self.layout layout.prop(self, "json_version") layout.prop(self, "json_compact") if bpy.data.is_saved: layout.prop(self, "use_relative_path") def invoke(self, context, event): if not tool.Ifc.get(): bpy.ops.wm.save_mainfile("INVOKE_DEFAULT") return {"FINISHED"} self.save_as_invoked = False if context.scene.BIMProperties.ifc_file and not self.should_save_as: self.filepath = context.scene.BIMProperties.ifc_file if not os.path.isabs(self.filepath): self.filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), self.filepath)) return self.execute(context) if not self.filepath: if bpy.data.is_saved: self.filepath = Path(bpy.data.filepath).with_suffix(".ifc").__str__() else: self.filepath = "untitled.ifc" self.save_as_invoked = True WindowManager = context.window_manager WindowManager.fileselect_add(self) return {"RUNNING_MODAL"} def execute(self, context): project_props = context.scene.BIMProjectProperties if self.save_as_invoked: project_props.use_relative_project_path = self.use_relative_path if project_props.should_disable_undo_on_save: old_history_size = tool.Ifc.get().history_size old_undo_steps = context.preferences.edit.undo_steps tool.Ifc.get().history_size = 0 context.preferences.edit.undo_steps = 0 IfcStore.execute_ifc_operator(self, context) if project_props.should_disable_undo_on_save: tool.Ifc.get().history_size = old_history_size context.preferences.edit.undo_steps = old_undo_steps return {"FINISHED"} def _execute(self, context): start = time.time() logger = logging.getLogger("ExportIFC") path_log = os.path.join(context.scene.BIMProperties.data_dir, "process.log") if not os.access(context.scene.BIMProperties.data_dir, os.W_OK): path_log = os.path.join(tempfile.mkdtemp(), "process.log") logging.basicConfig( filename=path_log, filemode="a", level=logging.DEBUG, ) extension = self.filepath.split(".")[-1] if extension == "ifczip": output_file = bpy.path.ensure_ext(self.filepath, ".ifczip") elif extension == "ifcjson": output_file = bpy.path.ensure_ext(self.filepath, ".ifcjson") else: output_file = bpy.path.ensure_ext(self.filepath, ".ifc") settings = export_ifc.IfcExportSettings.factory(context, output_file, logger) settings.json_version = self.json_version settings.json_compact = self.json_compact ifc_exporter = export_ifc.IfcExporter(settings) print("Starting export") settings.logger.info("Starting export") ifc_exporter.export() settings.logger.info("Export finished in {:.2f} seconds".format(time.time() - start)) print("Export finished in {:.2f} seconds".format(time.time() - start)) scene = context.scene if not scene.DocProperties.ifc_files: new = scene.DocProperties.ifc_files.add() new.name = output_file if context.scene.BIMProjectProperties.use_relative_project_path and bpy.data.is_saved: output_file = os.path.relpath(output_file, bpy.path.abspath("//")) if scene.BIMProperties.ifc_file != output_file and extension not in ["ifczip", "ifcjson"]: scene.BIMProperties.ifc_file = output_file save_blend_file = bool(bpy.data.is_saved and bpy.data.is_dirty and bpy.data.filepath) if save_blend_file: bpy.ops.wm.save_mainfile(filepath=bpy.data.filepath) blenderbim.bim.handler.refresh_ui_data() self.report( {"INFO"}, f'IFC Project "{os.path.basename(output_file)}" {"" if not save_blend_file else "And Current Blend File Are"} Saved', ) if bpy.data.is_saved: bpy.ops.wm.save_mainfile("INVOKE_DEFAULT") @classmethod def description(cls, context, properties): if properties.should_save_as: return "Export the IFC project to a selected file" return "Export the IFC project to this file" class ImportIFC(bpy.types.Operator): bl_idname = "import_ifc.bim" bl_label = "Import IFC" bl_options = {"REGISTER", "UNDO"} def execute(self, context): bpy.ops.bim.load_project("INVOKE_DEFAULT") return {"FINISHED"}