Proof of concept of native representation roundtripping

This commit is contained in:
Dion Moult
2020-09-17 17:28:17 +10:00
parent 2310bf628f
commit 6cc4ce46c2
6 changed files with 154 additions and 10 deletions
@@ -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,
@@ -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)):
@@ -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
@@ -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)
@@ -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')
+14
View File
@@ -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')