From 0d243beb365a40aad92b0a2c8a803b3dc3820936 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 17 Nov 2019 19:08:29 +1100 Subject: [PATCH 1/6] Implement export for objects with non-uniform scale --- src/ifcblenderexport/blenderbim/export_ifc.py | 55 +++++++++++++++++-- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/export_ifc.py b/src/ifcblenderexport/blenderbim/export_ifc.py index 18ab837e19..ef12530f3b 100644 --- a/src/ifcblenderexport/blenderbim/export_ifc.py +++ b/src/ifcblenderexport/blenderbim/export_ifc.py @@ -254,6 +254,9 @@ class IfcParser(): 'location': object.matrix_world.translation, 'up_axis': object.matrix_world.to_quaternion() @ Vector((0, 0, 1)), 'forward_axis': object.matrix_world.to_quaternion() @ Vector((1, 0, 0)), + 'right_axis': object.matrix_world.to_quaternion() @ Vector((0, 1, 0)), + 'has_scale': object.matrix_world.to_scale() != Vector((1, 1, 1)), + 'scale': object.matrix_world.to_scale(), 'class': self.get_ifc_class(object.name), 'relating_structure': None, 'relating_host': None, @@ -1237,10 +1240,13 @@ class IfcExporter(): else: placement_rel_to = None - placement = self.file.createIfcLocalPlacement(placement_rel_to, - self.create_ifc_axis_2_placement_3d(product['location'], - product['up_axis'], - product['forward_axis'])) + if product['has_scale']: + placement = self.file.createIfcLocalPlacement(placement_rel_to, self.origin) + else: + placement = self.file.createIfcLocalPlacement(placement_rel_to, + self.create_ifc_axis_2_placement_3d(product['location'], + product['up_axis'], + product['forward_axis'])) product['attributes'].update({ 'OwnerHistory': self.owner_history, # TODO: unhardcode @@ -1271,11 +1277,50 @@ class IfcExporter(): def get_product_shape(self, product): try: shape = self.file.createIfcProductDefinitionShape(None, None, - [self.ifc_parser.representations[p]['ifc'] for p in product['representations']]) + self.get_product_shape_representations(product)) except: shape = None return shape + def get_product_shape_representations(self, product): + results = [] + for representation_name in product['representations']: + shape_representation = self.ifc_parser.representations[representation_name]['ifc'] + if product['has_scale']: + results.append(self.get_product_mapped_geometry(product, shape_representation)) + else: + results.append(shape_representation) + return results + + def get_product_mapped_geometry(self, product, shape_representation): + mapping_source = self.file.createIfcRepresentationMap(self.origin, shape_representation) + mapping_target = self.file.createIfcCartesianTransformationOperator3DnonUniform( + self.file.createIfcDirection(( + product['forward_axis'].x, + product['forward_axis'].y, + product['forward_axis'].z)), + self.file.createIfcDirection(( + product['right_axis'].x, + product['right_axis'].y, + product['right_axis'].z)), + self.create_cartesian_point( + product['location'].x, + product['location'].y, + product['location'].z), + product['scale'].x, + self.file.createIfcDirection(( + product['up_axis'].x, + product['up_axis'].y, + product['up_axis'].z)), + product['scale'].y, + product['scale'].z) + mapped_item = self.file.createIfcMappedItem(mapping_source, mapping_target) + return self.file.createIfcShapeRepresentation( + shape_representation.ContextOfItems, + shape_representation.RepresentationIdentifier, + shape_representation.RepresentationType, + [mapped_item]) + def calculate_quantities(self, qto_name, object): quantities = [] for index, vg in enumerate(object.vertex_groups): From 448a56afe91d00ede6c08d752ececdfce2f826a9 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 17 Nov 2019 20:41:53 +1100 Subject: [PATCH 2/6] Minor scale fix and code cleanup --- src/ifcblenderexport/blenderbim/export_ifc.py | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/export_ifc.py b/src/ifcblenderexport/blenderbim/export_ifc.py index ef12530f3b..573ca1c6b0 100644 --- a/src/ifcblenderexport/blenderbim/export_ifc.py +++ b/src/ifcblenderexport/blenderbim/export_ifc.py @@ -255,8 +255,8 @@ class IfcParser(): 'up_axis': object.matrix_world.to_quaternion() @ Vector((0, 0, 1)), 'forward_axis': object.matrix_world.to_quaternion() @ Vector((1, 0, 0)), 'right_axis': object.matrix_world.to_quaternion() @ Vector((0, 1, 0)), - 'has_scale': object.matrix_world.to_scale() != Vector((1, 1, 1)), - 'scale': object.matrix_world.to_scale(), + 'has_scale': object.scale != Vector((1, 1, 1)), + 'scale': object.scale, 'class': self.get_ifc_class(object.name), 'relating_structure': None, 'relating_host': None, @@ -1295,23 +1295,14 @@ class IfcExporter(): def get_product_mapped_geometry(self, product, shape_representation): mapping_source = self.file.createIfcRepresentationMap(self.origin, shape_representation) mapping_target = self.file.createIfcCartesianTransformationOperator3DnonUniform( - self.file.createIfcDirection(( - product['forward_axis'].x, - product['forward_axis'].y, - product['forward_axis'].z)), - self.file.createIfcDirection(( - product['right_axis'].x, - product['right_axis'].y, - product['right_axis'].z)), + self.create_direction(product['forward_axis']), + self.create_direction(product['right_axis']), self.create_cartesian_point( product['location'].x, product['location'].y, product['location'].z), product['scale'].x, - self.file.createIfcDirection(( - product['up_axis'].x, - product['up_axis'].y, - product['up_axis'].z)), + self.create_direction(product['up_axis']), product['scale'].y, product['scale'].z) mapped_item = self.file.createIfcMappedItem(mapping_source, mapping_target) @@ -1639,6 +1630,9 @@ class IfcExporter(): z = self.convert_si_to_unit(z) return self.file.createIfcCartesianPoint((x, y, z)) + def create_direction(self, vector): + return self.file.createIfcDirection((vector.x, vector.y, vector.z)) + def relate_elements_to_spatial_structures(self): for relating_structure, related_elements in self.ifc_parser.rel_contained_in_spatial_structure.items(): self.file.createIfcRelContainedInSpatialStructure( From 5237f4a634bab05eb762d20edef3684daadf6a21 Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Sun, 17 Nov 2019 14:07:32 +0100 Subject: [PATCH 3/6] Add warning related to rep usage. See #724 --- src/ifcgeom/IfcGeomFunctions.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ifcgeom/IfcGeomFunctions.cpp b/src/ifcgeom/IfcGeomFunctions.cpp index 88561dd8ec..09d1306dae 100644 --- a/src/ifcgeom/IfcGeomFunctions.cpp +++ b/src/ifcgeom/IfcGeomFunctions.cpp @@ -1959,6 +1959,11 @@ IfcSchema::IfcProduct::list::ptr IfcGeom::Kernel::products_represented_by(const } IfcSchema::IfcRepresentationMap::list::ptr maps = representation->RepresentationMap(); + + if (products->size() && maps->size()) { + Logger::Warning("Representation used by IfcRepresentationMap and IfcProductDefinitionShape", representation); + } + if (maps->size() == 1) { IfcSchema::IfcRepresentationMap* map = *maps->begin(); if (is_identity_transform(map->MappingOrigin())) { From c46167073b625ed4272d7339c188082ddc80aa7c Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 18 Nov 2019 10:18:21 +1100 Subject: [PATCH 4/6] Specifying an IFC diff now does an incremental import --- src/ifcblenderexport/blenderbim/import_ifc.py | 26 +++++++++++++++++++ src/ifcblenderexport/blenderbim/operator.py | 1 + 2 files changed, 27 insertions(+) diff --git a/src/ifcblenderexport/blenderbim/import_ifc.py b/src/ifcblenderexport/blenderbim/import_ifc.py index c4b7895d0a..725f241b35 100644 --- a/src/ifcblenderexport/blenderbim/import_ifc.py +++ b/src/ifcblenderexport/blenderbim/import_ifc.py @@ -68,6 +68,7 @@ class MaterialCreator(): class IfcImporter(): def __init__(self, ifc_import_settings): self.ifc_import_settings = ifc_import_settings + self.diff = None self.file = None self.settings = ifcopenshell.geom.settings() if self.ifc_import_settings.should_import_curves: @@ -83,14 +84,22 @@ class IfcImporter(): self.material_creator = MaterialCreator() def execute(self): + self.load_diff() self.load_file() self.calculate_unit_scale() self.create_project() self.create_spatial_hierarchy() + self.purge_diff() elements = self.file.by_type('IfcElement') + self.file.by_type('IfcSpace') for element in elements: self.create_object(element) + 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): print('loading file {}'.format(self.ifc_import_settings.input_file)) self.file = ifcopenshell.open(self.ifc_import_settings.input_file) @@ -140,7 +149,23 @@ class IfcImporter(): def get_name(self, element): return '{}/{}'.format(element.is_a(), element.Name) + def purge_diff(self): + 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 create_object(self, element): + if self.diff: + if element.GlobalId not in self.diff['added'] \ + and element.GlobalId not in self.diff['changed'].keys(): + return + print('Creating object {}'.format(element)) self.time = time.time() if element.is_a('IfcOpeningElement'): @@ -305,3 +330,4 @@ class IfcImportSettings: self.input_file = None self.should_ignore_site_coordinates = False self.should_import_curves = False + self.diff_file = None diff --git a/src/ifcblenderexport/blenderbim/operator.py b/src/ifcblenderexport/blenderbim/operator.py index 56ad699411..e79dc821b4 100644 --- a/src/ifcblenderexport/blenderbim/operator.py +++ b/src/ifcblenderexport/blenderbim/operator.py @@ -59,6 +59,7 @@ class ImportIFC(bpy.types.Operator, ImportHelper): ifc_import_settings.logger = logging.getLogger('ImportIFC') ifc_import_settings.logger.info('Starting import') ifc_import_settings.input_file = self.filepath + ifc_import_settings.diff_file = bpy.context.scene.BIMProperties.diff_json_file ifc_import_settings.should_ignore_site_coordinates = bpy.context.scene.BIMProperties.import_should_ignore_site_coordinates ifc_import_settings.should_import_curves = bpy.context.scene.BIMProperties.import_should_import_curves ifc_importer = import_ifc.IfcImporter(ifc_import_settings) From 1e802bd0a3e1255beccb7755af8750c8be18cce8 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 18 Nov 2019 10:48:50 +1100 Subject: [PATCH 5/6] Bugfix for BIMTester purge function to overwrite existing feature files --- src/ifcbimtester/bimtester.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/ifcbimtester/bimtester.py b/src/ifcbimtester/bimtester.py index d7e7ea7dc3..52f5e72d11 100644 --- a/src/ifcbimtester/bimtester.py +++ b/src/ifcbimtester/bimtester.py @@ -54,19 +54,22 @@ class TestPurger: def purge(self): for filename in Path('features/').glob('*.feature'): with open(filename, 'r') as feature_file: - with open('{}.purged'.format(filename), 'w') as new_file: - for line in feature_file: - if 'Given the IFC file ' in line: - filename = line.split('"')[1] - print('Loading file {} ...'.format(filename)) - self.file = ifcopenshell.open(filename) - if line.strip()[0:4] == 'Then': - words = line.strip().split() - for word in words: - if self.is_a_global_id(word): - if not self.does_global_id_exist(word): - print('Test for {} purged ...'.format(word)) - continue + old_file = feature_file.readlines() + with open(filename, 'w') as new_file: + for line in old_file: + is_purged = False + if 'Given the IFC file ' in line: + filename = line.split('"')[1] + print('Loading file {} ...'.format(filename)) + self.file = ifcopenshell.open(filename) + if line.strip()[0:4] == 'Then': + words = line.strip().split() + for word in words: + if self.is_a_global_id(word): + if not self.does_global_id_exist(word): + print('Test for {} purged ...'.format(word)) + is_purged = True + if not is_purged: new_file.write(line) def is_a_global_id(self, word): From c930b8f1558d06b99dbbffe19ddf98049e241eff Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 18 Nov 2019 11:30:08 +1100 Subject: [PATCH 6/6] Optimise select audited operator --- src/ifcblenderexport/blenderbim/operator.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/operator.py b/src/ifcblenderexport/blenderbim/operator.py index e79dc821b4..616ed5e0ea 100644 --- a/src/ifcblenderexport/blenderbim/operator.py +++ b/src/ifcblenderexport/blenderbim/operator.py @@ -225,13 +225,15 @@ class SelectAudited(bpy.types.Operator): bl_label = 'Select Audited' def execute(self, context): + audited_global_ids = [] with open(bpy.context.scene.BIMProperties.data_dir + 'audit.txt') as file: for line in file: - for object in bpy.context.visible_objects: - index = object.BIMObjectProperties.attributes.find('GlobalId') - if index != -1 \ - and object.BIMObjectProperties.attributes[index].string_value == line.split(' ')[3]: - object.select_set(True) + audited_global_ids.append(line.split(' ')[3]) + for object in bpy.context.visible_objects: + index = object.BIMObjectProperties.attributes.find('GlobalId') + if index != -1 \ + and object.BIMObjectProperties.attributes[index].string_value in audited_global_ids: + object.select_set(True) return {'FINISHED'} class QuickProjectSetup(bpy.types.Operator):