Merge pull request #3969 from brunoperdigao/refactor-duplicate-aggregate

Refactor duplicate aggregate
This commit is contained in:
Ryan Schultz
2023-12-06 14:39:43 -06:00
committed by GitHub
5 changed files with 398 additions and 443 deletions
@@ -27,10 +27,10 @@ classes = (
operator.EnableEditingRepresentationItems,
operator.FlipObject,
operator.GetRepresentationIfcParameters,
operator.DuplicateMoveLinkedAggregate,
operator.DuplicateMoveLinkedAggregateMacro,
operator.OverrideDelete,
operator.OverrideDuplicateMove,
operator.OverrideDuplicateMoveAggregate,
operator.OverrideDuplicateMoveAggregateMacro,
operator.OverrideDuplicateMoveLinked,
operator.OverrideDuplicateMoveLinkedMacro,
operator.OverrideDuplicateMoveMacro,
@@ -42,7 +42,7 @@ classes = (
operator.OverrideOutlinerDelete,
operator.OverridePasteBuffer,
operator.PurgeUnusedRepresentations,
operator.RefreshAggregate,
operator.RefreshLinkedAggregate,
operator.RemoveConnection,
operator.RemoveRepresentation,
operator.SelectConnection,
@@ -74,8 +74,8 @@ def register():
operator.OverrideDuplicateMoveMacro.define("TRANSFORM_OT_translate")
operator.OverrideDuplicateMoveLinkedMacro.define("BIM_OT_override_object_duplicate_move_linked")
operator.OverrideDuplicateMoveLinkedMacro.define("TRANSFORM_OT_translate")
operator.OverrideDuplicateMoveAggregateMacro.define("BIM_OT_override_object_duplicate_move_aggregate")
operator.OverrideDuplicateMoveAggregateMacro.define("TRANSFORM_OT_translate")
operator.DuplicateMoveLinkedAggregateMacro.define("BIM_OT_object_duplicate_move_linked_aggregate")
operator.DuplicateMoveLinkedAggregateMacro.define("TRANSFORM_OT_translate")
bpy.types.Object.BIMGeometryProperties = bpy.props.PointerProperty(type=prop.BIMObjectGeometryProperties)
bpy.types.Scene.BIMGeometryProperties = bpy.props.PointerProperty(type=prop.BIMGeometryProperties)
@@ -92,9 +92,7 @@ def register():
addon_keymaps.append((km, kmi))
kmi = km.keymap_items.new("bim.override_object_duplicate_move_linked_macro", "D", "PRESS", alt=True)
addon_keymaps.append((km, kmi))
kmi = km.keymap_items.new(
"bim.override_object_duplicate_move_aggregate_macro", "D", "PRESS", ctrl=True, shift=True
)
kmi = km.keymap_items.new("bim.object_duplicate_move_linked_aggregate_macro", "D", "PRESS", ctrl=True, shift=True)
addon_keymaps.append((km, kmi))
kmi = km.keymap_items.new("bim.override_paste_buffer", "V", "PRESS", ctrl=True)
addon_keymaps.append((km, kmi))
@@ -759,7 +759,8 @@ class OverrideDuplicateMove(bpy.types.Operator):
self.new_active_obj = None
# Track decompositions so they can be recreated after the operation
relationships = tool.Root.get_decomposition_relationships(objects_to_duplicate)
decomposition_relationships = tool.Root.get_decomposition_relationships(objects_to_duplicate)
connection_relationships = tool.Root.get_connection_relationships(objects_to_duplicate)
old_to_new = {}
for obj in objects_to_duplicate:
@@ -819,9 +820,19 @@ class OverrideDuplicateMove(bpy.types.Operator):
if new.is_a("IfcRelSpaceBoundary"):
tool.Boundary.decorate_boundary(new_obj)
# Recreate aggregate relationship
for old in old_to_new.keys():
if old.is_a("IfcElementAssembly"):
tool.Root.recreate_aggregate(old_to_new)
# Remove connections with old objects and recreates paths
OverrideDuplicateMove.remove_old_connections(old_to_new)
tool.Root.recreate_connections(connection_relationships, old_to_new)
# Recreate decompositions
tool.Root.recreate_decompositions(relationships, old_to_new)
tool.Root.recreate_decompositions(decomposition_relationships, old_to_new)
blenderbim.bim.handler.refresh_ui_data()
return old_to_new
@staticmethod
def process_arrays(self, context):
@@ -860,6 +871,18 @@ class OverrideDuplicateMove(bpy.types.Operator):
return arrays_to_create, array_children
def remove_old_connections(old_to_new):
for new in old_to_new.values():
for connection in new[0].ConnectedTo:
entity = connection.RelatedElement
if entity in old_to_new.keys():
core.remove_connection(tool.Geometry, connection=connection)
for connection in new[0].ConnectedFrom:
entity = connection.RelatingElement
if entity in old_to_new.keys():
core.remove_connection(tool.Geometry, connection=connection)
class OverrideDuplicateMoveLinkedMacro(bpy.types.Macro):
bl_idname = "bim.override_object_duplicate_move_linked_macro"
bl_label = "IFC Duplicate Linked"
@@ -882,15 +905,15 @@ class OverrideDuplicateMoveLinked(bpy.types.Operator):
return OverrideDuplicateMove.execute_ifc_duplicate_operator(self, context, linked=True)
class OverrideDuplicateMoveAggregateMacro(bpy.types.Macro):
bl_idname = "bim.override_object_duplicate_move_aggregate_macro"
bl_label = "IFC Duplicate Objects Aggregate"
class DuplicateMoveLinkedAggregateMacro(bpy.types.Macro):
bl_idname = "bim.object_duplicate_move_linked_aggregate_macro"
bl_label = "IFC Duplicate Linked Aggregate"
bl_options = {"REGISTER", "UNDO"}
class OverrideDuplicateMoveAggregate(bpy.types.Operator):
bl_idname = "bim.override_object_duplicate_move_aggregate"
bl_label = "IFC Duplicate Objects Aggregate"
class DuplicateMoveLinkedAggregate(bpy.types.Operator):
bl_idname = "bim.object_duplicate_move_linked_aggregate"
bl_label = "IFC Duplicate Linked Aggregate"
bl_options = {"REGISTER", "UNDO"}
is_interactive: bpy.props.BoolProperty(name="Is Interactive", default=True)
@@ -902,263 +925,103 @@ class OverrideDuplicateMoveAggregate(bpy.types.Operator):
return OverrideDuplicateMove.execute_duplicate_operator(self, context, linked=False)
def _execute(self, context):
return DuplicateMoveLinkedAggregate.execute_ifc_duplicate_linked_aggregate_operator(self, context)
@staticmethod
def execute_ifc_duplicate_linked_aggregate_operator(self, context):
self.new_active_obj = None
self.group_name = "BBIM_Linked_Aggregate"
self.pset_name = "BBIM_Linked_Aggregate"
old_to_new = {}
### Adding the assembly data
def add_assembly_data(element, parent, data_to_add):
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Aggregate_Data")
data = [data_to_add]
def select_objects_and_add_data(element):
add_linked_aggregate_group(element)
obj = tool.Ifc.get_object(element)
obj.select_set(True)
parts = ifcopenshell.util.element.get_parts(element)
if parts:
index = 0
for part in parts:
if part.is_a("IfcElementAssembly"):
select_objects_and_add_data(part)
if pset:
if parent == None:
ifcopenshell.api.run(
"pset.edit_pset",
tool.Ifc.get(),
pset=tool.Ifc.get().by_id(pset["id"]),
properties={"Data": tool.Ifc.get().createIfcText(json.dumps(data))},
)
else:
ifcopenshell.api.run(
"pset.edit_pset",
tool.Ifc.get(),
pset=tool.Ifc.get().by_id(pset["id"]),
properties={"Parent": parent.GlobalId, "Data": tool.Ifc.get().createIfcText(json.dumps(data))},
)
add_linked_aggregate_pset(part, index)
index += 1
obj = tool.Ifc.get_object(part)
obj.select_set(True)
else:
def add_linked_aggregate_group(element):
linked_aggregate_group = None
product_groups_name = [
r.RelatingGroup.Name
for r in getattr(element, "HasAssignments", []) or []
if r.is_a("IfcRelAssignsToGroup")
]
if self.group_name in product_groups_name:
return
linked_aggregate_group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), Name=self.group_name)
ifcopenshell.api.run(
"group.assign_group", tool.Ifc.get(), products=[element], group=linked_aggregate_group
)
def add_linked_aggregate_pset(part, index):
pset = ifcopenshell.util.element.get_pset(part, self.pset_name)
if not pset:
pset = ifcopenshell.api.run(
"pset.add_pset", tool.Ifc.get(), product=element, name="BBIM_Aggregate_Data"
"pset.add_pset", tool.Ifc.get(), product=part, name=self.pset_name
)
ifcopenshell.api.run(
"pset.edit_pset",
tool.Ifc.get(),
pset=pset,
properties={"Parent": parent.GlobalId, "Data": tool.Ifc.get().createIfcText(json.dumps(data))},
)
def add_child_to_assembly_data(new_entity):
pset = ifcopenshell.util.element.get_pset(new_entity, "BBIM_Aggregate_Data")
parent_element = tool.Ifc.get().by_guid(pset["Parent"])
parent_pset = ifcopenshell.util.element.get_pset(parent_element, "BBIM_Aggregate_Data")
data = json.loads(parent_pset["Data"])
data[0]["children"].append(new_entity.GlobalId)
else:
pset = tool.Ifc.get().by_id(pset["id"])
ifcopenshell.api.run(
"pset.edit_pset",
tool.Ifc.get(),
pset=tool.Ifc.get().by_id(parent_pset["id"]),
properties={"Data": tool.Ifc.get().createIfcText(json.dumps(data))},
pset=pset,
properties={"Index": index},
)
def create_data_structure(entity, level=-1):
level += 1
data_children = {
"children": [],
"instance_of": [],
}
data_parent = {
"children": [],
"instance_of": [entity.GlobalId],
}
if not entity.is_a("IfcElementAssembly"):
return
else:
if level == 0:
add_assembly_data(entity, entity, data_parent)
else:
add_assembly_data(entity, None, data_parent)
parts = ifcopenshell.util.element.get_parts(entity)
for part in parts:
if part.is_a("IfcElementAssembly"):
add_assembly_data(part, entity, data_children)
add_child_to_assembly_data(part)
create_data_structure(part, level)
continue
add_assembly_data(part, entity, data_children)
add_child_to_assembly_data(part)
return
def recreate_data_structure(entity, level=-1):
level += 1
pset = ifcopenshell.util.element.get_pset(entity, "BBIM_Aggregate_Data")
pset_data = json.loads(pset["Data"])[0]
instance_of = pset_data["instance_of"]
data_children = {
"children": [],
"instance_of": [],
}
# Keeps instance ID through all copies
data_parent = {
"children": [],
"instance_of": instance_of,
}
if not entity.is_a("IfcElementAssembly"):
return
else:
if level == 0:
add_assembly_data(entity, entity, data_parent)
else:
add_assembly_data(entity, None, data_parent)
parts = ifcopenshell.util.element.get_parts(entity)
for part in parts:
if part.is_a("IfcElementAssembly"):
add_child_to_assembly_data(part)
recreate_data_structure(part, level)
continue
add_assembly_data(part, entity, data_children)
add_child_to_assembly_data(part)
return
def duplicate_all(obj, level=-1, new_parent=None, parents=[]):
level += 1
entity = tool.Ifc.get_entity(obj)
if level == 0:
new_parent = duplicate_objects(obj)
parents.append(new_parent)
else:
pair = []
pair.append(new_parent)
new_parent = duplicate_objects(obj)
pair.append(new_parent)
parents.append(pair)
if not entity.is_a("IfcElementAssembly"):
return
else:
parts = ifcopenshell.util.element.get_parts(entity)
# Ensures that we are duplication all the IfcElementAssembly
# before duplicating the nested objects
for part in parts:
if part.is_a("IfcElementAssembly"):
pass
else:
part_obj = tool.Ifc.get_object(part)
new_part = duplicate_objects(part_obj)
blenderbim.core.aggregate.assign_object(
tool.Ifc,
tool.Aggregate,
tool.Collector,
relating_obj=tool.Ifc.get_object(new_parent),
related_obj=tool.Ifc.get_object(new_part),
)
for part in parts:
if part.is_a("IfcElementAssembly"):
part_obj = tool.Ifc.get_object(part)
# Recursion Call
duplicate_all(part_obj, level, new_parent, parents)
if level == 0:
for p in parents[1:]:
blenderbim.core.aggregate.assign_object(
tool.Ifc,
tool.Aggregate,
tool.Collector,
relating_obj=tool.Ifc.get_object(p[0]),
related_obj=tool.Ifc.get_object(p[1]),
)
return new_parent
return
def duplicate_objects(obj, is_root=False):
new_obj = obj.copy()
if obj.data:
new_obj.data = obj.data.copy()
if obj == context.active_object:
self.new_active_obj = new_obj
for collection in obj.users_collection:
collection.objects.link(new_obj)
obj.select_set(False)
new_obj.select_set(True)
# This is needed to make sure the new object gets unlink from
# the old object assembly collection
new_obj.BIMObjectProperties.collection = None
# Copy the actual class
new_entity = blenderbim.core.root.copy_class(
tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj
)
if new_entity:
tool.Model.handle_array_on_copied_element(new_entity)
blenderbim.core.aggregate.unassign_object(
tool.Ifc,
tool.Aggregate,
tool.Collector,
relating_obj=tool.Ifc.get_object(selected_root_entity),
related_obj=tool.Ifc.get_object(new_entity),
)
old_to_new[tool.Ifc.get_entity(obj)] = [new_entity]
return new_entity
### Check if only one element and it's assembly
return index
if len(context.selected_objects) != 1:
return {"FINISHED"}
selected_obj = context.selected_objects[0]
selected_root_entity = tool.Ifc.get_entity(selected_obj)
selected_element = tool.Ifc.get_entity(selected_obj)
if not selected_root_entity.is_a("IfcElementAssembly"):
if selected_element.is_a("IfcElementAssembly"):
pass
elif selected_element.Decomposes:
if selected_element.Decomposes[0].RelatingObject.is_a("IfcElementAssembly"):
selected_element = selected_element.Decomposes[0].RelatingObject
selected_obj = tool.Ifc.get_object(selected_element)
else:
self.report({"INFO"}, "Object is not part of a IfcElementAssembly.")
return {"FINISHED"}
pset = ifcopenshell.util.element.get_pset(selected_root_entity, "BBIM_Aggregate_Data")
if not pset:
create_data_structure(selected_root_entity)
pset = ifcopenshell.util.element.get_pset(selected_root_entity, "BBIM_Aggregate_Data")
select_objects_and_add_data(selected_element)
selected_root_parent = selected_root_entity
old_to_new = OverrideDuplicateMove.execute_ifc_duplicate_operator(self, context, linked=True)
new_root_entity = duplicate_all(selected_obj)
# Recreate aggregate relationship
for old in old_to_new.keys():
if old.is_a("IfcElementAssembly"):
tool.Root.recreate_aggregate(old_to_new)
recreate_data_structure(new_root_entity)
# Remove connections with old objects
for new in old_to_new.values():
for connection in new[0].ConnectedTo:
entity = connection.RelatedElement
if entity in old_to_new.keys():
core.remove_connection(tool.Geometry, connection=connection)
for connection in new[0].ConnectedFrom:
entity = connection.RelatingElement
if entity in old_to_new.keys():
core.remove_connection(tool.Geometry, connection=connection)
old_objs = []
for old, new in old_to_new.items():
old_objs.append(tool.Ifc.get_object(old))
relationships = tool.Root.get_decomposition_relationships(old_objs)
tool.Root.recreate_decompositions(relationships, old_to_new)
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
return old_to_new
class RefreshAggregate(bpy.types.Operator):
bl_idname = "bim.refresh_aggregate"
bl_label = "IFC Refresh Aggregate"
class RefreshLinkedAggregate(bpy.types.Operator):
bl_idname = "bim.refresh_linked_aggregate"
bl_label = "IFC Refresh Linked Aggregate"
bl_options = {"REGISTER", "UNDO"}
@classmethod
@@ -1174,214 +1037,132 @@ class RefreshAggregate(bpy.types.Operator):
return {"FINISHED"}
def _execute(self, context):
refresh_start_time = time()
self.new_active_obj = None
self.group_name = 'BBIM_Linked_Aggregate'
self.pset_name = "BBIM_Linked_Aggregate"
refresh_start_time = time()
old_to_new = {}
def remove_objects(entity, level=-1, parents=[]):
level += 1
if level == 0:
parents = [entity]
parts = ifcopenshell.util.element.get_parts(entity)
for part in parts:
if part.is_a("IfcElementAssembly"):
parents.append(part)
remove_objects(part, level, parents)
continue
else:
part_obj = tool.Ifc.get_object(part)
if part_obj:
tool.Geometry.delete_ifc_object(part_obj)
def delete_objects(element):
parts = ifcopenshell.util.element.get_parts(element)
if parts:
for part in parts:
if part.is_a("IfcElementAssembly"):
delete_objects(part)
tool.Geometry.delete_ifc_object(tool.Ifc.get_object(part))
tool.Geometry.delete_ifc_object(tool.Ifc.get_object(element))
return parents
def duplicate_children(entity):
pset = ifcopenshell.util.element.get_pset(entity, "BBIM_Aggregate_Data")
pset_data = json.loads(pset["Data"])[0]
instance_of = pset_data["instance_of"][0]
instance_entity = tool.Ifc.get().by_guid(instance_of)
if not instance_entity.is_a("IfcElementAssembly"):
return
def get_element_assembly(element):
if element.is_a("IfcElementAssembly"):
return element
elif element.Decomposes:
if element.Decomposes[0].RelatingObject.is_a("IfcElementAssembly"):
element = element.Decomposes[0].RelatingObject
return element
else:
parts = ifcopenshell.util.element.get_parts(instance_entity)
for part in parts:
pset = ifcopenshell.util.element.get_pset(part, "BBIM_Aggregate_Data")
if part.is_a("IfcElementAssembly"):
if not pset:
data_children = {
"children": [],
"instance_of": [part.GlobalId],
}
data = [data_children]
return None
pset = ifcopenshell.api.run(
"pset.add_pset", tool.Ifc.get(), product=part, name="BBIM_Aggregate_Data"
)
ifcopenshell.api.run(
"pset.edit_pset",
tool.Ifc.get(),
pset=pset,
properties={
"Parent": instance_entity.GlobalId,
"Data": tool.Ifc.get().createIfcText(json.dumps(data)),
},
)
def handle_selection(selected_objs):
selected_elements = [tool.Ifc.get_entity(selected_obj) for selected_obj in selected_objs]
if None in selected_elements:
self.report({"INFO"}, "Object has no Ifc Metadata.")
return None, None
part_obj = tool.Ifc.get_object(part)
new_part = duplicate_objects(part_obj)
selected_parents = []
for selected_element in selected_elements:
selected_element = get_element_assembly(selected_element)
if not selected_element:
self.report({"INFO"}, "Object is not part of a IfcElementAssembly.")
return None, None
selected_parents.append(selected_element)
selected_parents = list(set(selected_parents))
linked_aggregate_groups = []
for selected_element in selected_parents:
product_linked_agg_group = [
r.RelatingGroup
for r in getattr(selected_element, "HasAssignments", []) or []
if r.is_a("IfcRelAssignsToGroup")
if self.group_name in r.RelatingGroup.Name
]
try:
linked_aggregate_groups.append(product_linked_agg_group[0].id())
except:
self.report({"INFO"}, "Object is not part of a Linked Aggregate.")
return None, None
return list(set(linked_aggregate_groups)), selected_parents
active_element = tool.Ifc.get_entity(context.active_object)
if not active_element:
self.report({"INFO"}, "Object has no Ifc metadata.")
return {"FINISHED"}
active_element = get_element_assembly(active_element)
selected_objs = context.selected_objects
linked_aggregate_groups, selected_parents = handle_selection(selected_objs)
if not linked_aggregate_groups or not selected_parents:
return {"FINISHED"}
if len(linked_aggregate_groups) > 1:
if len(selected_parents) != len(linked_aggregate_groups):
self.report({"INFO"}, "Select only one object from each Linked Aggregate or multiple objects from the same Linked Aggregate.")
return {"FINISHED"}
for group in linked_aggregate_groups:
elements = tool.Drawing.get_group_elements(tool.Ifc.get().by_id(group))
if len(linked_aggregate_groups) > 1:
base_instance = [e for e in elements if e in selected_parents][0]
instances_to_refresh = elements
elif (len(linked_aggregate_groups) == 1) and (len(selected_parents) > 1):
base_instance = active_element
instances_to_refresh = [element for element in elements if element in selected_parents]
else:
base_instance = active_element
instances_to_refresh = elements
for element in instances_to_refresh:
if element.GlobalId == base_instance.GlobalId:
continue
element_aggregate = ifcopenshell.util.element.get_aggregate(element)
selected_obj = tool.Ifc.get_object(base_instance)
selected_matrix = selected_obj.matrix_world
object_duplicate = tool.Ifc.get_object(element)
duplicate_matrix = object_duplicate.matrix_world.decompose()
delete_objects(element)
for obj in context.selected_objects:
obj.select_set(False)
tool.Ifc.get_object(base_instance).select_set(True)
old_to_new = DuplicateMoveLinkedAggregate.execute_ifc_duplicate_linked_aggregate_operator(self, context)
for old, new in old_to_new.items():
new_obj = tool.Ifc.get_object(new[0])
new_base_matrix = Matrix.LocRotScale(*duplicate_matrix)
matrix_diff = Matrix.inverted(selected_matrix) @ new_obj.matrix_world
new_obj_matrix = new_base_matrix @ matrix_diff
new_obj.matrix_world = new_obj_matrix
if element_aggregate and new[0].is_a("IfcElementAssembly"):
new_aggregate = ifcopenshell.util.element.get_aggregate(new[0])
if not new_aggregate:
blenderbim.core.aggregate.assign_object(
tool.Ifc,
tool.Aggregate,
tool.Collector,
relating_obj=tool.Ifc.get_object(entity),
related_obj=tool.Ifc.get_object(new_part),
)
duplicate_children(new_part)
for part in parts:
pset = ifcopenshell.util.element.get_pset(part, "BBIM_Aggregate_Data")
if part.is_a("IfcElementAssembly"):
pass
else:
if not pset:
pset = ifcopenshell.api.run(
"pset.add_pset", tool.Ifc.get(), product=part, name="BBIM_Aggregate_Data"
)
else:
pset = ifcopenshell.util.element.get_pset(part, "BBIM_Aggregate_Data")
pset = tool.Ifc.get().by_id(pset["id"])
data_children = {
"children": [],
"instance_of": [],
}
data = [data_children]
ifcopenshell.api.run(
"pset.edit_pset",
tool.Ifc.get(),
pset=pset,
properties={
"Parent": instance_entity.GlobalId,
"Data": tool.Ifc.get().createIfcText(json.dumps(data)),
},
)
part_obj = tool.Ifc.get_object(part)
new_part = duplicate_objects(part_obj)
blenderbim.core.aggregate.assign_object(
tool.Ifc,
tool.Aggregate,
tool.Collector,
relating_obj=tool.Ifc.get_object(entity),
related_obj=tool.Ifc.get_object(new_part),
)
def duplicate_objects(obj, is_root=False):
new_obj = obj.copy()
if obj.data:
new_obj.data = obj.data.copy()
if obj == context.active_object:
self.new_active_obj = new_obj
for collection in obj.users_collection:
collection.objects.link(new_obj)
obj.select_set(False)
new_obj.select_set(True)
# This is needed to make sure the new object gets unlink from
# the old object assembly collection
new_obj.BIMObjectProperties.collection = None
# Copy the actual class
new_entity = blenderbim.core.root.copy_class(
tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj
)
if new_entity:
tool.Model.handle_array_on_copied_element(new_entity)
if not new_entity.is_a("IfcElementAssembly"):
blenderbim.core.aggregate.unassign_object(
tool.Ifc,
tool.Aggregate,
tool.Collector,
relating_obj=obj,
related_obj=tool.Ifc.get_object(new_entity),
)
old_to_new[tool.Ifc.get_entity(obj)] = [new_entity]
return new_entity
if len(context.selected_objects) != 1:
self.report({"INFO"}, "Only 1 object need to be selected.")
return {"FINISHED"}
selected_root_obj = context.selected_objects[0]
selected_root_entity = tool.Ifc.get_entity(selected_root_obj)
if selected_root_entity.is_a("IfcElementAssembly"):
pass
elif selected_root_entity.Decomposes:
if selected_root_entity.Decomposes[0].RelatingObject.is_a("IfcElementAssembly"):
selected_root_entity = selected_root_entity.Decomposes[0].RelatingObject
selected_root_obj = tool.Ifc.get_object(selected_root_entity)
else:
self.report({"INFO"}, "Object is not part of a IfcElementAssembly.")
return {"FINISHED"}
pset = ifcopenshell.util.element.get_pset(selected_root_entity, "BBIM_Aggregate_Data")
if not pset:
self.report({"INFO"}, "Object is not part of an assembly aggregate.")
return {"FINISHED"}
pset_data = json.loads(pset["Data"])[0]
instance_of = pset_data["instance_of"][0]
original_root_entity = tool.Ifc.get().by_guid(instance_of)
if original_root_entity == selected_root_entity:
self.report({"INFO"}, "Cannot refresh original assembly. Select an assembly instance.")
return {"FINISHED"}
parents = remove_objects(selected_root_entity)
original_root_object = tool.Ifc.get_object(original_root_entity)
selected_matrix = selected_root_obj.matrix_world
original_matrix = original_root_object.matrix_world
for parent in parents:
duplicate_children(parent)
# Remove connections with old objects
for new in old_to_new.values():
for connection in new[0].ConnectedTo:
entity = connection.RelatedElement
if entity in old_to_new.keys():
core.remove_connection(tool.Geometry, connection=connection)
for connection in new[0].ConnectedFrom:
entity = connection.RelatingElement
if entity in old_to_new.keys():
core.remove_connection(tool.Geometry, connection=connection)
old_objs = []
for old, new in old_to_new.items():
old_objs.append(tool.Ifc.get_object(old))
new_obj = tool.Ifc.get_object(new[0])
matrix_diff = Matrix.inverted(original_matrix) @ new_obj.matrix_world
new_matrix = selected_matrix @ matrix_diff
new_obj.matrix_world = new_matrix
relationships = tool.Root.get_decomposition_relationships(old_objs)
tool.Root.recreate_decompositions(relationships, old_to_new)
tool.Ifc,
tool.Aggregate,
tool.Collector,
relating_obj=tool.Ifc.get_object(element_aggregate),
related_obj=tool.Ifc.get_object(new[0]),
)
blenderbim.bim.handler.refresh_ui_data()
operator_time = time() - refresh_start_time
+74
View File
@@ -89,6 +89,26 @@ class Root(blenderbim.core.tool.Root):
relationships[element] = {"type": "fill", "element": building}
return relationships
@classmethod
def get_connection_relationships(cls, objs):
relationships = {}
for obj in objs:
element = tool.Ifc.get_entity(obj)
if not element:
continue
if hasattr(element, "ConnectedTo") and element.ConnectedTo:
paths = [connection for connection in element.ConnectedTo if connection.is_a("IfcRelConnectsPathElements")]
for path in paths:
relationships[element] = {"type": "path",
"related_connection_type": path.RelatedConnectionType,
"related_element": path.RelatedElement,
"related_priorities": path.RelatedPriorities,
"relating_connection_type": path.RelatingConnectionType,
"relating_element": path.RelatingElement,
"relating_priorities": path.RelatingPriorities,
}
return relationships
@classmethod
def get_element_representation(cls, element, context):
if context.is_a("IfcGeometricRepresentationSubContext"):
@@ -197,6 +217,60 @@ class Root(blenderbim.core.tool.Root):
should_sync_changes_first=False,
)
@classmethod
def recreate_connections(cls, relationship, old_to_new):
for element, data in relationship.items():
new_relating_element = old_to_new.get(data["relating_element"])[0]
new_related_element = old_to_new.get(data["related_element"])[0]
ifcopenshell.api.run(
"geometry.connect_path",
tool.Ifc.get(),
relating_element=new_relating_element,
related_element=new_related_element,
relating_connection=data["relating_connection_type"],
related_connection=data["related_connection_type"],
)
@classmethod
def recreate_aggregate(cls, old_to_new):
for old, new in old_to_new.items():
old_aggregate = ifcopenshell.util.element.get_aggregate(old)
if old_aggregate:
try:
new_aggregate = old_to_new[old_aggregate]
except:
blenderbim.core.aggregate.unassign_object(
tool.Ifc,
tool.Aggregate,
tool.Collector,
relating_obj=tool.Ifc.get_object(old_aggregate),
related_obj=tool.Ifc.get_object(new[0]),
)
continue
blenderbim.core.aggregate.assign_object(
tool.Ifc,
tool.Aggregate,
tool.Collector,
relating_obj=tool.Ifc.get_object(new_aggregate[0]),
related_obj=tool.Ifc.get_object(new[0]),
)
# Make sure that the array children also get reassigned to the correct aggregate
pset = ifcopenshell.util.element.get_pset(new[0], "BBIM_Array")
if pset:
array_children = tool.Blender.Modifier.Array.get_all_children_objects(new[0])
for obj in array_children:
blenderbim.core.aggregate.assign_object(
tool.Ifc,
tool.Aggregate,
tool.Collector,
relating_obj=tool.Ifc.get_object(new_aggregate[0]),
related_obj=tool.Ifc.get_object(tool.Ifc.get_entity(obj)),
)
@classmethod
def run_geometry_add_representation(
cls, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None
@@ -418,6 +418,83 @@ Scenario: Override duplicate move - copying a profiled extrusion
Then the object "IfcWall/Cube.001" exists
Then the object "IfcWall/Cube.001" has a "SweptSolid" representation of "Model/Body/MODEL_VIEW"
Scenario: Override duplicate move - copying an aggregate
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
When I press "bim.add_aggregate"
Then the object "IfcWall/Cube" is in the collection "IfcElementAssembly/Assembly"
And the object "IfcElementAssembly/Assembly" is in the collection "IfcElementAssembly/Assembly"
And the collection "IfcElementAssembly/Assembly" is in the collection "IfcBuildingStorey/My Storey"
When the object "IfcWall/Cube" is selected
And additionally the object "IfcElementAssembly/Assembly" is selected
When I duplicate the selected objects
Then the object "IfcWall/Cube.001" exists
And the object "IfcWall/Cube.001" is in the collection "IfcElementAssembly/Assembly.001"
And the object "IfcElementAssembly/Assembly.001" exists
And the object "IfcElementAssembly/Assembly.001" is in the collection "IfcElementAssembly/Assembly.001"
And the collection "IfcElementAssembly/Assembly.001" is in the collection "IfcBuildingStorey/My Storey"
Scenario: Override duplicate move - copying objects with connection
Given an empty IFC project
And I load the demo construction library
And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcWallType') if e.Name == 'WAL100'][0].id()"
And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}"
When I press "bim.add_constr_type_instance"
Then the object "IfcWall/Wall" is an "IfcWall"
And the object "IfcWall/Wall" dimensions are "1,0.1,3"
And the object "IfcWall/Wall" bottom left corner is at "0,0,0"
When I set "scene.BIMModelProperties.ifc_class" to "IfcSlabType"
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcSlabType') if e.Name == 'FLR150'][0].id()"
And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}"
When I press "bim.add_constr_type_instance"
Then the object "IfcSlab/Slab" is an "IfcSlab"
When the object "IfcSlab/Slab" is selected
And the object "IfcSlab/Slab" is moved to "0,0,4"
When I deselect all objects
And the object "IfcWall/Wall" is selected
And additionally the object "IfcSlab/Slab" is selected
When I press "bim.hotkey(hotkey='S_E')"
Then the object "IfcWall/Wall" dimensions are "1,0.1,4"
When I duplicate the selected objects
Then the object "IfcWall/Wall.001" exists
And the variable "wall_name" is "[o.name for o in bpy.context.selected_objects if o.name == 'IfcWall/Wall.001'][0]"
Then the object "IfcSlab/Slab.001" exists
And the variable "slab_name" is "[o.name for o in bpy.context.selected_objects if o.name == 'IfcSlab/Slab.001'][0]"
Then the object "{wall_name}" has a connection with "{slab_name}"
Scenario: Override duplicate move - copying walls with mitre joint
Given an empty IFC project
And I load the demo construction library
And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcWallType') if e.Name == 'WAL100'][0].id()"
And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}"
And I press "bim.hotkey(hotkey='S_A')"
And the cursor is at "0.5,0,0"
And I press "bim.hotkey(hotkey='S_A')"
And the object "IfcWall/Wall" is selected
And additionally the object "IfcWall/Wall.001" is selected
When I press "bim.hotkey(hotkey='S_Y')"
Then the object "IfcWall/Wall.001" dimensions are "0.5,0.1,3"
And the object "IfcWall/Wall.001" bottom left corner is at "0.5,0,0"
And the object "IfcWall/Wall" dimensions are "1.1,0.1,3"
And the object "IfcWall/Wall" bottom left corner is at "0.5,0.1,0"
And the object "IfcWall/Wall" top right corner is at "0.6,-1,3"
When I deselect all objects
And the object "IfcWall/Wall" is selected
And additionally the object "IfcWall/Wall.001" is selected
When I duplicate the selected objects
Then the object "IfcWall/Wall.002" exists
And the variable "wall_name1" is "[o.name for o in bpy.context.selected_objects if o.name == 'IfcWall/Wall.002'][0]"
Then the object "IfcWall/Wall.003" exists
And the variable "wall_name2" is "[o.name for o in bpy.context.selected_objects if o.name == 'IfcWall/Wall.003'][0]"
Then the object "{wall_name1}" has a connection with "{wall_name2}"
Scenario: Override duplicate move linked - without active IFC data
Given an empty Blender session
And I add a cube
+25
View File
@@ -874,3 +874,28 @@ def run_pdb():
import pdb
pdb.set_trace()
@then(parsers.parse('the object "{obj_name1}" has a connection with "{obj_name2}"'))
def the_obj1_has_a_connection_with_obj2(obj_name1, obj_name2):
element1 = replace_variables(obj_name1)
element1 = tool.Ifc.get_entity(the_object_name_exists(element1))
element2 = replace_variables(obj_name2)
element2 = tool.Ifc.get_entity(the_object_name_exists(element2))
connections = []
if hasattr(element1, "ConnectedTo") and element1.ConnectedTo:
connections = [connection for connection in element1.ConnectedTo]
elif hasattr(element1, "ConnectedFrom") and element1.ConnectedFrom:
connections = [connection for connection in element1.ConnectedFrom]
else:
assert False, f'Object "{obj_name1}" has no connections'
relationships = {}
for conn in connections:
relationships[conn.RelatedElement] = conn.RelatingElement
for key, value in relationships.items():
print(key, value)
# assert False, f"1-{key} and {element1.id()} and {key.id()}"
assert (key.id() == element1.id() and value.id() == element2.id()) or (key.id() == element2.id() and value.id() == element1.id()), f"The object {obj_name1} is connected to {obj_name2}"