diff --git a/src/blenderbim/blenderbim/bim/__init__.py b/src/blenderbim/blenderbim/bim/__init__.py index 8ecb59a8ee..7ec82ae4fa 100644 --- a/src/blenderbim/blenderbim/bim/__init__.py +++ b/src/blenderbim/blenderbim/bim/__init__.py @@ -21,6 +21,7 @@ import importlib from . import handler, ui, prop, operator modules = { + "lendlease": None, "project": None, "search": None, "bcf": None, @@ -74,8 +75,6 @@ classes = [ operator.SelectDataDir, operator.SelectSchemaDir, operator.SelectIfcFile, - operator.ExportIFC, - operator.ImportIFC, operator.OpenUpstream, operator.AddSectionPlane, operator.RemoveSectionPlane, @@ -105,13 +104,6 @@ for mod in modules.values(): classes.extend(mod.classes) -def menu_func_export(self, context): - self.layout.operator(operator.ExportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcjson)") - - -def menu_func_import(self, context): - self.layout.operator(operator.ImportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcxml)") - def on_register(scene): handler.setDefaultProperties(scene) @@ -129,8 +121,6 @@ def register(): bpy.app.handlers.load_post.append(handler.setDefaultProperties) bpy.app.handlers.load_post.append(handler.loadIfcStore) bpy.app.handlers.save_pre.append(handler.ensureIfcExported) - 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) bpy.types.Object.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties) bpy.types.Material.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties) @@ -151,8 +141,6 @@ def unregister(): bpy.app.handlers.load_post.remove(handler.setDefaultProperties) bpy.app.handlers.load_post.remove(handler.loadIfcStore) bpy.app.handlers.save_pre.remove(handler.ensureIfcExported) - 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 del bpy.types.Object.BIMObjectProperties del bpy.types.Material.BIMObjectProperties diff --git a/src/blenderbim/blenderbim/bim/export_ifc.py b/src/blenderbim/blenderbim/bim/export_ifc.py index df8fb90be6..25cb6bd27d 100644 --- a/src/blenderbim/blenderbim/bim/export_ifc.py +++ b/src/blenderbim/blenderbim/bim/export_ifc.py @@ -28,6 +28,8 @@ import ifcopenshell import ifcopenshell.api import ifcopenshell.util.placement import blenderbim.tool as tool +import blenderbim.core.aggregate +import blenderbim.core.spatial import blenderbim.core.style from blenderbim.bim.ifc import IfcStore @@ -135,7 +137,7 @@ class IfcExporter: float(props.blender_x_axis_ordinate), ) if not np.allclose(ifc_matrix, blender_matrix, atol=0.0001): - bpy.ops.bim.edit_object_placement(obj=obj.name) + blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Surveyor, obj=obj) def sync_object_container(self, guid, obj): element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) @@ -166,9 +168,13 @@ class IfcExporter: if parent.is_a("IfcSpatialStructureElement") and not element.is_a("IfcSpatialStructureElement"): if parent != ifcopenshell.util.element.get_container(element): - bpy.ops.bim.assign_container(relating_structure=parent.id(), related_element=obj.name) + blenderbim.core.spatial.assign_container( + tool.Ifc, tool.Collector, tool.Container, structure_obj=parent_obj, element_obj=obj + ) elif parent != ifcopenshell.util.element.get_aggregate(element): - bpy.ops.bim.assign_object(relating_object=parent_obj.name, related_object=obj.name) + blenderbim.core.aggregate.assign_object( + tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=parent_obj, related_obj=obj + ) def should_delete(self, obj): try: diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index e60cb0e519..e1e611a0fd 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -305,7 +305,7 @@ class UpdateRepresentation(bpy.types.Operator): ifcopenshell.api.run("grid.create_axis_curve", self.file, **{"axis_curve": obj, "grid_axis": product}) return - bpy.ops.bim.edit_object_placement(obj=obj.name) + core.edit_object_placement(tool.Ifc, tool.Surveyor, obj=obj) old_representation = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id) context_of_items = old_representation.ContextOfItems @@ -335,7 +335,7 @@ class UpdateRepresentation(bpy.types.Operator): new_representation = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data) [ - bpy.ops.bim.add_style(material=s.material.name) + blenderbim.core.style.add_style(tool.Ifc, tool.Style, obj=s.material) for s in obj.material_slots if s.material and not s.material.BIMMaterialProperties.ifc_style_id ] diff --git a/src/blenderbim/blenderbim/bim/module/project/__init__.py b/src/blenderbim/blenderbim/bim/module/project/__init__.py index a486c8d554..45c5f3a0f8 100644 --- a/src/blenderbim/blenderbim/bim/module/project/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/project/__init__.py @@ -39,6 +39,8 @@ classes = ( operator.EnableEditingHeader, operator.DisableEditingHeader, operator.EditHeader, + operator.ExportIFC, + operator.ImportIFC, prop.LibraryElement, prop.FilterCategory, prop.Link, @@ -51,10 +53,21 @@ classes = ( ui.BIM_UL_links, ) +def menu_func_export(self, context): + self.layout.operator(operator.ExportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcjson)") + + +def menu_func_import(self, context): + self.layout.operator(operator.ImportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcxml)") + def register(): bpy.types.Scene.BIMProjectProperties = bpy.props.PointerProperty(type=prop.BIMProjectProperties) + bpy.types.TOPBAR_MT_file_export.append(menu_func_export) + bpy.types.TOPBAR_MT_file_import.append(menu_func_import) def unregister(): + bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) + bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) del bpy.types.Scene.BIMProjectProperties diff --git a/src/blenderbim/blenderbim/bim/module/project/operator.py b/src/blenderbim/blenderbim/bim/module/project/operator.py index e0a814a952..c92f7fe9ef 100644 --- a/src/blenderbim/blenderbim/bim/module/project/operator.py +++ b/src/blenderbim/blenderbim/bim/module/project/operator.py @@ -31,6 +31,7 @@ import blenderbim.core.context import blenderbim.core.owner from blenderbim.bim.ifc import IfcStore from blenderbim.bim import import_ifc +from blenderbim.bim import export_ifc class CreateProject(bpy.types.Operator): @@ -729,3 +730,79 @@ class LoadLink(bpy.types.Operator): link = context.scene.BIMProjectProperties.links.get(self.filepath) link.is_loaded = True return {"FINISHED"} + + +class ExportIFC(bpy.types.Operator): + bl_idname = "export_ifc.bim" + bl_label = "Export 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) + + def invoke(self, context, event): + if not IfcStore.get_file(): + self.report({"ERROR"}, "No IFC project is available for export - create or import a project first.") + return {"FINISHED"} + if context.scene.BIMProperties.ifc_file: + self.filepath = context.scene.BIMProperties.ifc_file + return self.execute(context) + if not self.filepath: + self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".ifc") + WindowManager = context.window_manager + WindowManager.fileselect_add(self) + return {"RUNNING_MODAL"} + + def execute(self, context): + return IfcStore.execute_ifc_operator(self, context) + + 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) + 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 not scene.BIMProperties.ifc_file: + scene.BIMProperties.ifc_file = output_file + if bpy.data.is_saved and bpy.data.is_dirty and bpy.data.filepath: + bpy.ops.wm.save_mainfile(filepath=bpy.data.filepath) + blenderbim.bim.handler.purge_module_data() + return {"FINISHED"} + + +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"} diff --git a/src/blenderbim/blenderbim/bim/module/spatial/operator.py b/src/blenderbim/blenderbim/bim/module/spatial/operator.py index 2c867e4515..f783ebba33 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/operator.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/operator.py @@ -43,11 +43,7 @@ class AssignContainer(bpy.types.Operator, Operator): structure_obj = tool.Ifc.get_object(tool.Ifc.get().by_id(self.structure)) for element_obj in context.selected_objects: core.assign_container( - tool.Ifc, - tool.Collector, - tool.Container, - structure_obj=structure_obj, - element_obj=element_obj, + tool.Ifc, tool.Collector, tool.Container, structure_obj=structure_obj, element_obj=element_obj ) @@ -127,6 +123,8 @@ class CopyToContainer(bpy.types.Operator): new_obj.data = obj.data.copy() new_obj.matrix_world = container_obj.matrix_world @ local_position bpy.ops.bim.copy_class(obj=new_obj.name) - bpy.ops.bim.assign_container(relating_structure=container_id, related_element=new_obj.name) + core.assign_container( + tool.Ifc, tool.Collector, tool.Container, structure_obj=container_obj, element_obj=new_obj + ) obj.BIMObjectSpatialProperties.is_editing = False return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/operator.py b/src/blenderbim/blenderbim/bim/operator.py index 9c6fe239cc..88f2e37e71 100644 --- a/src/blenderbim/blenderbim/bim/operator.py +++ b/src/blenderbim/blenderbim/bim/operator.py @@ -25,89 +25,12 @@ import logging import webbrowser import ifcopenshell import blenderbim.bim.handler -from . import export_ifc from . import schema from blenderbim.bim.ifc import IfcStore from mathutils import Vector, Matrix, Euler from math import radians -class ExportIFC(bpy.types.Operator): - bl_idname = "export_ifc.bim" - bl_label = "Export 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) - - def invoke(self, context, event): - if not IfcStore.get_file(): - self.report({"ERROR"}, "No IFC project is available for export - create or import a project first.") - return {"FINISHED"} - if context.scene.BIMProperties.ifc_file: - self.filepath = context.scene.BIMProperties.ifc_file - return self.execute(context) - if not self.filepath: - self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".ifc") - WindowManager = context.window_manager - WindowManager.fileselect_add(self) - return {"RUNNING_MODAL"} - - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - - 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) - 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 not scene.BIMProperties.ifc_file: - scene.BIMProperties.ifc_file = output_file - if bpy.data.is_saved and bpy.data.is_dirty and bpy.data.filepath: - bpy.ops.wm.save_mainfile(filepath=bpy.data.filepath) - blenderbim.bim.handler.purge_module_data() - return {"FINISHED"} - - -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"} - - class OpenUri(bpy.types.Operator): bl_idname = "bim.open_uri" bl_label = "Open URI" diff --git a/src/blenderbim/test/bim/feature/project.feature b/src/blenderbim/test/bim/feature/project.feature index 9d462fe1e1..28fb55c42c 100644 --- a/src/blenderbim/test/bim/feature/project.feature +++ b/src/blenderbim/test/bim/feature/project.feature @@ -275,3 +275,14 @@ Scenario: Unlink IFC When I press "bim.unlink_ifc(filepath='{cwd}/test/files/basic.blend')" Then "scene.BIMProjectProperties.links.get('{cwd}/test/files/basic.blend')" is "None" And "scene.collection.children.get('IfcProject/My Project')" is "None" + +Scenario: Export IFC - blank project + Given an empty IFC project + When I press "export_ifc.bim(filepath='{cwd}/test/files/export.ifc')" + Then nothing happens + +Scenario: Export IFC - with basic contents + Given an empty Blender session + And I press "bim.load_project(filepath='{cwd}/test/files/basic.ifc')" + When I press "export_ifc.bim(filepath='{cwd}/test/files/export.ifc')" + Then nothing happens