diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py index ba7a501e68..326a4fea29 100644 --- a/src/blenderbim/blenderbim/bim/import_ifc.py +++ b/src/blenderbim/blenderbim/bim/import_ifc.py @@ -200,10 +200,6 @@ class IfcImporter: bpy.context.window_manager.progress_begin(0, 100) self.progress = 0 self.profile_code("Starting import process") - self.load_diff() - self.profile_code("Load diff") - self.purge_diff() - self.profile_code("Purge diffs") self.load_file() self.profile_code("Loading file") self.calculate_unit_scale() @@ -953,15 +949,10 @@ class IfcImporter: return self.openings[element.GlobalId] = obj - def load_diff(self): - if not self.ifc_import_settings.diff_file: - return - with open(self.ifc_import_settings.diff_file, "r") as file: - self.diff = json.load(file) - def load_file(self): self.ifc_import_settings.logger.info("loading file %s", self.ifc_import_settings.input_file) - bpy.context.scene.BIMProperties.ifc_file = self.ifc_import_settings.input_file + if not bpy.context.scene.BIMProperties.ifc_file: + bpy.context.scene.BIMProperties.ifc_file = self.ifc_import_settings.input_file self.file = IfcStore.get_file() def calculate_unit_scale(self): @@ -1128,18 +1119,6 @@ class IfcImporter: def get_name(self, element): return "{}/{}".format(element.is_a(), element.Name) - def purge_diff(self): - if not self.diff: - return - objects_to_purge = [] - for obj in bpy.data.objects: - if "GlobalId" not in obj.BIMObjectProperties.attributes: - continue - global_id = obj.BIMObjectProperties.attributes["GlobalId"].string_value - if global_id in self.diff["deleted"] or global_id in self.diff["changed"].keys(): - objects_to_purge.append(obj) - bpy.ops.object.delete({"selected_objects": objects_to_purge}) - def place_objects_in_spatial_tree(self): for ifc_definition_id, obj in self.added_data.items(): if isinstance(obj, bpy.types.Object): diff --git a/src/blenderbim/blenderbim/bim/module/project/__init__.py b/src/blenderbim/blenderbim/bim/module/project/__init__.py index 331e27bbbe..31c0a48143 100644 --- a/src/blenderbim/blenderbim/bim/module/project/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/project/__init__.py @@ -21,6 +21,8 @@ from . import ui, prop, operator classes = ( operator.CreateProject, + operator.LoadProject, + operator.LoadProjectElements, operator.SelectLibraryFile, operator.ChangeLibraryElement, operator.RefreshLibrary, diff --git a/src/blenderbim/blenderbim/bim/module/project/operator.py b/src/blenderbim/blenderbim/bim/module/project/operator.py index 46a18e69d5..4a8a122f94 100644 --- a/src/blenderbim/blenderbim/bim/module/project/operator.py +++ b/src/blenderbim/blenderbim/bim/module/project/operator.py @@ -16,8 +16,11 @@ # 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.representation @@ -501,3 +504,55 @@ class DisableEditingHeader(bpy.types.Operator): def execute(self, context): context.scene.BIMProjectProperties.is_editing = False return {"FINISHED"} + + +class LoadProject(bpy.types.Operator): + bl_idname = "bim.load_project" + bl_label = "Load Project" + bl_options = {"REGISTER", "UNDO"} + filepath: bpy.props.StringProperty(subtype="FILE_PATH") + filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"}) + + def execute(self, context): + if os.path.exists(self.filepath) and "ifc" in os.path.splitext(self.filepath)[1]: + context.scene.BIMProperties.ifc_file = self.filepath + context.scene.BIMProjectProperties.is_loading = True + return {"FINISHED"} + + def invoke(self, context, event): + context.window_manager.fileselect_add(self) + return {"RUNNING_MODAL"} + + +class LoadProjectElements(bpy.types.Operator): + bl_idname = "bim.load_project_elements" + bl_label = "Load Project Elements" + bl_options = {"REGISTER", "UNDO"} + mode: bpy.props.EnumProperty( + items=[ + ("ALL", "All", ""), + ("WHITELIST", "Whitelist", ""), + ("BLACKLIST", "Blacklist", ""), + ], + name="Mode", + ) + + def execute(self, context): + 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.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 + return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/project/prop.py b/src/blenderbim/blenderbim/bim/module/project/prop.py index 8038a0dcce..bb5545f6dc 100644 --- a/src/blenderbim/blenderbim/bim/module/project/prop.py +++ b/src/blenderbim/blenderbim/bim/module/project/prop.py @@ -41,6 +41,7 @@ class LibraryElement(PropertyGroup): class BIMProjectProperties(PropertyGroup): is_authoring: BoolProperty(name="Enable Authoring Mode", default=True) is_editing: BoolProperty(name="Is Editing", default=False) + is_loading: BoolProperty(name="Is Loading", default=False) mvd: StringProperty(name="MVD") author_name: StringProperty(name="Author") author_email: StringProperty(name="Author Email") @@ -51,6 +52,15 @@ class BIMProjectProperties(PropertyGroup): library_breadcrumb: CollectionProperty(name="Library Breadcrumb", type=StrProperty) library_elements: CollectionProperty(name="Library Elements", type=LibraryElement) active_library_element_index: IntProperty(name="Active Library Element Index") + collection_mode: bpy.props.EnumProperty( + items=[ + ("DECOMPOSITION", "Decomposition", "Collections represent aggregates and spatial containers"), + ("SPATIAL_DECOMPOSITION", "Spatial Decomposition", "Collections represent spatial containers"), + ("IFC_CLASS", "IFC Class", "Collections represention IFC class"), + ], + name="Collection Mode", + ) + def get_library_element_index(self, lib_element): return next((i for i in range(len(self.library_elements)) if self.library_elements[i] == lib_element)) diff --git a/src/blenderbim/blenderbim/bim/module/project/ui.py b/src/blenderbim/blenderbim/bim/module/project/ui.py index 30a89dc5f5..5764ef085a 100644 --- a/src/blenderbim/blenderbim/bim/module/project/ui.py +++ b/src/blenderbim/blenderbim/bim/module/project/ui.py @@ -32,12 +32,22 @@ class BIM_PT_project(Panel): self.layout.use_property_decorate = False self.layout.use_property_split = True props = context.scene.BIMProperties + pprops = context.scene.BIMProjectProperties self.file = IfcStore.get_file() - if self.file or props.ifc_file: + if pprops.is_loading: + self.draw_load_ui(context) + elif self.file or props.ifc_file: self.draw_project_ui(context) else: self.draw_create_project_ui(context) + def draw_load_ui(self, context): + pprops = context.scene.BIMProjectProperties + row = self.layout.row() + row.prop(pprops, "collection_mode") + row = self.layout.row() + row.operator("bim.load_project_elements").mode = "ALL" + def draw_project_ui(self, context): props = context.scene.BIMProperties pprops = context.scene.BIMProjectProperties @@ -109,8 +119,9 @@ class BIM_PT_project(Panel): row.prop(props, "area_unit", text="Area Unit") row = self.layout.row() row.prop(props, "volume_unit", text="Volume Unit") - row = self.layout.row() + row = self.layout.row(align=True) row.operator("bim.create_project") + row.operator("bim.load_project") class BIM_PT_project_library(Panel): diff --git a/src/blenderbim/test/bim/bootstrap.py b/src/blenderbim/test/bim/bootstrap.py index 902815b4f5..facd60a0b1 100644 --- a/src/blenderbim/test/bim/bootstrap.py +++ b/src/blenderbim/test/bim/bootstrap.py @@ -78,12 +78,32 @@ def additionally_the_object_name_is_selected(name): def i_set_prop_to_value(prop, value): + try: + eval(f"bpy.context.{prop}") + except: + assert False, "Property does not exist" try: exec(f'bpy.context.{prop} = "{value}"') except: exec(f"bpy.context.{prop} = {value}") +def prop_is_value(prop, value): + is_value = False + try: + exec(f'assert bpy.context.{prop} == "{value}"') + is_value = True + except: + try: + exec(f"assert bpy.context.{prop} == {value}") + is_value = True + except: + pass + if not is_value: + actual_value = eval(f"bpy.context.{prop}") + assert False, f"Value is {actual_value}" + + def i_enable_prop(prop): exec(f"bpy.context.{prop} = True") @@ -222,6 +242,7 @@ definitions = { 'the object "(.*)" is selected': the_object_name_is_selected, 'additionally the object "(.*)" is selected': additionally_the_object_name_is_selected, 'I set "(.*)" to "(.*)"': i_set_prop_to_value, + '"(.*)" is "(.*)"': prop_is_value, 'I enable "(.*)"': i_enable_prop, 'I press "(.*)"': i_press_operator, 'the object "(.*)" is an "(.*)"': the_object_name_is_an_ifc_class, diff --git a/src/blenderbim/test/bim/module/project/test_operator.py b/src/blenderbim/test/bim/module/project/test_operator.py index 5cc7b0bae4..db7cdfa226 100644 --- a/src/blenderbim/test/bim/module/project/test_operator.py +++ b/src/blenderbim/test/bim/module/project/test_operator.py @@ -34,3 +34,37 @@ class TestCreateProject(test.bim.bootstrap.NewFile): And the object "IfcBuilding/My Building" is in the collection "IfcBuilding/My Building" And the object "IfcBuildingStorey/My Storey" is in the collection "IfcBuildingStorey/My Storey" """ + + +class TestLoadProject(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_loading_a_project(self): + return """ + When I press "bim.load_project(filepath='{cwd}/test/files/basic.ifc')" + Then an IFC file exists + And "scene.BIMProjectProperties.is_loading" is "True" + """ + + +class TestLoadProjectElements(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_loading_all_project_elements(self): + return """ + Given I press "bim.load_project(filepath='{cwd}/test/files/basic.ifc')" + When I set "scene.BIMProjectProperties.collection_mode" to "DECOMPOSITION" + And I press "bim.load_project_elements(mode='ALL')" + Then the object "IfcProject/My Project" is an "IfcProject" + And the object "IfcSite/My Site" is an "IfcSite" + And the object "IfcBuilding/My Building" is an "IfcBuilding" + And the object "IfcBuildingStorey/Ground Floor" is an "IfcBuildingStorey" + And the object "IfcBuildingStorey/Level 1" is an "IfcBuildingStorey" + And the object "IfcSlab/Slab" is an "IfcSlab" + And the object "IfcWall/Wall" is an "IfcWall" + And the object "IfcSite/My Site" is in the collection "IfcSite/My Site" + And the object "IfcBuilding/My Building" is in the collection "IfcBuilding/My Building" + And the object "IfcBuildingStorey/Ground Floor" is in the collection "IfcBuildingStorey/Ground Floor" + And the object "IfcBuildingStorey/Level 1" is in the collection "IfcBuildingStorey/Level 1" + And the object "IfcSlab/Slab" is in the collection "IfcBuildingStorey/Ground Floor" + And the object "IfcWall/Wall" is in the collection "IfcBuildingStorey/Level 1" + And "scene.BIMProjectProperties.is_loading" is "False" + """ diff --git a/src/blenderbim/test/files/basic.ifc b/src/blenderbim/test/files/basic.ifc new file mode 100644 index 0000000000..f239509bdf --- /dev/null +++ b/src/blenderbim/test/files/basic.ifc @@ -0,0 +1,126 @@ +ISO-10303-21; +HEADER; +FILE_DESCRIPTION(('ViewDefinition[DesignTransferView]'),'2;1'); +FILE_NAME('basic.ifc','2021-09-11T19:44:13+10:00',(),(),'IfcOpenShell 0.6.0b0','BlenderBIM 0.0.999999','Nobody'); +FILE_SCHEMA(('IFC4')); +ENDSEC; +DATA; +#1=IFCPERSON('HSeldon','Seldon','Hari',$,$,$,$,$); +#2=IFCORGANIZATION('APTR','Aperture Science',$,$,$); +#3=IFCACTORROLE(.USERDEFINED.,'CONTRIBUTOR',$); +#4=IFCTELECOMADDRESS(.USERDEFINED.,'The main webpage of the software collection.','WEBPAGE',$,$,$,$,'https://ifcopenshell.org',$); +#5=IFCTELECOMADDRESS(.USERDEFINED.,'The BlenderBIM Add-on webpage of the software collection.','WEBPAGE',$,$,$,$,'https://blenderbim.org',$); +#6=IFCTELECOMADDRESS(.USERDEFINED.,'The source code repository of the software collection.','REPOSITORY',$,$,$,$,'https://github.com/IfcOpenShell/IfcOpenShell.git',$); +#7=IFCORGANIZATION($,'IfcOpenShell','IfcOpenShell is an open source (LGPL) software library that helps users and software developers to work with the IFC file format.',(#3),(#4,#5,#6)); +#8=IFCAPPLICATION(#7,'0.0.999999','BlenderBIM Add-on','BlenderBIM'); +#9=IFCPERSONANDORGANIZATION(#1,#2,$); +#10=IFCOWNERHISTORY(#9,#8,.READWRITE.,.ADDED.,1631353388,#9,#8,1631353388); +#11=IFCPROJECT('2xwg5dkcT4T8MlEIX1jLjD',#10,'My Project',$,$,$,$,(#20,#27),#15); +#12=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.); +#13=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.); +#14=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.); +#15=IFCUNITASSIGNMENT((#14,#12,#13)); +#16=IFCCARTESIANPOINT((0.,0.,0.)); +#17=IFCDIRECTION((0.,0.,1.)); +#18=IFCDIRECTION((1.,0.,0.)); +#19=IFCAXIS2PLACEMENT3D(#16,#17,#18); +#20=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#19,$); +#21=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#20,$,.MODEL_VIEW.,$); +#22=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Box','Model',*,*,*,*,#20,$,.MODEL_VIEW.,$); +#23=IFCCARTESIANPOINT((0.,0.,0.)); +#24=IFCDIRECTION((0.,0.,1.)); +#25=IFCDIRECTION((1.,0.,0.)); +#26=IFCAXIS2PLACEMENT3D(#23,#24,#25); +#27=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Plan',2,1.E-05,#26,$); +#28=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Plan',*,*,*,*,#27,$,.PLAN_VIEW.,$); +#29=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1631353388,#9,#8,1631353388); +#30=IFCSITE('3cz3qtLYbCURHSIUoySdUS',#29,'My Site',$,$,#56,$,$,$,$,$,$,$,$); +#36=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1631353388,#9,#8,1631353388); +#37=IFCBUILDING('2dErbdcOP6Nh7ym5xn_$Ec',#36,'My Building',$,$,#63,$,$,$,$,$,$); +#43=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1631353388,#9,#8,1631353388); +#44=IFCBUILDINGSTOREY('0MbU9rGEH1LAWUFmJ3gCcg',#43,'Ground Floor',$,$,#70,$,$,$,$); +#50=IFCOWNERHISTORY(#9,#8,.READWRITE.,.ADDED.,1631353388,#9,#8,1631353388); +#51=IFCRELAGGREGATES('1_TFH$83TDGOqH3GdA9HdZ',#50,$,$,#11,(#30)); +#52=IFCCARTESIANPOINT((0.,0.,0.)); +#53=IFCDIRECTION((0.,0.,1.)); +#54=IFCDIRECTION((1.,0.,0.)); +#55=IFCAXIS2PLACEMENT3D(#52,#53,#54); +#56=IFCLOCALPLACEMENT($,#55); +#57=IFCOWNERHISTORY(#9,#8,.READWRITE.,.ADDED.,1631353388,#9,#8,1631353388); +#58=IFCRELAGGREGATES('0rW4qoplT3zBMjr14wc1ok',#57,$,$,#30,(#37)); +#59=IFCCARTESIANPOINT((0.,0.,0.)); +#60=IFCDIRECTION((0.,0.,1.)); +#61=IFCDIRECTION((1.,0.,0.)); +#62=IFCAXIS2PLACEMENT3D(#59,#60,#61); +#63=IFCLOCALPLACEMENT(#56,#62); +#64=IFCOWNERHISTORY(#9,#8,.READWRITE.,.ADDED.,1631353388,#9,#8,1631353388); +#65=IFCRELAGGREGATES('041C6Y06v8$AmAQhXWgREJ',#64,$,$,#37,(#44,#71)); +#66=IFCCARTESIANPOINT((0.,0.,0.)); +#67=IFCDIRECTION((0.,0.,1.)); +#68=IFCDIRECTION((1.,0.,0.)); +#69=IFCAXIS2PLACEMENT3D(#66,#67,#68); +#70=IFCLOCALPLACEMENT(#63,#69); +#71=IFCBUILDINGSTOREY('1Pkxs$2EDD3ApZ07AQn5om',#77,'Level 1',$,$,#152,$,$,$,$); +#72=IFCCARTESIANPOINT((0.,0.,0.)); +#73=IFCDIRECTION((0.,0.,1.)); +#74=IFCDIRECTION((1.,0.,0.)); +#75=IFCAXIS2PLACEMENT3D(#72,#73,#74); +#76=IFCLOCALPLACEMENT(#63,#75); +#77=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1631353453,#9,#8,1631353388); +#78=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1631353439,#9,#8,1631353439); +#79=IFCSLAB('20Njb8mHv8v9ESSk_1YM2m',#78,'Slab',$,$,#114,#97,$,.BASESLAB.); +#85=IFCINDEXEDPOLYGONALFACE((1,5,7,3)); +#86=IFCINDEXEDPOLYGONALFACE((4,3,7,8)); +#87=IFCINDEXEDPOLYGONALFACE((8,7,5,6)); +#88=IFCINDEXEDPOLYGONALFACE((6,2,4,8)); +#89=IFCINDEXEDPOLYGONALFACE((2,1,3,4)); +#90=IFCINDEXEDPOLYGONALFACE((6,5,1,2)); +#91=IFCCARTESIANPOINTLIST3D(((1.,1.,1.),(1.,1.,-1.),(1.,-1.,1.),(1.,-1.,-1.),(-1.,1.,1.),(-1.,1.,-1.),(-1.,-1.,1.),(-1.,-1.,-1.))); +#92=IFCPOLYGONALFACESET(#91,$,(#85,#86,#87,#88,#89,#90),$); +#93=IFCSHAPEREPRESENTATION(#21,'Body','Tessellation',(#92)); +#94=IFCCARTESIANPOINT((-1.,-1.,-1.)); +#95=IFCBOUNDINGBOX(#94,2.,2.,2.); +#96=IFCSHAPEREPRESENTATION(#22,'Box','BoundingBox',(#95)); +#97=IFCPRODUCTDEFINITIONSHAPE($,$,(#96,#93)); +#98=IFCCOLOURRGB($,0.800000011920929,0.800000011920929,0.800000011920929); +#99=IFCCOLOURRGB($,0.800000011920929,0.800000011920929,0.800000011920929); +#100=IFCSURFACESTYLERENDERING(#98,0.,#99,$,$,$,$,$,.NOTDEFINED.); +#101=IFCSURFACESTYLE('Material',.BOTH.,(#100)); +#102=IFCSTYLEDITEM(#92,(#101),'Material'); +#103=IFCOWNERHISTORY(#9,#8,.READWRITE.,.ADDED.,1631353439,#9,#8,1631353439); +#104=IFCRELCONTAINEDINSPATIALSTRUCTURE('2T4j5EAcn0qQ9vTOZFK9HO',#103,$,$,(#79),#44); +#110=IFCCARTESIANPOINT((0.,0.,0.)); +#111=IFCDIRECTION((0.,0.,1.)); +#112=IFCDIRECTION((1.,0.,0.)); +#113=IFCAXIS2PLACEMENT3D(#110,#111,#112); +#114=IFCLOCALPLACEMENT(#70,#113); +#115=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1631353453,#9,#8,1631353443); +#116=IFCWALL('27uW0y7Gr72R8yo2C4vBlV',#115,'Wall',$,$,#157,#134,$,.ELEMENTEDWALL.); +#122=IFCINDEXEDPOLYGONALFACE((1,5,7,3)); +#123=IFCINDEXEDPOLYGONALFACE((4,3,7,8)); +#124=IFCINDEXEDPOLYGONALFACE((8,7,5,6)); +#125=IFCINDEXEDPOLYGONALFACE((6,2,4,8)); +#126=IFCINDEXEDPOLYGONALFACE((2,1,3,4)); +#127=IFCINDEXEDPOLYGONALFACE((6,5,1,2)); +#128=IFCCARTESIANPOINTLIST3D(((1.,1.,1.),(1.,1.,-1.),(1.,-1.,1.),(1.,-1.,-1.),(-1.,1.,1.),(-1.,1.,-1.),(-1.,-1.,1.),(-1.,-1.,-1.))); +#129=IFCPOLYGONALFACESET(#128,$,(#122,#123,#124,#125,#126,#127),$); +#130=IFCSHAPEREPRESENTATION(#21,'Body','Tessellation',(#129)); +#131=IFCCARTESIANPOINT((-1.,-1.,-1.)); +#132=IFCBOUNDINGBOX(#131,2.,2.,2.); +#133=IFCSHAPEREPRESENTATION(#22,'Box','BoundingBox',(#132)); +#134=IFCPRODUCTDEFINITIONSHAPE($,$,(#133,#130)); +#135=IFCSTYLEDITEM(#129,(#101),'Material'); +#136=IFCOWNERHISTORY(#9,#8,.READWRITE.,.ADDED.,1631353443,#9,#8,1631353443); +#137=IFCRELCONTAINEDINSPATIALSTRUCTURE('1CgivZt6z1l8IgKaTkZMcT',#136,$,$,(#116),#71); +#148=IFCCARTESIANPOINT((0.,0.,3.)); +#149=IFCDIRECTION((0.,0.,1.)); +#150=IFCDIRECTION((1.,0.,0.)); +#151=IFCAXIS2PLACEMENT3D(#148,#149,#150); +#152=IFCLOCALPLACEMENT(#63,#151); +#153=IFCCARTESIANPOINT((0.,0.,-3.)); +#154=IFCDIRECTION((0.,0.,1.)); +#155=IFCDIRECTION((1.,0.,0.)); +#156=IFCAXIS2PLACEMENT3D(#153,#154,#155); +#157=IFCLOCALPLACEMENT(#152,#156); +ENDSEC; +END-ISO-10303-21;