diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index d53311eac5..a511f14a37 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -201,6 +201,8 @@ if bpy is not None: operator.CreateShapeFromStepId, operator.SelectHighPolygonMeshes, operator.RefreshDrawingList, + operator.GetRepresentationIfcParameters, + operator.UpdateIfcRepresentation, prop.StrProperty, prop.Variable, prop.Role, @@ -236,6 +238,7 @@ if bpy is not None: prop.MapConversion, prop.TargetCRS, prop.Attribute, + prop.IfcParameter, prop.BoundaryCondition, prop.PsetQto, prop.GlobalId, diff --git a/src/ifcblenderexport/blenderbim/bim/export_ifc.py b/src/ifcblenderexport/blenderbim/bim/export_ifc.py index 88d75cdef0..5ab9cba773 100644 --- a/src/ifcblenderexport/blenderbim/bim/export_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/export_ifc.py @@ -489,7 +489,7 @@ class IfcParser(): def get_applicable_qtos(self, ifc_class): results = [] - empty = ifcopenshell.file() + empty = ifcopenshell.file(schema=self.ifc_export_settings.schema) element = empty.create_entity(ifc_class) for ifc_class, qto_names in schema.ifc.applicable_qtos.items(): if element.is_a(ifc_class): @@ -1026,6 +1026,9 @@ class IfcParser(): self.representations['Model/Body/MODEL_VIEW/{}'.format(obj.data.name)] = self.get_representation( obj.data, obj, 'Model', 'Body', 'MODEL_VIEW') if 'Model/Box/MODEL_VIEW' in self.generated_subcontexts: + if self.ifc_export_settings.should_roundtrip_native \ + and obj.data.BIMMeshProperties.ifc_definition: + return self.representations['Model/Box/MODEL_VIEW/{}'.format(obj.data.name)] = self.get_representation( obj.data, obj, 'Model', 'Box', 'MODEL_VIEW') @@ -1063,11 +1066,13 @@ class IfcParser(): self.representations[mesh_name] = self.get_representation( mesh, obj, context, subcontext, target_view) if 'Model/Box/MODEL_VIEW' in self.generated_subcontexts \ - and context == 'Model' \ - and subcontext == 'Body' \ - and target_view == 'MODEL_VIEW': - self.representations['Model/Box/MODEL_VIEW/{}'.format(mesh_name.split('/')[3])] = self.get_representation( - obj.data, obj, 'Model', 'Box', 'MODEL_VIEW') + and context_prefix == 'Model/Body/MODEL_VIEW': + if self.ifc_export_settings.should_roundtrip_native \ + and obj.data.BIMMeshProperties.ifc_definition: + pass + else: + self.representations['Model/Box/MODEL_VIEW/{}'.format(mesh_name.split('/')[3])] = self.get_representation( + obj.data, obj, 'Model', 'Box', 'MODEL_VIEW') elif context_prefix == 'Model/Body/MODEL_VIEW' \ and obj.data \ and not self.is_mesh_context_sensitive(obj.data.name): @@ -1096,6 +1101,8 @@ class IfcParser(): 'context': context, 'subcontext': subcontext, 'target_view': target_view, + 'ifc_definition': mesh.BIMMeshProperties.ifc_definition if hasattr(mesh, 'BIMMeshProperties') else None, + 'ifc_definition_id': mesh.BIMMeshProperties.ifc_definition_id if hasattr(mesh, 'BIMMeshProperties') else None, 'is_parametric': mesh.BIMMeshProperties.is_parametric if hasattr(mesh, 'BIMMeshProperties') else False, 'is_curve': isinstance(mesh, bpy.types.Curve), 'is_point_cloud': self.is_point_cloud(obj), @@ -2172,11 +2179,15 @@ class IfcExporter(): def get_product_shape_representations(self, product): results = [] for representation_name in product['representations']: - results.append(self.get_product_mapped_geometry(product, representation_name)) + representation = self.ifc_parser.representations[representation_name] + if self.ifc_export_settings.should_roundtrip_native and representation['ifc_definition']: + results.append(representation['ifc']) + else: + results.append(self.get_product_mapped_geometry(product, representation)) return results - def get_product_mapped_geometry(self, product, representation_name): - mapping_source = self.ifc_parser.representations[representation_name]['ifc'] + def get_product_mapped_geometry(self, product, representation): + mapping_source = representation['ifc'] shape_representation = mapping_source.MappedRepresentation if product['has_scale']: if not product['has_mirror']: @@ -2222,6 +2233,8 @@ class IfcExporter(): self.file.createIfcDirection((forward.x, forward.y, forward.z))) def create_representation(self, representation): + if self.ifc_export_settings.should_roundtrip_native and representation['ifc_definition']: + return self.create_representation_from_definition(representation) self.ifc_vertices = [] self.ifc_edges = [] if representation['context'] == 'Model': @@ -2231,6 +2244,30 @@ class IfcExporter(): elif representation['context'] == 'NotDefined': return self.create_variable_representation(representation) + def create_representation_from_definition(self, representation): + # See bug #999 on why we don't garbage collect the temporary IFC file + representation['ifc_definition_file'] = ifcopenshell.file.from_string(representation['ifc_definition']) + entry = self.file.add(representation['ifc_definition_file'].by_id(representation['ifc_definition_id'])) + substitutions = [] + for element in representation['ifc_definition_file']: + added_element = self.file.add(element) + if added_element.is_a('IfcGeometricRepresentationContext'): + substitutions.append(added_element) + for element in substitutions: + if element.is_a() == 'IfcGeometricRepresentationContext': + new_element = [e for e in + self.file.by_type('IfcGeometricRepresentationContext') + if e.ContextType == element.ContextType][0] + elif element.is_a() == 'IfcGeometricRepresentationSubContext': + new_element = [e for e in + self.file.by_type('IfcGeometricRepresentationContext') + if e.ContextType == element.ContextType and + e.ContextIdentifier == element.ContextIdentifier][0] + for inverse in self.file.get_inverse(element): + ifcopenshell.util.element.replace_attribute(inverse, element, new_element) + self.file.remove(element) + return entry + def create_model_representation(self, representation): if representation['subcontext'] == 'Annotation': return self.file.createIfcRepresentationMap(self.origin, @@ -3063,6 +3100,7 @@ class IfcExportSettings: settings.should_use_presentation_style_assignment = scene_bim.export_should_use_presentation_style_assignment settings.should_guess_quantities = scene_bim.export_should_guess_quantities settings.should_force_faceted_brep = scene_bim.export_should_force_faceted_brep + settings.should_roundtrip_native = scene_bim.import_export_should_roundtrip_native settings.context_tree = [] for ifc_context in ['model', 'plan']: if getattr(scene_bim, 'has_{}_context'.format(ifc_context)): diff --git a/src/ifcblenderexport/blenderbim/bim/import_ifc.py b/src/ifcblenderexport/blenderbim/bim/import_ifc.py index 989e6cc961..7384a6e6e6 100644 --- a/src/ifcblenderexport/blenderbim/bim/import_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/import_ifc.py @@ -876,6 +876,7 @@ class IfcImporter(): cumulative_vertex_index += item['total_vertices'] def create_native_mesh(self, element, shape): + # TODO This should be split off into its own module for run-time native mesh conversion data = self.native_elements[element.GlobalId] materials = [] items = [] @@ -1826,6 +1827,11 @@ class IfcImporter(): results.append({ 'raw': representation, 'matrix': self.scale_matrix(matrix) }) return results + def get_representation_of_context(self, representations, context): + for representation in representations: + if representation.RepresentationIdentifier == context: + return representation + def scale_matrix(self, matrix): matrix[0][3] *= self.unit_scale matrix[1][3] *= self.unit_scale @@ -1912,10 +1918,29 @@ class IfcImporter(): ios_materials.append(mat.name) mesh['ios_materials'] = ios_materials mesh['ios_material_ids'] = geometry.material_ids - mesh.BIMMeshProperties.geometry_type = str(self.get_geometry_type(element)) + self.store_representation_source(mesh, element, shape) return mesh except: self.ifc_import_settings.logger.error('Could not create mesh for {}'.format(element)) + import traceback + print(traceback.format_exc()) + + def store_representation_source(self, mesh, element, shape): + # TODO Refactor to specialist class + mesh.BIMMeshProperties.geometry_type = str(self.get_geometry_type(element)) + if not self.ifc_import_settings.should_roundtrip_native: + return + dummy = ifcopenshell.file(schema=self.file.schema) + if element.is_a('IfcRepresentation'): + representation = element + else: + representation = self.get_representation_of_context(element.Representation.Representations, shape.context) + mesh.BIMMeshProperties.ifc_definition_id = int(dummy.add(representation).id()) + for child in self.file.traverse(representation): + [dummy.add(inverse) for inverse in self.file.get_inverse(child)] + dummy.add(child) + mesh.BIMMeshProperties.ifc_definition = dummy.to_string() + def create_curve(self, geometry): curve = bpy.data.curves.new(geometry.id, type='CURVE') @@ -2062,6 +2087,7 @@ class IfcImportSettings: settings.should_use_cpu_multiprocessing = scene_bim.import_should_use_cpu_multiprocessing settings.should_import_with_profiling = scene_bim.import_should_import_with_profiling settings.should_import_native = scene_bim.import_should_import_native + settings.should_roundtrip_native = scene_bim.import_export_should_roundtrip_native settings.should_use_legacy = scene_bim.import_should_use_legacy settings.should_import_aggregates = scene_bim.import_should_import_aggregates settings.should_merge_aggregates = scene_bim.import_should_merge_aggregates diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 91e5002e0a..dfbd971e68 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -4082,3 +4082,53 @@ class RefreshDrawingList(bpy.types.Operator): new.name = obj.name.split('/')[1] new.camera = obj return {'FINISHED'} + + +class GetRepresentationIfcParameters(bpy.types.Operator): + bl_idname = 'bim.get_representation_ifc_parameters' + bl_label = 'Get Representation IFC Parameters' + + def execute(self, context): + props = bpy.context.active_object.data.BIMMeshProperties + dummy = ifcopenshell.file.from_string(props.ifc_definition) + for element in dummy: + if not element.is_a('IfcRepresentationItem'): + continue + for i in range(0, len(element)): + if element.attribute_type(i) == 'DOUBLE': + new = props.ifc_parameters.add() + new.name = '{}/{}'.format(element.is_a(), element.attribute_name(i)) + new.step_id = element.id() + new.type = element.attribute_type(i) + new.index = i + if element[i]: + new.value = element[i] + return {'FINISHED'} + + +class UpdateIfcRepresentation(bpy.types.Operator): + bl_idname = 'bim.update_ifc_representation' + bl_label = 'Update IFC Representation' + index: bpy.props.IntProperty() + + def execute(self, context): + props = bpy.context.active_object.data.BIMMeshProperties + parameter = props.ifc_parameters[self.index] + dummy = ifcopenshell.file.from_string(props.ifc_definition) + element = dummy.by_id(parameter.step_id)[parameter.index] = parameter.value + props.ifc_definition = dummy.to_string() + self.recreate_ifc_representation() + return {'FINISHED'} + + def recreate_ifc_representation(self): + props = bpy.context.active_object.data.BIMMeshProperties + dummy = ifcopenshell.file.from_string(props.ifc_definition) + logger = logging.getLogger('ImportIFC') + self.ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, ifc.IfcStore.path, logger) + element = dummy.by_id(props.ifc_definition_id) + settings = ifcopenshell.geom.settings() + shape = ifcopenshell.geom.create_shape(settings, element) + ifc_importer = import_ifc.IfcImporter(self.ifc_import_settings) + ifc_importer.file = dummy + mesh = ifc_importer.create_mesh(element, shape) + bpy.context.active_object.data.user_remap(mesh) diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index b9548f1ba0..c7f2c58efd 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -1207,6 +1207,7 @@ class BIMProperties(PropertyGroup): import_should_treat_styled_item_as_material: BoolProperty(name="Import Treating Styled Item as Material", default=False) import_should_use_legacy: BoolProperty(name="Import with Legacy Importer", default=False) import_should_import_native: BoolProperty(name="Import Native Representations", default=False) + import_export_should_roundtrip_native: BoolProperty(name="Roundtrip Native Representations", default=False) import_should_use_cpu_multiprocessing: BoolProperty(name="Import with CPU Multiprocessing", default=True) import_should_import_with_profiling: BoolProperty(name="Import with Profiling", default=True) import_should_import_aggregates: BoolProperty(name="Import Aggregates", default=True) @@ -1351,6 +1352,15 @@ class Attribute(PropertyGroup): int_value: IntProperty(name="Value") float_value: FloatProperty(name="Value") + +class IfcParameter(PropertyGroup): + name: StringProperty(name="Name") + step_id: IntProperty(name="STEP ID") + index: IntProperty(name="Index") + value: FloatProperty(name="Value") # For now, only floats + type: StringProperty(name="Type") + + class PsetQto(PropertyGroup): name: StringProperty(name="Name") properties: CollectionProperty(name="Properties", type=Attribute) @@ -1426,4 +1436,7 @@ class BIMMeshProperties(PropertyGroup): is_parametric: BoolProperty(name='Is Parametric', default=False) presentation_layer: StringProperty(name="Presentation Layer") geometry_type: StringProperty(name="Geometry Type") + ifc_definition: StringProperty(name="IFC Definition") + ifc_definition_id: IntProperty(name="IFC Definition ID") + ifc_parameters: CollectionProperty(name="IFC Parameters", type=IfcParameter) active_representation_item_index: IntProperty(name='Active Representation Item Index') diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 1c5eb13bc3..c4e02d7b04 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -605,6 +605,18 @@ class BIM_PT_mesh(Panel): row = layout.row() row.prop(props, 'geometry_type') + row = layout.row() + row.prop(props, 'ifc_definition') + layout.label(text="IFC Parameters:") + row = layout.row() + row.operator('bim.get_representation_ifc_parameters') + for index, ifc_parameter in enumerate(props.ifc_parameters): + row = layout.row(align=True) + row.prop(ifc_parameter, 'name', text='') + row.prop(ifc_parameter, 'step_id') + row.prop(ifc_parameter, 'index') + row.prop(ifc_parameter, 'value', text='') + row.operator('bim.update_ifc_representation', icon='FILE_REFRESH', text='').index = index row = layout.row() row.prop(props, 'presentation_layer') @@ -1696,6 +1708,8 @@ class BIM_PT_mvd(Panel): row = layout.row() row.prop(bim_properties, 'import_should_import_native') row = layout.row() + row.prop(bim_properties, 'import_export_should_roundtrip_native') + row = layout.row() row.prop(bim_properties, 'import_should_use_cpu_multiprocessing') row = layout.row() row.prop(bim_properties, 'import_should_import_with_profiling')