Fix potential instability when assigning types and synchronising Blender geometry

This commit is contained in:
Dion Moult
2021-05-18 14:36:12 +10:00
parent 30c31d694a
commit e6bf32b832
7 changed files with 69 additions and 78 deletions
@@ -4,8 +4,6 @@ from . import ui, prop, operator
classes = ( classes = (
operator.EditObjectPlacement, operator.EditObjectPlacement,
operator.AddRepresentation, operator.AddRepresentation,
operator.MapRepresentations,
operator.MapRepresentation,
operator.SwitchRepresentation, operator.SwitchRepresentation,
operator.RemoveRepresentation, operator.RemoveRepresentation,
operator.UpdateMeshRepresentation, operator.UpdateMeshRepresentation,
@@ -156,23 +156,35 @@ class AddRepresentation(bpy.types.Operator):
class SwitchRepresentation(bpy.types.Operator): class SwitchRepresentation(bpy.types.Operator):
bl_idname = "bim.switch_representation" bl_idname = "bim.switch_representation"
bl_label = "Switch Representation" bl_label = "Switch Representation"
obj: bpy.props.StringProperty()
ifc_definition_id: bpy.props.IntProperty() ifc_definition_id: bpy.props.IntProperty()
should_reload: bpy.props.BoolProperty()
disable_opening_subtractions: bpy.props.BoolProperty() disable_opening_subtractions: bpy.props.BoolProperty()
def execute(self, context): def execute(self, context):
self.obj = bpy.context.active_object self.element_obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
self.oprops = self.obj.BIMObjectProperties self.oprops = self.element_obj.BIMObjectProperties
self.file = IfcStore.get_file() self.file = IfcStore.get_file()
self.context_of_items = self.file.by_id(self.ifc_definition_id).ContextOfItems self.context_of_items = self.file.by_id(self.ifc_definition_id).ContextOfItems
self.mesh_name = "{}/{}".format(self.context_of_items.id(), self.ifc_definition_id) self.mesh_name = self.get_mesh_name()
mesh = bpy.data.meshes.get(self.mesh_name) mesh = bpy.data.meshes.get(self.mesh_name)
if mesh: if mesh:
self.obj.data.user_remap(mesh) self.element_obj.data.user_remap(mesh)
self.pull_mesh_from_ifc() if not mesh or self.should_reload:
self.pull_mesh_from_ifc()
return {"FINISHED"} return {"FINISHED"}
def get_mesh_name(self):
representation = self.resolve_mapped_representation(self.file.by_id(self.ifc_definition_id))
return "{}/{}".format(self.context_of_items.id(), representation.id())
def resolve_mapped_representation(self, representation):
if representation.RepresentationType == "MappedRepresentation":
return self.resolve_mapped_representation(representation.Items[0].MappingSource.MappedRepresentation)
return representation
def pull_mesh_from_ifc(self): def pull_mesh_from_ifc(self):
self.file = IfcStore.get_file() self.file = IfcStore.get_file()
logger = logging.getLogger("ImportIFC") logger = logging.getLogger("ImportIFC")
@@ -194,9 +206,9 @@ class SwitchRepresentation(bpy.types.Operator):
mesh = ifc_importer.create_mesh(element, shape) mesh = ifc_importer.create_mesh(element, shape)
mesh.name = self.mesh_name mesh.name = self.mesh_name
mesh.BIMMeshProperties.ifc_definition_id = self.ifc_definition_id mesh.BIMMeshProperties.ifc_definition_id = self.ifc_definition_id
self.obj.data.user_remap(mesh) self.element_obj.data.user_remap(mesh)
material_creator = import_ifc.MaterialCreator(ifc_import_settings, ifc_importer) material_creator = import_ifc.MaterialCreator(ifc_import_settings, ifc_importer)
material_creator.create(element, self.obj, mesh) material_creator.create(element, self.element_obj, mesh)
if self.disable_opening_subtractions and self.context_of_items.ContextIdentifier == "Body": if self.disable_opening_subtractions and self.context_of_items.ContextIdentifier == "Body":
if self.oprops.ifc_definition_id not in VoidData.products: if self.oprops.ifc_definition_id not in VoidData.products:
@@ -204,13 +216,13 @@ class SwitchRepresentation(bpy.types.Operator):
for opening_id in VoidData.products[self.oprops.ifc_definition_id]: for opening_id in VoidData.products[self.oprops.ifc_definition_id]:
if opening_id in IfcStore.id_map: if opening_id in IfcStore.id_map:
opening = IfcStore.id_map[opening_id] opening = IfcStore.id_map[opening_id]
modifier = self.obj.modifiers.new("IfcOpeningElement", "BOOLEAN") modifier = self.element_obj.modifiers.new("IfcOpeningElement", "BOOLEAN")
modifier.operation = "DIFFERENCE" modifier.operation = "DIFFERENCE"
modifier.object = opening modifier.object = opening
else: else:
for modifier in self.obj.modifiers: for modifier in self.element_obj.modifiers:
if modifier.type == "BOOLEAN" and "IfcOpeningElement" in modifier.name: if modifier.type == "BOOLEAN" and "IfcOpeningElement" in modifier.name:
self.obj.modifiers.remove(modifier) self.element_obj.modifiers.remove(modifier)
class RemoveRepresentation(bpy.types.Operator): class RemoveRepresentation(bpy.types.Operator):
@@ -249,61 +261,6 @@ class RemoveRepresentation(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
class MapRepresentations(bpy.types.Operator):
bl_idname = "bim.map_representations"
bl_label = "Map Representations"
product_id: bpy.props.IntProperty()
type_product_id: bpy.props.IntProperty()
def execute(self, context):
related_object = IfcStore.id_map[self.product_id]
if self.product_id not in Data.products:
Data.load(IfcStore.get_file(), self.product_id)
for representation_id in Data.products[self.product_id]:
bpy.ops.bim.remove_representation(obj=related_object.name, representation_id=representation_id)
if self.type_product_id not in Data.products:
Data.load(IfcStore.get_file(), self.type_product_id)
for representation_id in Data.products[self.type_product_id]:
bpy.ops.bim.map_representation(
obj=related_object.name,
representation_id=representation_id,
obj_data=IfcStore.id_map[self.type_product_id].data.name,
)
return {"FINISHED"}
class MapRepresentation(bpy.types.Operator):
bl_idname = "bim.map_representation"
bl_label = "Map Representation"
obj: bpy.props.StringProperty()
representation_id: bpy.props.IntProperty()
obj_data: bpy.props.StringProperty()
def execute(self, context):
objs = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
obj_data = bpy.data.meshes.get(self.obj_data) if self.obj_data else None
self.file = IfcStore.get_file()
for obj in objs:
bpy.ops.bim.edit_object_placement(obj=obj.name)
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
if obj_data:
obj.data = obj_data
result = ifcopenshell.api.run(
"geometry.map_representation", self.file, **{"representation": self.file.by_id(self.representation_id)}
)
ifcopenshell.api.run(
"geometry.assign_representation", self.file, **{"product": product, "representation": result}
)
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
return {"FINISHED"}
class UpdateMeshRepresentation(bpy.types.Operator): class UpdateMeshRepresentation(bpy.types.Operator):
bl_idname = "bim.update_mesh_representation" bl_idname = "bim.update_mesh_representation"
bl_label = "Update Mesh Representation" bl_label = "Update Mesh Representation"
@@ -407,7 +364,7 @@ class UpdateParametricRepresentation(bpy.types.Operator):
props = obj.data.BIMMeshProperties props = obj.data.BIMMeshProperties
parameter = props.ifc_parameters[self.index] parameter = props.ifc_parameters[self.index]
element = IfcStore.get_file().by_id(parameter.step_id)[parameter.index] = parameter.value element = IfcStore.get_file().by_id(parameter.step_id)[parameter.index] = parameter.value
bpy.ops.bim.switch_representation(ifc_definition_id=props.ifc_definition_id) bpy.ops.bim.switch_representation(ifc_definition_id=props.ifc_definition_id, should_reload=True)
return {"FINISHED"} return {"FINISHED"}
@@ -39,6 +39,7 @@ class BIM_PT_representations(Panel):
row.label(text=representation["ContextOfItems"]["TargetView"]) row.label(text=representation["ContextOfItems"]["TargetView"])
row.label(text=representation["RepresentationType"]) row.label(text=representation["RepresentationType"])
op = row.operator("bim.switch_representation", icon="OUTLINER_DATA_MESH", text="") op = row.operator("bim.switch_representation", icon="OUTLINER_DATA_MESH", text="")
op.should_reload = True
op.ifc_definition_id = ifc_definition_id op.ifc_definition_id = ifc_definition_id
op.disable_opening_subtractions = False op.disable_opening_subtractions = False
row.operator("bim.remove_representation", icon="X", text="").representation_id = ifc_definition_id row.operator("bim.remove_representation", icon="X", text="").representation_id = ifc_definition_id
@@ -68,9 +69,11 @@ class BIM_PT_mesh(Panel):
row = layout.row(align=True) row = layout.row(align=True)
op = row.operator("bim.switch_representation", text="Bake Voids", icon="SELECT_SUBTRACT") op = row.operator("bim.switch_representation", text="Bake Voids", icon="SELECT_SUBTRACT")
op.should_reload = True
op.ifc_definition_id = props.ifc_definition_id op.ifc_definition_id = props.ifc_definition_id
op.disable_opening_subtractions = False op.disable_opening_subtractions = False
op = row.operator("bim.switch_representation", text="Dynamic Voids", icon="SELECT_INTERSECT") op = row.operator("bim.switch_representation", text="Dynamic Voids", icon="SELECT_INTERSECT")
op.should_reload = True
op.ifc_definition_id = props.ifc_definition_id op.ifc_definition_id = props.ifc_definition_id
op.disable_opening_subtractions = True op.disable_opening_subtractions = True
@@ -245,9 +245,7 @@ class CopyClass(bpy.types.Operator):
IfcStore.link_element(result, obj) IfcStore.link_element(result, obj)
relating_type = ifcopenshell.util.element.get_type(result) relating_type = ifcopenshell.util.element.get_type(result)
if relating_type and relating_type.RepresentationMaps: if relating_type and relating_type.RepresentationMaps:
bpy.ops.bim.map_representations( bpy.ops.bim.assign_type(relating_type=relating_type.id(), related_object=obj.name)
product_id=result.id(), type_product_id=ifcopenshell.util.element.get_type(result).id()
)
else: else:
bpy.ops.bim.add_representation(obj=obj.name) bpy.ops.bim.add_representation(obj=obj.name)
return {"FINISHED"} return {"FINISHED"}
@@ -4,6 +4,7 @@ import ifcopenshell.util.type
import ifcopenshell.api import ifcopenshell.api
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.type.data import Data from ifcopenshell.api.type.data import Data
from ifcopenshell.api.geometry.data import Data as GeometryData
from blenderbim.bim.module.type.prop import getIfcTypes, getAvailableTypes, updateTypeInstanceIfcClass from blenderbim.bim.module.type.prop import getIfcTypes, getAvailableTypes, updateTypeInstanceIfcClass
from mathutils import Vector from mathutils import Vector
@@ -31,8 +32,18 @@ class AssignType(bpy.types.Operator):
}, },
) )
Data.load(IfcStore.get_file(), oprops.ifc_definition_id) Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
if self.file.by_id(relating_type).RepresentationMaps: GeometryData.load(IfcStore.get_file(), oprops.ifc_definition_id)
bpy.ops.bim.map_representations(product_id=oprops.ifc_definition_id, type_product_id=relating_type) representation_ids = GeometryData.products[oprops.ifc_definition_id]
if not representation_ids:
pass # TODO: clear geometry? Make void? Make none type?
has_switched = False
for representation_id in representation_ids:
representation = GeometryData.representations[representation_id]
if representation["ContextOfItems"]["ContextIdentifier"] == "Body":
bpy.ops.bim.switch_representation(obj=related_object.name, ifc_definition_id=representation_id)
has_switched = True
if not has_switched and representation_ids:
bpy.ops.bim.switch_representation(obj=related_object.name, ifc_definition_id=representation_id)
bpy.ops.bim.disable_editing_type(obj=related_object.name) bpy.ops.bim.disable_editing_type(obj=related_object.name)
return {"FINISHED"} return {"FINISHED"}
@@ -14,11 +14,8 @@ class Data:
cls.products[product_id] = [] cls.products[product_id] = []
product = file.by_id(product_id) product = file.by_id(product_id)
representations = [] representations = []
if product.is_a("IfcProduct"): if product.is_a("IfcProduct") and product.Representation:
if product.Representation: representations = product.Representation.Representations
representations = product.Representation.Representations
else:
representations = []
elif product.is_a("IfcTypeProduct"): elif product.is_a("IfcTypeProduct"):
representations = [rm.MappedRepresentation for rm in product.RepresentationMaps or []] representations = [rm.MappedRepresentation for rm in product.RepresentationMaps or []]
for representation in representations: for representation in representations:
@@ -52,3 +52,30 @@ class Usecase:
"RelatingType": self.settings["relating_type"], "RelatingType": self.settings["relating_type"],
} }
) )
self.map_representations()
def map_representations(self):
if not self.settings["relating_type"].RepresentationMaps:
return
representations = []
if self.settings["related_object"].Representation:
representations = self.settings["related_object"].Representation.Representations
for representation in representations:
# TODO: check if this is right? Surely this can be a single usecase?
ifcopenshell.api.run(
"geometry.unassign_representation",
self.file,
**{"product": self.settings["related_object"], "representation": representation}
)
ifcopenshell.api.run("geometry.remove_representation", self.file, **{"representation": representation})
for representation_map in self.settings["relating_type"].RepresentationMaps:
representation = representation_map.MappedRepresentation
result = ifcopenshell.api.run(
"geometry.map_representation", self.file, **{"representation": representation}
)
ifcopenshell.api.run(
"geometry.assign_representation",
self.file,
**{"product": self.settings["related_object"], "representation": result}
)