From 09f6991565f0073878b584b01d0a44c9a58db800 Mon Sep 17 00:00:00 2001 From: Andrej Date: Fri, 6 Jun 2025 18:36:43 +0500 Subject: [PATCH] cost items ui - update current index on item deletion To make it less awkward to remove multiple items starting from the last. Before - https://files.catbox.moe/wce56c.mp4 After - https://files.catbox.moe/wv1jhs.mp4 --- src/bonsai/bonsai/bim/module/classification/data.py | 5 ++++- src/bonsai/bonsai/bim/module/cost/prop.py | 6 +++++- src/bonsai/bonsai/bim/module/profile/operator.py | 3 +-- src/bonsai/bonsai/tool/blender.py | 10 +++++++++- src/bonsai/bonsai/tool/cost.py | 4 ++++ src/bonsai/bonsai/tool/spatial.py | 2 +- 6 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/classification/data.py b/src/bonsai/bonsai/bim/module/classification/data.py index aca0b3b1cb..d47c64881e 100644 --- a/src/bonsai/bonsai/bim/module/classification/data.py +++ b/src/bonsai/bonsai/bim/module/classification/data.py @@ -161,7 +161,10 @@ class CostClassificationsData(ReferencesData): def references(cls) -> list[dict[str, Any]]: results: list[dict[str, Any]] = [] props = tool.Cost.get_cost_props() - element = tool.Ifc.get().by_id(props.cost_items[props.active_cost_item_index].ifc_definition_id) + active_cost_item = props.active_cost_item + if not active_cost_item: + return results + element = tool.Ifc.get().by_id(active_cost_item.ifc_definition_id) if element: for reference in ifcopenshell.util.classification.get_references(element): data = reference.get_info() diff --git a/src/bonsai/bonsai/bim/module/cost/prop.py b/src/bonsai/bonsai/bim/module/cost/prop.py index f232c9fc1a..67be31c303 100644 --- a/src/bonsai/bonsai/bim/module/cost/prop.py +++ b/src/bonsai/bonsai/bim/module/cost/prop.py @@ -34,7 +34,7 @@ from bpy.props import ( FloatVectorProperty, CollectionProperty, ) -from typing import TYPE_CHECKING, Literal +from typing import TYPE_CHECKING, Literal, Union def get_schedule_of_rates(self: "BIMCostProperties", context: bpy.types.Context) -> tool.Blender.BLENDER_ENUM_ITEMS: @@ -345,3 +345,7 @@ class BIMCostProperties(PropertyGroup): currency: str custom_currency: str cost_schedule_files: bpy.types.bpy_prop_collection_idprop[CostItemsMapping] + + @property + def active_cost_item(self) -> Union[CostItem, None]: + return tool.Blender.get_active_uilist_element(self.cost_items, self.active_cost_item_index) diff --git a/src/bonsai/bonsai/bim/module/profile/operator.py b/src/bonsai/bonsai/bim/module/profile/operator.py index 3f5d3411bd..d7f91eaf60 100644 --- a/src/bonsai/bonsai/bim/module/profile/operator.py +++ b/src/bonsai/bonsai/bim/module/profile/operator.py @@ -106,8 +106,7 @@ class RemoveProfileDef(bpy.types.Operator, tool.Ifc.Operator): bpy.ops.bim.load_profiles() # preserve selected index if possible - if props.profiles: - props.active_profile_index = min(current_index, len(props.profiles) - 1) + props.active_profile_index = tool.Blender.get_valid_uilist_index(props.active_profile_index, props.profiles) class EnableEditingProfile(bpy.types.Operator): diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py index 9c10522cca..f9785c06a5 100644 --- a/src/bonsai/bonsai/tool/blender.py +++ b/src/bonsai/bonsai/tool/blender.py @@ -42,7 +42,7 @@ from mathutils import Vector from pathlib import Path from functools import lru_cache, cache from bonsai.bim.ifc import IFC_CONNECTED_TYPE -from typing import Any, Optional, Union, Literal, Iterable, Callable, TypeVar, Generator, TYPE_CHECKING, Sequence +from typing import Any, Optional, Union, Literal, Iterable, Callable, TypeVar, Generator, TYPE_CHECKING, Sequence, Sized from typing_extensions import assert_never if TYPE_CHECKING: @@ -1692,6 +1692,14 @@ class Blender(bonsai.core.tool.Blender): return collection[index] return None + @classmethod + def get_valid_uilist_index(cls, current_index: int, items: Sized) -> int: + """ + Method to help maintaining item selection after some uilist item was removed + and items were reloaded. + """ + return max(0, min(current_index, len(items) - 1)) + @classmethod def clear_undo_history(cls) -> None: """Clears the Blender history, Bonsai history, and IfcOpenShell history""" diff --git a/src/bonsai/bonsai/tool/cost.py b/src/bonsai/bonsai/tool/cost.py index a41f57b24d..566e5ece71 100644 --- a/src/bonsai/bonsai/tool/cost.py +++ b/src/bonsai/bonsai/tool/cost.py @@ -168,6 +168,9 @@ class Cost(bonsai.core.tool.Cost): for rel in cost_schedule.Controls or [] for cost_item in rel.RelatedObjects or [] ] + props.active_cost_item_index = tool.Blender.get_valid_uilist_index( + props.active_cost_item_index, props.cost_items + ) props.is_cost_update_enabled = True @classmethod @@ -208,6 +211,7 @@ class Cost(bonsai.core.tool.Cost): @classmethod def clean_up_cost_item_tree(cls, cost_item_id: int) -> None: + """Clean up cost item tree after ``cost_item_id`` was deleted.""" props = cls.get_cost_props() if not hasattr(cls, "contracted_cost_items"): cls.contracted_cost_items = json.loads(props.contracted_cost_items) diff --git a/src/bonsai/bonsai/tool/spatial.py b/src/bonsai/bonsai/tool/spatial.py index 8c77a2ec3e..8ed7e5097b 100644 --- a/src/bonsai/bonsai/tool/spatial.py +++ b/src/bonsai/bonsai/tool/spatial.py @@ -475,7 +475,7 @@ class Spatial(bonsai.core.tool.Spatial): props.containers.clear() cls.contracted_containers = json.loads(props.contracted_containers) cls.import_spatial_element(tool.Ifc.get().by_type("IfcProject")[0], 0) - props.active_container_index = min(previous_container_index, len(props.containers) - 1) + props.active_container_index = tool.Blender.get_valid_uilist_index(previous_container_index, props.containers) @classmethod def import_spatial_element(cls, element: ifcopenshell.entity_instance, level_index: int) -> None: