Supersede 3x3 box alignment with more familiar horizontal / vertical UI

* Fix #7712 - global alignment controls now affects all literals
 * Fix #7760 - goodbye 3x3 box alignment

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dion Moult
2026-03-15 08:22:53 +11:00
parent 6e0f24105e
commit 273ecfe8e4
5 changed files with 61 additions and 84 deletions
@@ -3305,9 +3305,8 @@ class AddTextLiteral(bpy.types.Operator):
attr.data_type = "string" attr.data_type = "string"
attr.string_value = literal_attr_values[attr_name] attr.string_value = literal_attr_values[attr_name]
box_alignment_mask = [False] * 9 literal_props.align_vertical = "bottom"
box_alignment_mask[6] = True # bottom_left box_alignment literal_props.align_horizontal = "left"
literal_props.box_alignment = box_alignment_mask
return {"FINISHED"} return {"FINISHED"}
@@ -4178,10 +4177,7 @@ class SelectSimilarTextLiteralValue(bpy.types.Operator):
should_select = True should_select = True
break break
elif self.attribute_type == "box_alignment": elif self.attribute_type == "box_alignment":
box_alignment_attr = next( if literal.get_box_alignment() == self.literal_value:
(attr for attr in literal.attributes if attr.name == "BoxAlignment"), None
)
if box_alignment_attr and box_alignment_attr.string_value == self.literal_value:
should_select = True should_select = True
break break
+35 -42
View File
@@ -673,20 +673,6 @@ class BIMCameraProperties(PropertyGroup):
return ortho_scale, aspect_ratio return ortho_scale, aspect_ratio
DEFAULT_BOX_ALIGNMENT = [False] * 6 + [True] + [False] * 2
BOX_ALIGNMENT_POSITIONS = [
"top-left",
"top-middle",
"top-right",
"middle-left",
"center",
"middle-right",
"bottom-left",
"bottom-middle",
"bottom-right",
]
class ElementValueRow(PropertyGroup): class ElementValueRow(PropertyGroup):
"""Represents a single element value row with category, key, and formatted value""" """Represents a single element value row with category, key, and formatted value"""
@@ -789,40 +775,38 @@ def get_category_items_with_counts(self, context):
class LiteralProps(PropertyGroup): class LiteralProps(PropertyGroup):
def set_box_alignment(self, new_value):
markers = new_value.count(True)
if not markers:
return
if markers > 1:
prev_value = self.get("box_alignment", DEFAULT_BOX_ALIGNMENT)
# looking for the first value changed to positive
first_changed_value = next((i for i in range(9) if new_value[i] and new_value[i] != prev_value[i]), None)
# if nothing have changed we just keep the previous value
if first_changed_value is None:
return
new_value = [False] * 9
new_value[first_changed_value] = True
self["box_alignment"] = new_value
position_string = BOX_ALIGNMENT_POSITIONS[next(i for i in range(9) if new_value[i])]
self.attributes["BoxAlignment"].set_value(position_string)
def get_box_alignment(self):
return self.get("box_alignment", DEFAULT_BOX_ALIGNMENT)
attributes: CollectionProperty(name="Attributes", type=Attribute) attributes: CollectionProperty(name="Attributes", type=Attribute)
box_alignment: BoolVectorProperty(
name="Box alignment", size=9, set=set_box_alignment, get=get_box_alignment, default=DEFAULT_BOX_ALIGNMENT
)
ifc_definition_id: IntProperty(name="IFC definition ID", default=0) ifc_definition_id: IntProperty(name="IFC definition ID", default=0)
align_horizontal: EnumProperty(
items=[
("left", "Left", "", "ALIGN_LEFT", 0),
("middle", "Middle", "", "ALIGN_CENTER", 1),
("right", "Right", "", "ALIGN_RIGHT", 2),
],
default="left",
name="Horizontal Alignment",
)
align_vertical: EnumProperty(
items=[
("top", "Top", "", "ALIGN_TOP", 0),
("middle", "Middle", "", "ALIGN_MIDDLE", 1),
("bottom", "Bottom", "", "ALIGN_BOTTOM", 2),
],
default="middle",
name="Vertical Alignment",
)
def get_box_alignment(self) -> str:
alignment = self.align_vertical + "-" + self.align_horizontal
if alignment == "middle-middle":
alignment = "center"
return alignment
def get_literal_edited_data(self) -> dict[str, str]: def get_literal_edited_data(self) -> dict[str, str]:
text_data = { text_data = {
"CurrentValue": self.attributes["Literal"].string_value, "CurrentValue": self.attributes["Literal"].string_value,
"Literal": self.attributes["Literal"].string_value, "Literal": self.attributes["Literal"].string_value,
"BoxAlignment": self.attributes["BoxAlignment"].string_value, "BoxAlignment": self.get_box_alignment(),
} }
return text_data return text_data
@@ -860,12 +844,19 @@ class LiteralProps(PropertyGroup):
if TYPE_CHECKING: if TYPE_CHECKING:
attributes: bpy.types.bpy_prop_collection_idprop[Attribute] attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
value: str value: str
box_alignment: tuple[bool, bool, bool, bool, bool, bool, bool, bool, bool]
ifc_definition_id: int ifc_definition_id: int
align_horizontal: str
align_vertical: str
element_value_rows: bpy.types.bpy_prop_collection_idprop[ElementValueRow] element_value_rows: bpy.types.bpy_prop_collection_idprop[ElementValueRow]
category_for_adding: str category_for_adding: str
def update_text_alignment(self, context):
for literal_props in self.literals:
literal_props.align_horizontal = self.align_horizontal
literal_props.align_vertical = self.align_vertical
class BIMTextProperties(PropertyGroup): class BIMTextProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing", default=False) is_editing: BoolProperty(name="Is Editing", default=False)
literals: CollectionProperty(name="Literals", type=LiteralProps) literals: CollectionProperty(name="Literals", type=LiteralProps)
@@ -899,6 +890,7 @@ class BIMTextProperties(PropertyGroup):
], ],
default="left", default="left",
name="Horizontal Alignment", name="Horizontal Alignment",
update=update_text_alignment,
) )
align_vertical: EnumProperty( align_vertical: EnumProperty(
items=[ items=[
@@ -908,6 +900,7 @@ class BIMTextProperties(PropertyGroup):
], ],
default="middle", default="middle",
name="Vertical Alignment", name="Vertical Alignment",
update=update_text_alignment,
) )
if TYPE_CHECKING: if TYPE_CHECKING:
+4 -27
View File
@@ -781,33 +781,10 @@ class BIM_PT_text(Panel):
if other_attributes: if other_attributes:
bonsai.bim.helper.draw_attributes(other_attributes, box) bonsai.bim.helper.draw_attributes(other_attributes, box)
row = box.row(align=True) row = box.row()
cols = [row.column(align=True) for j in range(3)] row.label(text="Alignment")
for j in range(9): row.prop(literal_props, "align_horizontal", text="", expand=True)
cols[j % 3].prop( row.prop(literal_props, "align_vertical", text="", expand=True)
literal_props,
"box_alignment",
text="",
index=j,
icon="RADIOBUT_ON" if literal_props.box_alignment[j] else "RADIOBUT_OFF",
)
col = row.column(align=True)
alignment_label_row = col.row(align=True)
alignment_label_row.label(text=" Text box alignment:")
box_alignment_value = (
literal_props.attributes[
next(
(idx for idx, attr in enumerate(literal_props.attributes) if attr.name == "BoxAlignment"),
-1,
)
].string_value
if any(attr.name == "BoxAlignment" for attr in literal_props.attributes)
else "N/A"
)
col.label(text=f" {box_alignment_value}")
def draw(self, context): def draw(self, context):
obj = context.active_object obj = context.active_object
+15 -7
View File
@@ -603,6 +603,10 @@ class Drawing(bonsai.core.tool.Drawing):
props = tool.Drawing.get_text_props(obj) props = tool.Drawing.get_text_props(obj)
for literal_props in props.literals: for literal_props in props.literals:
literal_data = bonsai.bim.helper.export_attributes(literal_props.attributes) literal_data = bonsai.bim.helper.export_attributes(literal_props.attributes)
alignment = literal_props.align_vertical + "-" + literal_props.align_horizontal
if alignment == "middle-middle":
alignment = "center"
literal_data["BoxAlignment"] = alignment
literals.append(literal_data) literals.append(literal_data)
return literals return literals
@@ -1176,22 +1180,26 @@ class Drawing(bonsai.core.tool.Drawing):
@classmethod @classmethod
def import_text_attributes(cls, obj: bpy.types.Object) -> None: def import_text_attributes(cls, obj: bpy.types.Object) -> None:
from bonsai.bim.module.drawing.prop import BOX_ALIGNMENT_POSITIONS
props = cls.get_text_props(obj) props = cls.get_text_props(obj)
props.literals.clear() props.literals.clear()
ifc_literals = cls.get_text_literal(obj, return_list=True) ifc_literals = cls.get_text_literal(obj, return_list=True)
assert isinstance(ifc_literals, list) assert isinstance(ifc_literals, list)
if ifc_literals:
first_alignment = getattr(ifc_literals[0], "BoxAlignment", None) or "bottom-left"
if first_alignment == "center":
first_alignment = "middle-middle"
props.align_vertical, props.align_horizontal = first_alignment.split("-")
for ifc_literal in ifc_literals: for ifc_literal in ifc_literals:
literal_props = props.literals.add() literal_props = props.literals.add()
bonsai.bim.helper.import_attributes(ifc_literal, literal_props.attributes) bonsai.bim.helper.import_attributes(ifc_literal, literal_props.attributes)
box_alignment_mask = [False] * 9 alignment = getattr(ifc_literal, "BoxAlignment", None) or "bottom-left"
position_string = literal_props.attributes["BoxAlignment"].string_value if alignment == "center":
box_alignment_mask[BOX_ALIGNMENT_POSITIONS.index(position_string)] = True alignment = "middle-middle"
literal_props.align_vertical, literal_props.align_horizontal = alignment.split("-")
literal_props.box_alignment = box_alignment_mask # pyright: ignore[reportAttributeAccessIssue]
literal_props.ifc_definition_id = ifc_literal.id() literal_props.ifc_definition_id = ifc_literal.id()
from bonsai.bim.module.drawing.data import DecoratorData from bonsai.bim.module.drawing.data import DecoratorData
+4 -1
View File
@@ -666,10 +666,13 @@ class TestImportTextAttributes(NewFile):
literal_props = props.literals[0] literal_props = props.literals[0]
assert literal_props.ifc_definition_id == item.id() assert literal_props.ifc_definition_id == item.id()
assert literal_props.box_alignment[:] == tuple([False] * 6 + [True] + [False] * 2)
assert literal_props.attributes["Literal"].string_value == "Literal" assert literal_props.attributes["Literal"].string_value == "Literal"
assert literal_props.attributes["Path"].enum_value == "RIGHT" assert literal_props.attributes["Path"].enum_value == "RIGHT"
assert literal_props.attributes["BoxAlignment"].string_value == "bottom-left" assert literal_props.attributes["BoxAlignment"].string_value == "bottom-left"
assert literal_props.align_vertical == "bottom"
assert literal_props.align_horizontal == "left"
assert props.align_vertical == "bottom"
assert props.align_horizontal == "left"
class TestReplaceTextLiteralVariables(NewFile): class TestReplaceTextLiteralVariables(NewFile):