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.string_value = literal_attr_values[attr_name]
box_alignment_mask = [False] * 9
box_alignment_mask[6] = True # bottom_left box_alignment
literal_props.box_alignment = box_alignment_mask
literal_props.align_vertical = "bottom"
literal_props.align_horizontal = "left"
return {"FINISHED"}
@@ -4178,10 +4177,7 @@ class SelectSimilarTextLiteralValue(bpy.types.Operator):
should_select = True
break
elif self.attribute_type == "box_alignment":
box_alignment_attr = next(
(attr for attr in literal.attributes if attr.name == "BoxAlignment"), None
)
if box_alignment_attr and box_alignment_attr.string_value == self.literal_value:
if literal.get_box_alignment() == self.literal_value:
should_select = True
break
+35 -42
View File
@@ -673,20 +673,6 @@ class BIMCameraProperties(PropertyGroup):
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):
"""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):
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)
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)
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]:
text_data = {
"CurrentValue": 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
@@ -860,12 +844,19 @@ class LiteralProps(PropertyGroup):
if TYPE_CHECKING:
attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
value: str
box_alignment: tuple[bool, bool, bool, bool, bool, bool, bool, bool, bool]
ifc_definition_id: int
align_horizontal: str
align_vertical: str
element_value_rows: bpy.types.bpy_prop_collection_idprop[ElementValueRow]
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):
is_editing: BoolProperty(name="Is Editing", default=False)
literals: CollectionProperty(name="Literals", type=LiteralProps)
@@ -899,6 +890,7 @@ class BIMTextProperties(PropertyGroup):
],
default="left",
name="Horizontal Alignment",
update=update_text_alignment,
)
align_vertical: EnumProperty(
items=[
@@ -908,6 +900,7 @@ class BIMTextProperties(PropertyGroup):
],
default="middle",
name="Vertical Alignment",
update=update_text_alignment,
)
if TYPE_CHECKING:
+4 -27
View File
@@ -781,33 +781,10 @@ class BIM_PT_text(Panel):
if other_attributes:
bonsai.bim.helper.draw_attributes(other_attributes, box)
row = box.row(align=True)
cols = [row.column(align=True) for j in range(3)]
for j in range(9):
cols[j % 3].prop(
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}")
row = box.row()
row.label(text="Alignment")
row.prop(literal_props, "align_horizontal", text="", expand=True)
row.prop(literal_props, "align_vertical", text="", expand=True)
def draw(self, context):
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)
for literal_props in props.literals:
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)
return literals
@@ -1176,22 +1180,26 @@ class Drawing(bonsai.core.tool.Drawing):
@classmethod
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.literals.clear()
ifc_literals = cls.get_text_literal(obj, return_list=True)
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:
literal_props = props.literals.add()
bonsai.bim.helper.import_attributes(ifc_literal, literal_props.attributes)
box_alignment_mask = [False] * 9
position_string = literal_props.attributes["BoxAlignment"].string_value
box_alignment_mask[BOX_ALIGNMENT_POSITIONS.index(position_string)] = True
literal_props.box_alignment = box_alignment_mask # pyright: ignore[reportAttributeAccessIssue]
alignment = getattr(ifc_literal, "BoxAlignment", None) or "bottom-left"
if alignment == "center":
alignment = "middle-middle"
literal_props.align_vertical, literal_props.align_horizontal = alignment.split("-")
literal_props.ifc_definition_id = ifc_literal.id()
from bonsai.bim.module.drawing.data import DecoratorData
+4 -1
View File
@@ -666,10 +666,13 @@ class TestImportTextAttributes(NewFile):
literal_props = props.literals[0]
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["Path"].enum_value == "RIGHT"
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):