Fix #952. Implement roundtripping of all representation contexts.

This commit is contained in:
Dion Moult
2020-11-30 19:37:53 +11:00
parent 7b7f756506
commit 2596f90015
4 changed files with 115 additions and 75 deletions
@@ -1023,8 +1023,6 @@ class IfcParser:
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_id:
return
self.representations["Model/Box/MODEL_VIEW/{}".format(obj.data.name)] = self.get_representation(
obj.data, obj, "Model", "Box", "MODEL_VIEW"
)
@@ -1057,21 +1055,44 @@ class IfcParser:
for context in self.ifc_export_settings.context_tree:
for subcontext in context["subcontexts"]:
for target_view in subcontext["target_views"]:
self.append_representation_in_context(obj, context["name"], subcontext["name"], target_view, name)
rep_context = self.get_obj_representation_context(obj, context["name"], subcontext["name"], target_view)
if rep_context:
self.append_representation_in_context(obj, rep_context, name)
def get_obj_representation_context(self, obj, context, subcontext, target_view):
for c in obj.BIMObjectProperties.representation_contexts:
if c.context == context and c.name == subcontext and c.target_view == target_view:
return c
if obj.BIMObjectProperties.representation_contexts:
return
if context == "Model" and subcontext == "Body" and target_view == "MODEL_VIEW":
representation_context = obj.BIMObjectProperties.representation_contexts.add()
representation_context.context = "Model"
representation_context.name = "Body"
representation_context.target_view = "MODEL_VIEW"
return representation_context
def append_representation_in_context(self, obj, rep_context, name):
context = rep_context.context
subcontext = rep_context.name
target_view = rep_context.target_view
if self.ifc_export_settings.should_roundtrip_native and rep_context.ifc_definition_id:
self.representations[
"{}/{}/{}/{}".format(context, subcontext, target_view, name)
] = self.get_representation(obj.data, obj, context, subcontext, target_view)
return
def append_representation_in_context(self, obj, context, subcontext, target_view, name):
context_prefix = "/".join([context, subcontext, target_view])
mesh_name = "/".join([context_prefix, name])
mesh = self.search_for_mesh_or_curve_data(mesh_name)
# TODO: if the search result is empty, we should check for ifc_definition_id
if mesh:
self.representations[mesh_name] = self.get_representation(mesh, obj, context, subcontext, target_view)
if "Model/Box/MODEL_VIEW" in self.generated_subcontexts and context_prefix == "Model/Body/MODEL_VIEW":
if self.ifc_export_settings.should_roundtrip_native and obj.data.BIMMeshProperties.ifc_definition_id:
pass
else:
self.representations[
"Model/Box/MODEL_VIEW/{}".format(mesh_name.split("/")[3])
] = self.get_representation(obj.data, obj, "Model", "Box", "MODEL_VIEW")
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)
):
@@ -1090,6 +1111,7 @@ class IfcParser:
return data
def get_representation(self, mesh, obj, context, subcontext, target_view):
rep_context = self.get_obj_representation_context(obj, context, subcontext, target_view)
return {
"ifc": None,
"raw": mesh,
@@ -1097,11 +1119,9 @@ class IfcParser:
"context": context,
"subcontext": subcontext,
"target_view": target_view,
"has_ifc_definition": False
if not hasattr(mesh, "BIMMeshProperties")
else (mesh.BIMMeshProperties.ifc_definition or mesh.BIMMeshProperties.ifc_definition_id),
"has_ifc_definition": rep_context and rep_context.ifc_definition_id,
"ifc_definition": mesh.BIMMeshProperties.ifc_definition if hasattr(mesh, "BIMMeshProperties") else None,
"ifc_definition_id": mesh.BIMMeshProperties.ifc_definition_id
"ifc_definition_id": rep_context.ifc_definition_id if rep_context else 0
if hasattr(mesh, "BIMMeshProperties")
else None,
"is_parametric": mesh.BIMMeshProperties.is_parametric if hasattr(mesh, "BIMMeshProperties") else False,
@@ -1116,7 +1136,7 @@ class IfcParser:
"presentation_layer": mesh.BIMMeshProperties.presentation_layer_index
if hasattr(mesh, "BIMMeshProperties") and mesh.BIMMeshProperties.presentation_layer_index != -1
else False,
"attributes": {"Name": mesh.name},
"attributes": {"Name": mesh.name if mesh else ""},
}
def is_wireframe_mesh(self, mesh, obj):
@@ -1323,7 +1343,8 @@ class IfcExporter:
self.template_file = "{}template.ifc".format(ifc_export_settings.schema_dir)
self.ifc_export_settings = ifc_export_settings
self.ifc_parser = ifc_parser
self.roundtrip_id_map = {}
self.migrator = ifcopenshell.util.schema.Migrator()
self.roundtrip_id_new_to_old = {}
def export(self, selected_objects):
self.schema_version = self.ifc_export_settings.schema
@@ -1633,6 +1654,7 @@ class IfcExporter:
if self.file.schema == "IFC4":
classification["ifc"] = self.file.add(classification["raw_element"])
else:
# TODO: Check if we can use self.migrator instead
migrator = ifcopenshell.util.schema.Migrator()
classification["ifc"] = migrator.migrate(classification["raw_element"], self.file)
self.file.createIfcRelAssociatesClassification(
@@ -1649,6 +1671,7 @@ class IfcExporter:
if self.file.schema == "IFC4":
reference["ifc"] = self.file.add(reference["raw_element"])
else:
# TODO: Check if we can use self.migrator instead
migrator = ifcopenshell.util.schema.Migrator()
reference["ifc"] = migrator.migrate(reference["raw_element"], self.file)
@@ -2023,10 +2046,11 @@ class IfcExporter:
# in edge cases.
if representation.RepresentationIdentifier != "Body":
continue
if product["raw"].data.BIMMeshProperties.ifc_definition_id:
rep_context = self.ifc_parser.get_obj_representation_context(product["raw"], "Model", "Body", "MODEL_VIEW")
if rep_context and rep_context.ifc_definition_id:
# For native roundtripping, each slot could be a one to many relationship to items
for item in self.get_geometric_representation_items(representation):
original_id = self.roundtrip_id_map[item.id()]
original_id = self.roundtrip_id_new_to_old[item.id()]
i = product["raw"].data.BIMMeshProperties.ifc_item_ids.get(str(original_id)).slot_index
material_slots.append((product["raw"].material_slots[i].name, item))
else:
@@ -2384,38 +2408,40 @@ class IfcExporter:
if representation["ifc_definition"]:
print("Authoring an IFC definition directly is not yet implemented")
return
elif representation["ifc_definition_id"]:
if representation["ifc_definition_id"]:
return self.create_representation_from_definition_id(representation)
def create_representation_from_definition_id(self, representation):
if self.file.schema == ifc.IfcStore.get_file().schema:
entry = self.file.add(ifc.IfcStore.get_file().by_id(representation["ifc_definition_id"]))
else:
entry = self.migrator.migrate(ifc.IfcStore.get_file().by_id(representation["ifc_definition_id"]), self.file)
substitutions = {"contexts": []}
representation_elements = ifc.IfcStore.get_file().traverse(
ifc.IfcStore.get_file().by_id(representation["ifc_definition_id"])
)
for element in representation_elements:
if self.file.schema == ifc.IfcStore.get_file().schema:
entry = self.file.add(ifc.IfcStore.get_file().by_id(representation["ifc_definition_id"]))
added_element = self.file.add(element)
else:
migrator = ifcopenshell.util.schema.Migrator()
entry = migrator.migrate(ifc.IfcStore.get_file().by_id(representation["ifc_definition_id"]), self.file)
added_element = self.migrator.migrate(element, self.file)
if added_element.is_a("IfcGeometricRepresentationContext"):
substitutions["contexts"].append(added_element)
elif added_element.is_a("IfcGeometricRepresentationItem"):
self.roundtrip_id_new_to_old[added_element.id()] = element.id()
substitutions = {"contexts": []}
representation_elements = ifc.IfcStore.get_file().traverse(
ifc.IfcStore.get_file().by_id(representation["ifc_definition_id"])
)
for element in representation_elements:
if self.file.schema == ifc.IfcStore.get_file().schema:
added_element = self.file.add(element)
else:
added_element = migrator.migrate(element, self.file)
if added_element.is_a("IfcGeometricRepresentationContext"):
substitutions["contexts"].append(added_element)
elif added_element.is_a("IfcGeometricRepresentationItem"):
self.roundtrip_id_map[added_element.id()] = element.id()
for element in substitutions["contexts"]:
new_element = self.ifc_rep_context[representation["context"]][representation["subcontext"]][
representation["target_view"]
]["ifc"]
for inverse in self.file.get_inverse(element):
ifcopenshell.util.element.replace_attribute(inverse, element, new_element)
# TODO: Work out how and when to purge this
# self.file.remove(element)
return entry
for element in substitutions["contexts"]:
new_element = self.ifc_rep_context[representation["context"]][representation["subcontext"]][
representation["target_view"]
]["ifc"]
for inverse in self.file.get_inverse(element):
ifcopenshell.util.element.replace_attribute(inverse, element, new_element)
# TODO: Work out how and when to purge this
# self.file.remove(element)
return entry
def create_model_representation(self, representation):
if representation["subcontext"] == "Annotation":
@@ -1366,10 +1366,12 @@ class IfcImporter:
def add_product_representation_contexts(self, element, obj):
subcontexts = []
ifc_definition_ids = []
if element.is_a("IfcProduct"):
if not element.Representation:
return
for r in element.Representation.Representations:
ifc_definition_ids.append(r.id())
if r.ContextOfItems.is_a("IfcGeometricRepresentationSubContext"):
subcontexts.append(
"{}/{}/{}".format(
@@ -1388,6 +1390,7 @@ class IfcImporter:
if not element.RepresentationMaps:
return
for r in element.RepresentationMaps:
ifc_definition_ids.append(r.id())
if r.MappedRepresentation.ContextOfItems.is_a("IfcGeometricRepresentationSubContext"):
subcontexts.append(
"{}/{}/{}".format(
@@ -1404,14 +1407,14 @@ class IfcImporter:
"",
)
)
subcontexts = set(subcontexts)
for subcontext in subcontexts:
for i, subcontext in enumerate(subcontexts):
representation_context = obj.BIMObjectProperties.representation_contexts.add()
(
representation_context.context,
representation_context.name,
representation_context.target_view,
) = subcontext.split("/")
representation_context.ifc_definition_id = ifc_definition_ids[i]
def add_product_definitions(self, element, obj):
if not hasattr(element, "IsDefinedBy") or not element.IsDefinedBy:
@@ -2059,11 +2062,6 @@ 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
@@ -2164,25 +2162,13 @@ class IfcImporter:
ios_materials.append(mat.name)
mesh["ios_materials"] = ios_materials
mesh["ios_material_ids"] = geometry.material_ids
self.store_representation_source(mesh, element, shape)
mesh.BIMMeshProperties.geometry_type = str(self.get_geometry_type(element))
return mesh
except:
self.ifc_import_settings.logger.error("Could not create mesh for %s", 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
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(representation.id())
def create_curve(self, geometry):
curve = bpy.data.curves.new(geometry.id, type="CURVE")
curve.dimensions = "3D"
@@ -2579,10 +2579,16 @@ class SwitchContext(bpy.types.Operator):
if "/" not in self.obj.data.name:
self.obj.data.name = ifcopenshell.guid.compress(str(uuid.uuid4()).replace("-", ""))
self.obj.data.name = "Model/Body/MODEL_VIEW/" + self.obj.data.name
representation_context = self.obj.BIMObjectProperties.representation_contexts.add()
representation_context.context = "Model"
representation_context.name = "Body"
representation_context.target_view = "MODEL_VIEW"
has_default_context = False
for subcontext in self.obj.BIMObjectProperties.representation_contexts:
if subcontext.context == "Model" and subcontext.name == "Body" and subcontext.target_view == "MODEL_VIEW":
has_default_context = True
break
if not has_default_context:
representation_context = self.obj.BIMObjectProperties.representation_contexts.add()
representation_context.context = "Model"
representation_context.name = "Body"
representation_context.target_view = "MODEL_VIEW"
self.context = bpy.context.scene.BIMProperties.available_contexts
self.subcontext = bpy.context.scene.BIMProperties.available_subcontexts
@@ -4512,8 +4518,12 @@ class GetRepresentationIfcParameters(bpy.types.Operator):
bl_label = "Get Representation IFC Parameters"
def execute(self, context):
props = bpy.context.active_object.data.BIMMeshProperties
elements = ifc.IfcStore.get_file().traverse(ifc.IfcStore.get_file().by_id(props.ifc_definition_id))
obj = bpy.context.active_object
props = obj.data.BIMMeshProperties
element_id = self.get_ifc_definition_id(obj)
if not element_id:
return {"FINISHED"}
elements = ifc.IfcStore.get_file().traverse(ifc.IfcStore.get_file().by_id(element_id))
for element in elements:
if not element.is_a("IfcRepresentationItem"):
continue
@@ -4528,6 +4538,15 @@ class GetRepresentationIfcParameters(bpy.types.Operator):
new.value = element[i]
return {"FINISHED"}
def get_ifc_definition_id(self, obj):
if "/" not in obj.data.name:
context, subcontext, target_view = ("Model", "Body", "MODEL_VIEW")
else:
context, subcontext, target_view = obj.data.name.split("/")[0:3]
for c in obj.BIMObjectProperties.representation_contexts:
if c.context == context and c.name == subcontext and c.target_view == target_view:
return c.ifc_definition_id or None
class UpdateIfcRepresentation(bpy.types.Operator):
bl_idname = "bim.update_ifc_representation"
@@ -4545,7 +4564,7 @@ class UpdateIfcRepresentation(bpy.types.Operator):
props = bpy.context.active_object.data.BIMMeshProperties
logger = logging.getLogger("ImportIFC")
self.ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, ifc.IfcStore.path, logger)
element = ifc.IfcStore.get_file().by_id(props.ifc_definition_id)
element = ifc.IfcStore.get_file().by_id(self.get_ifc_definition_id(bpy.context.active_object))
settings = ifcopenshell.geom.settings()
shape = ifcopenshell.geom.create_shape(settings, element)
ifc_importer = import_ifc.IfcImporter(self.ifc_import_settings)
@@ -4556,6 +4575,15 @@ class UpdateIfcRepresentation(bpy.types.Operator):
if ifc_importer.material_creator.parse_representation(element):
ifc_importer.material_creator.assign_material_slots_to_faces(bpy.context.active_object)
def get_ifc_definition_id(self, obj):
if "/" not in obj.data.name:
context, subcontext, target_view = ("Model", "Body", "MODEL_VIEW")
else:
context, subcontext, target_view = obj.data.name.split("/")[0:3]
for c in obj.BIMObjectProperties.representation_contexts:
if c.context == context and c.name == subcontext and c.target_view == target_view:
return c.ifc_definition_id or None
class BlenderClasher:
def process_clash_set(self):
+1 -1
View File
@@ -553,6 +553,7 @@ class Subcontext(PropertyGroup):
name: StringProperty(name="Name")
context: StringProperty(name="Context")
target_view: StringProperty(name="Target View")
ifc_definition_id: IntProperty(name="IFC Definition ID")
class MaterialLayer(PropertyGroup):
@@ -1732,7 +1733,6 @@ class BIMMeshProperties(PropertyGroup):
is_parametric: BoolProperty(name="Is Parametric", default=False)
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")
presentation_layer_index: IntProperty(name="Presentation Layer Index", default=-1)