See #6537. Improve how aggregate mode behaves when dealing with multiple levels of aggregates.

This commit is contained in:
Bruno Perdigão
2025-04-16 20:23:18 -03:00
parent 9994fec571
commit c9fc543e91
5 changed files with 73 additions and 18 deletions
@@ -386,7 +386,7 @@ class BIM_OT_disable_aggregate_mode(bpy.types.Operator):
def execute(self, context):
bpy.ops.object.select_all(action="DESELECT")
bonsai.core.aggregate.disable_aggregate_mode(tool.Aggregate)
bonsai.core.aggregate.exit_aggregate_mode(tool.Aggregate)
return {"FINISHED"}
@@ -119,6 +119,7 @@ class Objects(bpy.types.PropertyGroup):
class BIMAggregateProperties(PropertyGroup):
in_aggregate_mode: BoolProperty(name="In Edit Mode", update=update_aggregate_mode_decorator)
editing_aggregate: PointerProperty(name="Editing Aggregate", type=bpy.types.Object)
previous_editing_aggregate: PointerProperty(name="Editing Aggregate", type=bpy.types.Object)
editing_objects: CollectionProperty(type=Objects)
not_editing_objects: CollectionProperty(type=Objects)
aggregate_decorator: BoolProperty(
@@ -126,10 +127,16 @@ class BIMAggregateProperties(PropertyGroup):
default=False,
update=update_aggregate_decorator,
)
previous_state: BoolProperty(
name="True if it was previously in aggregate mode",
default=False,
)
if TYPE_CHECKING:
in_aggregate_mode: bool
editing_aggregate: Union[bpy.types.Object, None]
previous_editing_aggregate: Union[bpy.types.Object, None]
editing_objects: bpy.types.bpy_prop_collection_idprop[Objects]
not_editing_objects: bpy.types.bpy_prop_collection_idprop[Objects]
aggregate_decorator: bool
previous_state: bool
@@ -1957,9 +1957,8 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator):
nprops = tool.Nest.get_nest_props()
if self.has_aggregates(selected_objs):
if not aprops.in_aggregate_mode:
bonsai.core.aggregate.enable_aggregate_mode(tool.Aggregate, context.active_object)
return {"FINISHED"}
bonsai.core.aggregate.enter_aggregate_mode(tool.Aggregate, context.active_object)
return {"FINISHED"}
if self.has_nests(selected_objs):
if not nprops.in_nest_mode:
@@ -1970,7 +1969,11 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator):
self.handle_single_object(context, context.active_object)
elif len(selected_objs) == 0:
if aprops.in_aggregate_mode:
bonsai.core.aggregate.disable_aggregate_mode(tool.Aggregate)
gprops = tool.Geometry.get_geometry_props()
if gprops.representation_obj:
tool.Geometry.disable_item_mode()
else:
bonsai.core.aggregate.exit_aggregate_mode(tool.Aggregate)
return {"FINISHED"}
if nprops.in_nest_mode:
bonsai.core.nest.disable_nest_mode(tool.Nest)
@@ -2105,6 +2108,10 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator):
props = tool.Aggregate.get_aggregate_props()
if (aggregate or parts) and not props.in_aggregate_mode:
return True
elif element != tool.Ifc.get_entity(props.editing_aggregate) and aggregate != tool.Ifc.get_entity(props.editing_aggregate):
return True
elif parts and aggregate == tool.Ifc.get_entity(props.editing_aggregate):
return True
else:
return False
@@ -3291,10 +3298,12 @@ class OverrideMove(bpy.types.Operator):
parts = ifcopenshell.util.element.get_parts(element)
if parts:
aggregates_to_move.append(tool.Ifc.get_object(element))
continue
if not parts and props.in_aggregate_mode:
aggregates_to_move.extend(list(tool.Aggregate.get_parts_recursively(element)))
continue
aggregate = ifcopenshell.util.element.get_aggregate(element)
if not parts and props.in_aggregate_mode and aggregate == tool.Ifc.get_entity(props.editing_aggregate):
continue
if aggregate:
aggregates_to_move.append(tool.Ifc.get_object(aggregate))
obj.select_set(False)
+11 -5
View File
@@ -87,18 +87,24 @@ def add_part_to_object(
blender.set_active_object(obj)
def enable_aggregate_mode(
def enter_aggregate_mode(
aggregator: tool.Aggregate,
obj: bpy.types.Object,
):
if aggregator.get_aggregate_mode():
disable_aggregate_mode(aggregator)
aggregator.update_previous_aggregate_mode_state()
if aggregator.get_higher_aggregate():
aggregator.disable_aggregate_mode()
aggregator.enable_aggregate_mode(obj)
def disable_aggregate_mode(aggregator: tool.Aggregate):
aggregator.disable_aggregate_mode()
def exit_aggregate_mode(aggregator: tool.Aggregate):
aggregator.update_previous_aggregate_mode_state()
if new_obj := aggregator.get_higher_aggregate():
aggregator.disable_aggregate_mode()
aggregator.enable_aggregate_mode(new_obj)
else:
aggregator.disable_aggregate_mode()
class IncompatibleAggregateError(Exception):
+39 -6
View File
@@ -88,6 +88,20 @@ class Aggregate(bonsai.core.tool.Aggregate):
if rel.is_a("IfcRelAggregates"):
return rel.RelatingObject
@classmethod
def get_aggregates_recursively(cls, element: ifcopenshell.entity_instance) -> set[ifcopenshell.entity_instance]:
"""Get elements aggregates recursively, resulting set includes `element`."""
aggregates = list()
queue = {element}
while queue:
element = queue.pop()
aggregate = ifcopenshell.util.element.get_aggregate(element)
if aggregate:
queue.update({aggregate})
if ifcopenshell.util.element.get_parts(element):
aggregates.append(element)
return aggregates
@classmethod
def get_parts_recursively(cls, element: ifcopenshell.entity_instance) -> set[ifcopenshell.entity_instance]:
"""Get elements parts recursively, resulting set includes `element`."""
@@ -100,9 +114,17 @@ class Aggregate(bonsai.core.tool.Aggregate):
return parts
@classmethod
def get_aggregate_mode(cls) -> bool:
def get_higher_aggregate(cls) -> ifcopenshell.entity_instance:
props = cls.get_aggregate_props()
return props.in_aggregate_mode
editing_aggregate = tool.Ifc.get_entity(props.editing_aggregate)
higher_aggregate = ifcopenshell.util.element.get_aggregate(editing_aggregate)
return tool.Ifc.get_object(higher_aggregate) if higher_aggregate else None
@classmethod
def update_previous_aggregate_mode_state(cls):
props = cls.get_aggregate_props()
props.previous_state = props.in_aggregate_mode
props.previous_editing_aggregate = props.editing_aggregate
@classmethod
def enable_aggregate_mode(cls, active_object: bpy.types.Object) -> set[Literal["FINISHED"]]:
@@ -112,12 +134,24 @@ class Aggregate(bonsai.core.tool.Aggregate):
element = tool.Ifc.get_entity(active_object)
if not element:
return {"FINISHED"}
aggregate = ifcopenshell.util.element.get_aggregate(element)
parts = ifcopenshell.util.element.get_parts(element)
# Defines the aggregate based on previous state
# Controls whether the user is entering a deeper level or
# exiting to a higher level. See core/aggregate.py
aggregates = tool.Aggregate.get_aggregates_recursively(element)
aggregate = aggregates[-1]
if props.previous_state:
previous_aggregate = tool.Ifc.get_entity(props.previous_editing_aggregate)
if previous_aggregate in aggregates:
reference_index = aggregates.index(previous_aggregate)
aggregate = aggregates[reference_index - 1]
else:
aggregate = aggregates[0]
parts = tool.Aggregate.get_parts_recursively(element)
if not aggregate and not parts:
return {"FINISHED"}
if not parts:
parts = ifcopenshell.util.element.get_parts(aggregate)
parts = tool.Aggregate.get_parts_recursively(aggregate)
if parts:
props.editing_aggregate = tool.Ifc.get_object(aggregate) if aggregate else tool.Ifc.get_object(element)
parts_objs = [tool.Ifc.get_object(part) for part in parts]
@@ -157,7 +191,6 @@ class Aggregate(bonsai.core.tool.Aggregate):
if not element:
continue
parts = ifcopenshell.util.element.get_parts(tool.Ifc.get_entity(props.editing_aggregate))
if context.space_data.local_view:
bpy.ops.view3d.localview()