From 6284b3960d335550f44610bcac7763b4fffbc0c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Trzci=C5=84ski?= Date: Tue, 16 Jul 2019 18:30:03 +0100 Subject: [PATCH 1/6] Introduce defaultdict instead of checks --- .../io_import_scene_ifc/__init__.py | 70 +++++++++---------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/src/ifcblender/io_import_scene_ifc/__init__.py b/src/ifcblender/io_import_scene_ifc/__init__.py index 2375d7bc6b..08affa7d3b 100644 --- a/src/ifcblender/io_import_scene_ifc/__init__.py +++ b/src/ifcblender/io_import_scene_ifc/__init__.py @@ -43,6 +43,7 @@ if "bpy" in locals(): import bpy import mathutils +from collections import defaultdict from bpy.props import StringProperty, IntProperty, BoolProperty from bpy_extras.io_utils import ImportHelper @@ -74,7 +75,7 @@ def import_ifc(filename, use_names, process_relations, blender_booleans): if not valid_file: return False print("Done reading file") - id_to_object = {} + id_to_object = defaultdict(list) id_to_parent = {} id_to_matrix = {} openings = [] @@ -82,6 +83,7 @@ def import_ifc(filename, use_names, process_relations, blender_booleans): print("Creating geometry...") collection = bpy.data.collections.new(f"{bpy.path.basename(filename)}") bpy.context.scene.collection.children.link(collection) + if process_relations: rel_collection = bpy.data.collections.new("Relations") collection.children.link(rel_collection) @@ -178,8 +180,6 @@ def import_ifc(filename, use_names, process_relations, blender_booleans): bob.hide_viewport = bob.hide_render = True bob.display_type = 'WIRE' - if ob.id not in id_to_object: - id_to_object[ob.id] = [] id_to_object[ob.id].append(bob) if ob.parent_id > 0: @@ -201,45 +201,43 @@ def import_ifc(filename, use_names, process_relations, blender_booleans): if process_relations: print("Processing relations...") + while len(id_to_parent_temp): + id, parent_id = id_to_parent_temp.popitem() - while len(id_to_parent_temp) and process_relations: - id, parent_id = id_to_parent_temp.popitem() - - if parent_id in id_to_object: - bob = id_to_object[parent_id][0] - else: - parent_ob = iterator.getObject(parent_id) - if parent_ob.id == -1: - bob = None + if parent_id in id_to_object: + bob = id_to_object[parent_id][0] else: - m = parent_ob.transformation.matrix.data - nm = parent_ob.name if len(parent_ob.name) and use_names \ - else parent_ob.guid - bob = bpy.data.objects.new(nm, None) + parent_ob = iterator.getObject(parent_id) + if parent_ob.id == -1: + bob = None + else: + m = parent_ob.transformation.matrix.data + nm = parent_ob.name if len(parent_ob.name) and use_names \ + else parent_ob.guid + bob = bpy.data.objects.new(nm, None) - mat = mathutils.Matrix(( - [m[0], m[1], m[2], 0], - [m[3], m[4], m[5], 0], - [m[6], m[7], m[8], 0], - [m[9], m[10], m[11], 1])) - if transpose_matrices: - mat.transpose() - id_to_matrix[parent_ob.id] = mat + mat = mathutils.Matrix(( + [m[0], m[1], m[2], 0], + [m[3], m[4], m[5], 0], + [m[6], m[7], m[8], 0], + [m[9], m[10], m[11], 1])) + if transpose_matrices: + mat.transpose() + id_to_matrix[parent_ob.id] = mat - rel_collection.objects.link(bob) + rel_collection.objects.link(bob) - bob.ifc_id = parent_ob.id - bob.ifc_name, bob.ifc_type, bob.ifc_guid = \ - parent_ob.name, parent_ob.type, parent_ob.guid + bob.ifc_id = parent_ob.id + bob.ifc_name, bob.ifc_type, bob.ifc_guid = \ + parent_ob.name, parent_ob.type, parent_ob.guid - if parent_ob.parent_id > 0: - id_to_parent[parent_id] = parent_ob.parent_id - id_to_parent_temp[parent_id] = parent_ob.parent_id - if parent_id not in id_to_object: id_to_object[parent_id] = [] - id_to_object[parent_id].append(bob) - if bob: - for ob in id_to_object[id]: - ob.parent = bob + if parent_ob.parent_id > 0: + id_to_parent[parent_id] = parent_ob.parent_id + id_to_parent_temp[parent_id] = parent_ob.parent_id + id_to_object[parent_id].append(bob) + if bob: + for ob in id_to_object[id]: + ob.parent = bob id_to_matrix_temp = dict(id_to_matrix) From 3f3b548d7483d0d9d2dd1b8aa3d6fbdb6e02b441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Trzci=C5=84ski?= Date: Tue, 16 Jul 2019 18:33:54 +0100 Subject: [PATCH 2/6] Fix filename -> ifcopenshell.file --- src/ifcblender/io_import_scene_ifc/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ifcblender/io_import_scene_ifc/__init__.py b/src/ifcblender/io_import_scene_ifc/__init__.py index 08affa7d3b..f45c8e9817 100644 --- a/src/ifcblender/io_import_scene_ifc/__init__.py +++ b/src/ifcblender/io_import_scene_ifc/__init__.py @@ -70,7 +70,8 @@ def import_ifc(filename, use_names, process_relations, blender_booleans): print(f"Reading {bpy.path.basename(filename)}...") settings = ifcopenshell_geom.settings() settings.set(settings.DISABLE_OPENING_SUBTRACTIONS, blender_booleans) - iterator = ifcopenshell_geom.iterator(settings, filename) + ifc_file = ifcopenshell.file(filename) + iterator = ifcopenshell_geom.iterator(settings, ifc_file) valid_file = iterator.initialize() if not valid_file: return False From e450101a12b9f2a3dd596130ca2251161faf3c81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Trzci=C5=84ski?= Date: Tue, 16 Jul 2019 19:31:16 +0100 Subject: [PATCH 3/6] Add support for collections --- .../io_import_scene_ifc/__init__.py | 75 +++++++++++++++++-- 1 file changed, 67 insertions(+), 8 deletions(-) diff --git a/src/ifcblender/io_import_scene_ifc/__init__.py b/src/ifcblender/io_import_scene_ifc/__init__.py index f45c8e9817..596aad1b4d 100644 --- a/src/ifcblender/io_import_scene_ifc/__init__.py +++ b/src/ifcblender/io_import_scene_ifc/__init__.py @@ -41,11 +41,17 @@ if "bpy" in locals(): if "ifcopenshell" in locals(): importlib.reload(ifcopenshell) -import bpy -import mathutils -from collections import defaultdict -from bpy.props import StringProperty, IntProperty, BoolProperty +from bpy.props import ( + BoolProperty, + IntProperty, + StringProperty, +) from bpy_extras.io_utils import ImportHelper +from collections import defaultdict +import bpy +import logging +import mathutils +import os major, minor = bpy.app.version[0:2] transpose_matrices = minor >= 62 @@ -63,6 +69,25 @@ bpy.types.Object.ifc_type = StringProperty( name="IFC Entity Type", description="The STEP Datatype keyword") +def _get_parent(instance): + """This is based on ifcopenshell.app.geom""" + if instance.is_a("IfcOpeningElement"): + # We skip opening elements as they are nameless. + # We use this function to get usable collections. + return _get_parent(instance.VoidsElements[0].RelatingBuildingElement) + if instance.is_a("IfcElement"): + fills = instance.FillsVoids + if len(fills): + return fills[0].RelatingOpeningElement + containments = instance.ContainedInStructure + if len(containments): + return containments[0].RelatingStructure + if instance.is_a("IfcObjectDefinition"): + decompositions = instance.Decomposes + if len(decompositions): + return decompositions[0].RelatingObject + + def import_ifc(filename, use_names, process_relations, blender_booleans): from . import ifcopenshell @@ -70,7 +95,8 @@ def import_ifc(filename, use_names, process_relations, blender_booleans): print(f"Reading {bpy.path.basename(filename)}...") settings = ifcopenshell_geom.settings() settings.set(settings.DISABLE_OPENING_SUBTRACTIONS, blender_booleans) - ifc_file = ifcopenshell.file(filename) + assert os.path.exists(filename), filename + ifc_file = ifcopenshell.open(filename) iterator = ifcopenshell_geom.iterator(settings, ifc_file) valid_file = iterator.initialize() if not valid_file: @@ -82,8 +108,40 @@ def import_ifc(filename, use_names, process_relations, blender_booleans): openings = [] old_progress = -1 print("Creating geometry...") - collection = bpy.data.collections.new(f"{bpy.path.basename(filename)}") - bpy.context.scene.collection.children.link(collection) + root_collection = bpy.data.collections.new(f"{bpy.path.basename(filename)}") + bpy.context.scene.collection.children.link(root_collection) + + collections = { + 0: root_collection + } + def get_collection(cid): + if cid == 0: + return root_collection + + collection = collections.get(cid) + if collection is None: + try: + ifc_object = ifc_file.by_id(cid) + except Exception as exc: + logging.exception(exc) + ifc_object = None + + if ifc_object is not None: + # FIXME: I am really unsure if that is correct way to get parent object + ifc_parent_object = _get_parent(ifc_object) + parent_id = ifc_parent_object.id() if ifc_parent_object is not None else 0 + parent_collection = get_collection(parent_id) + name = ifc_object.Name or f'{ifc_object.is_a()}[{cid}]' + else: + parent_collection = get_collection(0) + name = f'unresolved_{cid}' + + collection = bpy.data.collections.new(name) + parent_collection.children.link(collection) + collections[cid] = collection + + + return collection if process_relations: rel_collection = bpy.data.collections.new("Relations") @@ -166,7 +224,8 @@ def import_ifc(filename, use_names, process_relations, blender_booleans): id_to_matrix[ob.id] = mat else: bob.matrix_world = mat - collection.objects.link(bob) + + get_collection(ob.parent_id).objects.link(bob) bpy.context.view_layer.objects.active = bob bpy.ops.object.mode_set(mode='EDIT') From 98f4c8518ecb35350f70ae753bb136821e533065 Mon Sep 17 00:00:00 2001 From: gpioto Date: Mon, 26 Aug 2019 14:12:09 -0300 Subject: [PATCH 4/6] Casting default timestamp template to integer The documentation of IFC indicates that the timestamp must be of type Integer. (https://standards.buildingsmart.org/IFC/DEV/IFC4_2/FINAL/HTML/link/ifctimestamp.htm) If not, it cause some problems importing the generated IFC file in some platforms --- src/ifcopenshell-python/ifcopenshell/template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ifcopenshell-python/ifcopenshell/template.py b/src/ifcopenshell-python/ifcopenshell/template.py index fdc34aec07..ec253ad414 100644 --- a/src/ifcopenshell-python/ifcopenshell/template.py +++ b/src/ifcopenshell-python/ifcopenshell/template.py @@ -66,7 +66,7 @@ DEFAULTS = { "application_version": lambda d: main.version, "project_globalid": lambda d: compress(uuid.uuid4().hex), "schema_identifier": lambda d: main.schema_identifier, - "timestamp": lambda d: time.time(), + "timestamp": lambda d: int(time.time()), "timestring": lambda d: time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(d.get('timestamp') or time.time())) } From 7c99294ba815f58176a4858e60defefa68fee0a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Trzci=C5=84ski?= Date: Tue, 3 Sep 2019 17:57:39 +0100 Subject: [PATCH 5/6] Fix clash of mesh names (fixes #661) --- src/ifcblender/io_import_scene_ifc/__init__.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ifcblender/io_import_scene_ifc/__init__.py b/src/ifcblender/io_import_scene_ifc/__init__.py index 596aad1b4d..5310e829a5 100644 --- a/src/ifcblender/io_import_scene_ifc/__init__.py +++ b/src/ifcblender/io_import_scene_ifc/__init__.py @@ -146,6 +146,9 @@ def import_ifc(filename, use_names, process_relations, blender_booleans): if process_relations: rel_collection = bpy.data.collections.new("Relations") collection.children.link(rel_collection) + + project_meshes = dict() + while True: ob = iterator.get() @@ -159,9 +162,9 @@ def import_ifc(filename, use_names, process_relations, blender_booleans): # MESH CREATION # Depending on version, geometry.id will be either int or str mesh_name = 'mesh-%r' % ob.geometry.id - if mesh_name in bpy.data.meshes: - me = bpy.data.meshes[mesh_name] - else: + + me = project_meshes.get(mesh_name) + if me is None: verts = [[v[i], v[i + 1], v[i + 2]] for i in range(0, len(v), 3)] faces = [[f[i], f[i + 1], f[i + 2]] From 42307972a8330451c86b95279a68d76bf9385038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Trzci=C5=84ski?= Date: Wed, 4 Sep 2019 21:02:25 +0100 Subject: [PATCH 6/6] Fix reusing meshes and mesh naming --- src/ifcblender/io_import_scene_ifc/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ifcblender/io_import_scene_ifc/__init__.py b/src/ifcblender/io_import_scene_ifc/__init__.py index 5310e829a5..8336dab850 100644 --- a/src/ifcblender/io_import_scene_ifc/__init__.py +++ b/src/ifcblender/io_import_scene_ifc/__init__.py @@ -140,7 +140,6 @@ def import_ifc(filename, use_names, process_relations, blender_booleans): parent_collection.children.link(collection) collections[cid] = collection - return collection if process_relations: @@ -161,7 +160,7 @@ def import_ifc(filename, use_names, process_relations, blender_booleans): nm = ob.name if len(ob.name) and use_names else ob.guid # MESH CREATION # Depending on version, geometry.id will be either int or str - mesh_name = 'mesh-%r' % ob.geometry.id + mesh_name = f'mesh-{ob.geometry.id}' me = project_meshes.get(mesh_name) if me is None: @@ -171,6 +170,8 @@ def import_ifc(filename, use_names, process_relations, blender_booleans): for i in range(0, len(f), 3)] me = bpy.data.meshes.new(mesh_name) + project_meshes[mesh_name] = me + me.from_pydata(verts, [], faces) me.validate() # MATERIAL CREATION