From 679fa6d8769fb2595d64482d85b51eafba9f17b9 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Tue, 9 Sep 2025 10:09:56 +0500 Subject: [PATCH] Option to change text symbol from general text editing UI Example - https://files.catbox.moe/kxyepa.mp4 --- .../bonsai/bim/module/drawing/decoration.py | 3 +- src/bonsai/bonsai/bim/module/drawing/prop.py | 32 +++++++++++++++++ src/bonsai/bonsai/bim/module/drawing/ui.py | 6 ++++ src/bonsai/bonsai/core/drawing.py | 2 +- src/bonsai/bonsai/core/tool.py | 2 +- src/bonsai/bonsai/tool/blender.py | 5 +++ src/bonsai/bonsai/tool/drawing.py | 34 ++++++++++++++++--- src/bonsai/test/core/test_drawing.py | 2 +- 8 files changed, 77 insertions(+), 9 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/drawing/decoration.py b/src/bonsai/bonsai/bim/module/drawing/decoration.py index 3a203ba468..4273feba9d 100644 --- a/src/bonsai/bonsai/bim/module/drawing/decoration.py +++ b/src/bonsai/bonsai/bim/module/drawing/decoration.py @@ -601,8 +601,7 @@ class BaseDecorator: props = tool.Drawing.get_text_props(obj) text_data = DecoratorData.data["text"].get(obj.name, None) if props.is_editing: - # Still use original `text_data`, because of the "Symbol". - text_data = text_data | props.get_text_edited_data() + text_data = props.get_text_edited_data() literals_data = text_data["Literals"] symbol = text_data["Symbol"] newline_at = text_data["Newline_At"] diff --git a/src/bonsai/bonsai/bim/module/drawing/prop.py b/src/bonsai/bonsai/bim/module/drawing/prop.py index 3a2a4ac4ca..3282a6ee55 100644 --- a/src/bonsai/bonsai/bim/module/drawing/prop.py +++ b/src/bonsai/bonsai/bim/module/drawing/prop.py @@ -738,12 +738,43 @@ class BIMTextProperties(PropertyGroup): name="Font Size", ) newline_at: IntProperty(name="Newline At") + symbol: EnumProperty( # pyright: ignore[reportRedeclaration] + name="Symbol", + description="Symbol from symbols.svg to use for this text.", + items=[(s, s, "") for s in ["NO SYMBOL", "CUSTOM SYMBOL"] + tool.Drawing.DEFAULT_SYMBOLS], + default="NO SYMBOL", + ) + custom_symbol: StringProperty( # pyright: ignore[reportRedeclaration] + name="Custom Symbol", + description="Non-default symbol to use for this text.", + ) if TYPE_CHECKING: is_editing: bool literals: bpy.types.bpy_prop_collection_idprop[LiteralProps] font_size: str newline_at: int + symbol: Union[str, Literal["NO SYMBOL", "CUSTOM SYMBOL"]] + custom_symbol: str + + def get_symbol(self) -> Union[str, None]: + if self.symbol == "NO SYMBOL": + return None + elif self.symbol == "CUSTOM SYMBOL": + return self.custom_symbol or None + else: + return self.symbol + + def set_symbol(self, symbol: Union[str, None]): + if not symbol: + self.property_unset("symbol") + self.property_unset("custom_symbol") + elif symbol in tool.Drawing.DEFAULT_SYMBOLS: + self.symbol = symbol + self.property_unset("custom_symbol") + else: + self.symbol = "CUSTOM SYMBOL" + self.custom_symbol = symbol def get_text_edited_data(self) -> dict[str, Any]: """should be called only if `is_editing` @@ -758,6 +789,7 @@ class BIMTextProperties(PropertyGroup): "Literals": literals_data, "FontSize": float(self.font_size), "Newline_At": int(self.newline_at), + "Symbol": self.get_symbol(), } return text_data diff --git a/src/bonsai/bonsai/bim/module/drawing/ui.py b/src/bonsai/bonsai/bim/module/drawing/ui.py index 02a2bc589b..8fb0c5743e 100644 --- a/src/bonsai/bonsai/bim/module/drawing/ui.py +++ b/src/bonsai/bonsai/bim/module/drawing/ui.py @@ -591,6 +591,12 @@ class BIM_PT_text(Panel): row = self.layout.row(align=True) row.prop(props, "newline_at") + row = self.layout.row(align=True) + row.prop(props, "symbol") + if props.symbol == "CUSTOM SYMBOL": + row = self.layout.row(align=True) + row.prop(props, "custom_symbol", text="") + for i, literal_props in enumerate(props.literals): box = self.layout.box() row = self.layout.row(align=True) diff --git a/src/bonsai/bonsai/core/drawing.py b/src/bonsai/bonsai/core/drawing.py index 6fa9770176..fd3f679270 100644 --- a/src/bonsai/bonsai/core/drawing.py +++ b/src/bonsai/bonsai/core/drawing.py @@ -39,7 +39,7 @@ def disable_editing_text(drawing: type[tool.Drawing], obj: bpy.types.Object) -> def edit_text(drawing: type[tool.Drawing], obj: bpy.types.Object) -> None: drawing.synchronise_ifc_and_text_attributes(obj) drawing.update_text_size_pset(obj) - drawing.update_newline_at(obj) + drawing.update_newline_at_and_symbol(obj) drawing.disable_editing_text(obj) diff --git a/src/bonsai/bonsai/core/tool.py b/src/bonsai/bonsai/core/tool.py index a8d0835462..f3fdc109cd 100644 --- a/src/bonsai/bonsai/core/tool.py +++ b/src/bonsai/bonsai/core/tool.py @@ -394,7 +394,7 @@ class Drawing: def sync_object_placement(cls, obj): pass def synchronise_ifc_and_text_attributes(cls, obj): pass def update_embedded_svg_location(cls, uri, old_location, new_location): pass - def update_newline_at(cls, obj): pass + def update_newline_at_and_symbol(cls, obj): pass def update_text_size_pset(cls, obj): pass diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py index c225d00d9b..2833467a0a 100644 --- a/src/bonsai/bonsai/tool/blender.py +++ b/src/bonsai/bonsai/tool/blender.py @@ -1581,6 +1581,11 @@ class Blender(bonsai.core.tool.Blender): return repr(bpy_prop) return repr(bpy_struct) + @classmethod + def get_props_attribute_name(cls, props: bpy.types.PropertyGroup) -> str: + """E.g. `bpy.data.objects['IfcAnnotation/TEXT'].BIMTextProperties` -> `BIMTextProperties`""" + return repr(props).rpartition(".")[-1] + @classmethod def resolve_data_path_to_data_attr(cls, data_path: str) -> tuple[bpy.types.bpy_struct, str]: """ diff --git a/src/bonsai/bonsai/tool/drawing.py b/src/bonsai/bonsai/tool/drawing.py index 8ce3349791..957ee2af37 100644 --- a/src/bonsai/bonsai/tool/drawing.py +++ b/src/bonsai/bonsai/tool/drawing.py @@ -110,6 +110,27 @@ class Drawing(bonsai.core.tool.Drawing): } # fmt: on + DEFAULT_SYMBOLS = [ + "rectangle-tag", + "triangle-tag", + "hexagon-tag", + "capsule-tag", + "circle-tag", + "door-tag", + "window-tag", + "space-tag", + "elevation-arrow", + "elevation-tag", + "section-arrow", + "section-tag", + "dot", + "setout-tag", + "setout-point", + "control-point", + "traverse-point", + "spot-elevation", + ] + @classmethod def get_document_props(cls) -> DocProperties: assert (scene := bpy.context.scene) @@ -434,8 +455,7 @@ class Drawing(bonsai.core.tool.Drawing): @classmethod def disable_editing_text(cls, obj: bpy.types.Object) -> None: props = tool.Drawing.get_text_props(obj) - props.is_editing = False - props.literals.clear() + obj.property_unset(tool.Blender.get_props_attribute_name(props)) @classmethod def disable_editing_assigned_product(cls, obj: bpy.types.Object) -> None: @@ -1077,6 +1097,7 @@ class Drawing(bonsai.core.tool.Drawing): text_data = DecoratorData.get_text_data(obj) props.font_size = str(text_data["FontSize"]) props.newline_at = text_data["Newline_At"] + props.set_symbol(text_data["Symbol"]) @classmethod def import_assigned_product(cls, obj: bpy.types.Object) -> None: @@ -1214,10 +1235,12 @@ class Drawing(bonsai.core.tool.Drawing): ) @classmethod - def update_newline_at(cls, obj: bpy.types.Object) -> None: + def update_newline_at_and_symbol(cls, obj: bpy.types.Object) -> None: props = cls.get_text_props(obj) element = tool.Ifc.get_entity(obj) + assert element newline_at = int(props.newline_at) + symbol = props.get_symbol() ifc_file = tool.Ifc.get() pset = tool.Pset.get_element_pset(element, "EPset_Annotation") if not pset: @@ -1225,7 +1248,10 @@ class Drawing(bonsai.core.tool.Drawing): ifcopenshell.api.pset.edit_pset( ifc_file, pset=pset, - properties={"Newline_At": newline_at}, + properties={ + "Newline_At": newline_at, + "Symbol": symbol, + }, ) # TODO below this point is highly experimental prototype code with no tests diff --git a/src/bonsai/test/core/test_drawing.py b/src/bonsai/test/core/test_drawing.py index ba5cf06e7d..875efba93c 100644 --- a/src/bonsai/test/core/test_drawing.py +++ b/src/bonsai/test/core/test_drawing.py @@ -37,7 +37,7 @@ class TestEditText: def test_run(self, drawing): drawing.synchronise_ifc_and_text_attributes("obj").should_be_called() drawing.update_text_size_pset("obj").should_be_called() - drawing.update_newline_at("obj").should_be_called() + drawing.update_newline_at_and_symbol("obj").should_be_called() drawing.disable_editing_text("obj").should_be_called() subject.edit_text(drawing, obj="obj")