mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 09:48:32 +00:00
Merge pull request #3969 from brunoperdigao/refactor-duplicate-aggregate
Refactor duplicate aggregate
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user