Fix #5220. See #7629. See #7505. Reimplement text bulk copying using established copy attribute paradigm.

Previously, copy attribution was coupled with text editing. This meant
that you couldn't just do something like change the font or alignment
without also affecting literals. Now like most apps you can just select
bunch of text and change font size etc, using the same UI look and feel
that copying attribute has when editing attributes.

This refactor also removes the need for explicit props tracking each
possible attribute to copy, and the settings collection group. Bulk
applying is now done in core with no calls to UI.
This commit is contained in:
Dion Moult
2026-02-06 16:12:14 +11:00
parent 2692341d3a
commit 5e9f97a0c7
9 changed files with 192 additions and 193 deletions
@@ -45,6 +45,7 @@ classes = (
operator.CleanWireframes, operator.CleanWireframes,
operator.ContractSheet, operator.ContractSheet,
operator.ConvertSVGToDXF, operator.ConvertSVGToDXF,
operator.CopyTextToSelection,
operator.CreateDrawing, operator.CreateDrawing,
operator.CreateSheets, operator.CreateSheets,
operator.DisableAddAnnotationType, operator.DisableAddAnnotationType,
@@ -115,7 +116,6 @@ classes = (
prop.BIMCameraProperties, prop.BIMCameraProperties,
prop.ElementValueRow, prop.ElementValueRow,
prop.LiteralProps, prop.LiteralProps,
prop.LiteralApplySettings,
prop.BIMTextProperties, prop.BIMTextProperties,
prop.BIMAssignedProductProperties, prop.BIMAssignedProductProperties,
prop.BIMAnnotationProperties, prop.BIMAnnotationProperties,
@@ -355,8 +355,6 @@ class DecoratorData:
font_size = FONT_SIZES[font_size_type] font_size = FONT_SIZES[font_size_type]
symbol = tool.Drawing.get_annotation_symbol(element) symbol = tool.Drawing.get_annotation_symbol(element)
newline_at = pset_data.get("Newline_At", 0) newline_at = pset_data.get("Newline_At", 0)
reverse_list = pset_data.get("Reverse_List", False)
list_separator = pset_data.get("List_Separator") or ", "
# other attributes # other attributes
literals = tool.Drawing.get_text_literal(obj, return_list=True) literals = tool.Drawing.get_text_literal(obj, return_list=True)
@@ -384,8 +382,6 @@ class DecoratorData:
"FontSize": font_size, "FontSize": font_size,
"Symbol": symbol, "Symbol": symbol,
"Newline_At": newline_at, "Newline_At": newline_at,
"Reverse_List": reverse_list,
"List_Separator": list_separator,
} }
@classmethod @classmethod
@@ -3181,9 +3181,20 @@ class EditText(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_text" bl_idname = "bim.edit_text"
bl_label = "Edit Text" bl_label = "Edit Text"
bl_description = "Save changes to the text annotation and\ndisable the text editing options" bl_description = "Save changes to the text annotation and\ndisable the text editing options"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.edit_text(tool.Drawing, obj=tool.Blender.get_active_object())
tool.Blender.update_viewport()
class CopyTextToSelection(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.copy_text_to_selection"
bl_label = "Copy Text To Selection"
bl_description = "Copy text formatting or literals to selected objects"
bl_options = {"REGISTER", "UNDO"}
attribute: bpy.props.StringProperty()
def _execute(self, context): def _execute(self, context):
apply_objs = [ apply_objs = [
obj obj
@@ -3191,7 +3202,12 @@ class EditText(bpy.types.Operator, tool.Ifc.Operator):
if (element := tool.Ifc.get_entity(obj)) if (element := tool.Ifc.get_entity(obj))
and tool.Drawing.is_annotation_object_type(element, ["TEXT", "TEXT_LEADER"]) and tool.Drawing.is_annotation_object_type(element, ["TEXT", "TEXT_LEADER"])
] ]
core.edit_text(tool.Drawing, attribute_obj=tool.Blender.get_active_object(), apply_objs=apply_objs) core.copy_text_to_selection(
tool.Drawing,
attribute=self.attribute,
attribute_obj=tool.Blender.get_active_object(),
apply_objs=apply_objs,
)
tool.Blender.update_viewport() tool.Blender.update_viewport()
@@ -3207,8 +3223,6 @@ class EnableEditingText(bpy.types.Operator, tool.Ifc.Operator):
props = tool.Drawing.get_text_props(obj) props = tool.Drawing.get_text_props(obj)
core.enable_editing_text(tool.Drawing, obj=obj) core.enable_editing_text(tool.Drawing, obj=obj)
props.ensure_literal_apply_settings(len(props.literals))
text_element = tool.Ifc.get_entity(obj) text_element = tool.Ifc.get_entity(obj)
assigned_product_entity = tool.Drawing.get_assigned_product(text_element) if text_element else None assigned_product_entity = tool.Drawing.get_assigned_product(text_element) if text_element else None
assigned_product_obj = tool.Ifc.get_object(assigned_product_entity) if assigned_product_entity else None assigned_product_obj = tool.Ifc.get_object(assigned_product_entity) if assigned_product_entity else None
@@ -3297,9 +3311,6 @@ class AddTextLiteral(bpy.types.Operator):
box_alignment_mask = [False] * 9 box_alignment_mask = [False] * 9
box_alignment_mask[6] = True # bottom_left box_alignment box_alignment_mask[6] = True # bottom_left box_alignment
literal_props.box_alignment = box_alignment_mask literal_props.box_alignment = box_alignment_mask
props.ensure_literal_apply_settings(len(props.literals))
return {"FINISHED"} return {"FINISHED"}
@@ -3318,9 +3329,6 @@ class RemoveTextLiteral(bpy.types.Operator):
props = tool.Drawing.get_text_props(obj) props = tool.Drawing.get_text_props(obj)
props.literals.remove(self.literal_prop_id) props.literals.remove(self.literal_prop_id)
tool.Blender.update_viewport() tool.Blender.update_viewport()
props.ensure_literal_apply_settings(len(props.literals))
return {"FINISHED"} return {"FINISHED"}
+30 -52
View File
@@ -872,22 +872,20 @@ class LiteralProps(PropertyGroup):
category_for_adding: str category_for_adding: str
class LiteralApplySettings(PropertyGroup):
literal_index: IntProperty(name="Literal Index")
apply_text_to_all: BoolProperty(name="Apply Text to All", default=False)
apply_path_to_all: BoolProperty(name="Apply Path to All", default=False)
apply_box_alignment_to_all: BoolProperty(name="Apply Box Alignment to All", default=False)
if TYPE_CHECKING:
literal_index: int
apply_text_to_all: bool
apply_path_to_all: bool
apply_box_alignment_to_all: bool
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)
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.",
)
font_size: EnumProperty( font_size: EnumProperty(
items=[ items=[
("1.8", "1.8 - Small", ""), ("1.8", "1.8 - Small", ""),
@@ -899,52 +897,34 @@ class BIMTextProperties(PropertyGroup):
default="2.5", default="2.5",
name="Font Size", name="Font Size",
) )
newline_at: IntProperty(name="Newline At") align_horizontal: EnumProperty(
reverse_list: BoolProperty(name="Reverse List", description="Reverses the order of any list.", default=False) items=[
list_separator: StringProperty( # pyright: ignore[reportRedeclaration] ("left", "Left", "", "ALIGN_LEFT", 0),
name="List Separator", ("middle", "Middle", "", "ALIGN_CENTER", 1),
description="Text used to separate lists. Uses a comma (, ) if empty.", ("right", "Right", "", "ALIGN_RIGHT", 2),
],
default="left",
name="Horizontal Alignment",
) )
symbol: EnumProperty( # pyright: ignore[reportRedeclaration] align_vertical: EnumProperty(
name="Symbol", items=[
description="Symbol from symbols.svg to use for this text.", ("top", "Top", "", "ALIGN_TOP", 0),
items=[(s, s, "") for s in ["NO SYMBOL", "CUSTOM SYMBOL"] + tool.Drawing.DEFAULT_SYMBOLS], ("middle", "Middle", "", "ALIGN_MIDDLE", 1),
default="NO SYMBOL", ("bottom", "Bottom", "", "ALIGN_BOTTOM", 2),
],
default="middle",
name="Vertical Alignment",
) )
custom_symbol: StringProperty( # pyright: ignore[reportRedeclaration]
name="Custom Symbol",
description="Non-default symbol to use for this text.",
)
apply_font_size_to_all: BoolProperty(
name="Apply Font Size to All", description="Apply font size changes to all selected text objects", default=False
)
apply_newline_to_all: BoolProperty(
name="Apply Newline to All", description="Apply newline changes to all selected text objects", default=False
)
literal_apply_settings: CollectionProperty(name="Literal Apply Settings", type=LiteralApplySettings)
def ensure_literal_apply_settings(self, literal_count: int):
"""Ensure we have apply settings for all literals"""
while len(self.literal_apply_settings) > literal_count:
self.literal_apply_settings.remove(len(self.literal_apply_settings) - 1)
while len(self.literal_apply_settings) < literal_count:
setting = self.literal_apply_settings.add()
setting.literal_index = len(self.literal_apply_settings) - 1
if TYPE_CHECKING: if TYPE_CHECKING:
is_editing: bool is_editing: bool
literals: bpy.types.bpy_prop_collection_idprop[LiteralProps] literals: bpy.types.bpy_prop_collection_idprop[LiteralProps]
font_size: str
newline_at: int newline_at: int
reverse_list: bool
list_separator: str
symbol: Union[str, Literal["NO SYMBOL", "CUSTOM SYMBOL"]] symbol: Union[str, Literal["NO SYMBOL", "CUSTOM SYMBOL"]]
custom_symbol: str custom_symbol: str
apply_font_size_to_all: bool font_size: str
apply_newline_to_all: bool align_horizontal: str
align_vertical: str
def get_symbol(self) -> Union[str, None]: def get_symbol(self) -> Union[str, None]:
if self.symbol == "NO SYMBOL": if self.symbol == "NO SYMBOL":
@@ -979,8 +959,6 @@ class BIMTextProperties(PropertyGroup):
"FontSize": float(self.font_size), "FontSize": float(self.font_size),
"Newline_At": int(self.newline_at), "Newline_At": int(self.newline_at),
"Symbol": self.get_symbol(), "Symbol": self.get_symbol(),
"Reverse_List": self.reverse_list,
"List_Separator": self.list_separator or ", ",
} }
return text_data return text_data
@@ -989,11 +989,6 @@ class SvgWriter:
symbol = tool.Drawing.get_annotation_symbol(element) symbol = tool.Drawing.get_annotation_symbol(element)
newline_at = tool.Drawing.get_newline_at(element) newline_at = tool.Drawing.get_newline_at(element)
# Get reverse_list and list_separator from EPset_Annotation
pset_data = ifcopenshell.util.element.get_pset(element, "EPset_Annotation") or {}
reverse_list = pset_data.get("Reverse_List", False)
list_separator = pset_data.get("List_Separator") or ", "
template_text_fields = [] template_text_fields = []
if symbol: if symbol:
symbol_transform = self.get_symbol_transform(text_position_svg_str, angle, text_obj) symbol_transform = self.get_symbol_transform(text_position_svg_str, angle, text_obj)
@@ -1010,7 +1005,7 @@ class SvgWriter:
# NOTE: zip makes sure that we iterate over the shortest list # NOTE: zip makes sure that we iterate over the shortest list
for field, text_literal in zip(template_text_fields, text_literals): for field, text_literal in zip(template_text_fields, text_literals):
field.text = tool.Drawing.replace_text_literal_variables( field.text = tool.Drawing.replace_text_literal_variables(
text_literal.Literal, product or element, reverse_list, list_separator text_literal.Literal, product or element
) )
field.attrib["class"] = classes_str field.attrib["class"] = classes_str
@@ -1030,9 +1025,7 @@ class SvgWriter:
line_number = 0 line_number = 0
for text_literal in text_literals: for text_literal in text_literals:
text = tool.Drawing.replace_text_literal_variables( text = tool.Drawing.replace_text_literal_variables(text_literal.Literal, product or element)
text_literal.Literal, product or element, reverse_list, list_separator
)
text_segments = parse_markdown_it(text) text_segments = parse_markdown_it(text)
+60 -77
View File
@@ -644,58 +644,56 @@ class BIM_PT_text(Panel):
row.operator("bim.add_text_literal", icon="ADD", text="Add Literal") row.operator("bim.add_text_literal", icon="ADD", text="Add Literal")
else: else:
row.operator("bim.edit_text", icon="CHECKMARK") row.operator("bim.edit_text", icon="CHECKMARK")
row.operator("bim.add_text_literal", icon="ADD", text="")
row.operator("bim.disable_editing_text", icon="CANCEL", text="") row.operator("bim.disable_editing_text", icon="CANCEL", text="")
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.prop(props, "font_size") row.prop(props, "font_size")
row.prop(props, "apply_font_size_to_all", text="", icon="COPYDOWN") row.operator("bim.copy_text_to_selection", text="", icon="COPYDOWN").attribute = "FONT_SIZE"
row = self.layout.row()
row.label(text="Alignment")
row.prop(props, "align_horizontal", text="", expand=True)
row.prop(props, "align_vertical", text="", expand=True)
row.operator("bim.copy_text_to_selection", text="", icon="COPYDOWN").attribute = "ALIGNMENT"
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.prop(props, "newline_at") row.prop(props, "newline_at")
row = self.layout.row(align=True) row.operator("bim.copy_text_to_selection", text="", icon="COPYDOWN").attribute = "WRAP_LENGTH"
row.prop(props, "reverse_list")
row = self.layout.row(align=True)
row.prop(props, "list_separator")
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.prop(props, "symbol") row.prop(props, "symbol")
if props.symbol == "CUSTOM SYMBOL": if props.symbol == "CUSTOM SYMBOL":
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.prop(props, "custom_symbol", text="") row.prop(props, "custom_symbol", text="")
row.prop(props, "apply_newline_to_all", text="", icon="COPYDOWN")
select_op = row.operator("bim.select_similar_text_literal_value", text="", icon="RESTRICT_SELECT_OFF") select_op = row.operator("bim.select_similar_text_literal_value", text="", icon="RESTRICT_SELECT_OFF")
select_op.literal_value = str(props.newline_at) select_op.literal_value = str(props.newline_at)
select_op.attribute_type = "newline" select_op.attribute_type = "newline"
row.operator("bim.copy_text_to_selection", text="", icon="COPYDOWN").attribute = "SYMBOL"
row = self.layout.row(align=True)
row.label(text="Literals:")
row.operator("bim.copy_text_to_selection", text="", icon="COPYDOWN").attribute = "LITERALS"
row.operator("bim.add_text_literal", icon="ADD", text="")
for i, literal_props in enumerate(props.literals): for i, literal_props in enumerate(props.literals):
box = self.layout.box() box = self.layout.box()
if len(literal_props.attributes):
row = box.row(align=True)
row.label(text=f"Literal[{i}]:")
if i > 0:
row.operator("bim.order_text_literal_up", icon="TRIA_UP", text="").literal_prop_id = i
if i < len(props.literals) - 1:
row.operator("bim.order_text_literal_down", icon="TRIA_DOWN", text="").literal_prop_id = i
row.operator("bim.remove_text_literal", icon="X", text="").literal_prop_id = i
if len(literal_props.attributes) > 0 and i < len(props.literal_apply_settings):
row = box.row(align=True) row = box.row(align=True)
bonsai.bim.helper.draw_attribute(literal_props.attributes[0], row, enable_search=True) bonsai.bim.helper.draw_attribute(literal_props.attributes[0], row, enable_search=True)
if i > 0:
row.operator("bim.order_text_literal_up", icon="TRIA_UP", text="").literal_prop_id = i
if i < len(props.literals) - 1:
row.operator("bim.order_text_literal_down", icon="TRIA_DOWN", text="").literal_prop_id = i
row.operator("bim.remove_text_literal", icon="X", text="").literal_prop_id = i
expand_icon = "DOWNARROW_HLT" if getattr(literal_props, "show_element_values", False) else "RIGHTARROW" expand_icon = "DOWNARROW_HLT" if getattr(literal_props, "show_element_values", False) else "RIGHTARROW"
op = row.operator("bim.toggle_element_values_panel", icon=expand_icon, text="") op = row.operator("bim.toggle_element_values_panel", icon=expand_icon, text="")
op.literal_prop_id = i op.literal_prop_id = i
row.prop(props.literal_apply_settings[i], "apply_text_to_all", text="", icon="COPYDOWN")
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
assigned_element = tool.Drawing.get_assigned_product(element) or element assigned_element = tool.Drawing.get_assigned_product(element) or element
resolved_value = tool.Drawing.replace_text_literal_variables( resolved_value = tool.Drawing.replace_text_literal_variables(
literal_props.attributes[0].string_value, literal_props.attributes[0].string_value,
assigned_element, assigned_element,
props.reverse_list,
props.list_separator,
) )
row = box.row(align=True) row = box.row(align=True)
row.label(text="CurrentValue:") row.label(text="CurrentValue:")
@@ -779,8 +777,6 @@ class BIM_PT_text(Panel):
else: else:
row.prop(attr, "string_value", text="Path") row.prop(attr, "string_value", text="Path")
select_value = attr.string_value select_value = attr.string_value
if i < len(props.literal_apply_settings):
row.prop(props.literal_apply_settings[i], "apply_path_to_all", text="", icon="COPYDOWN")
other_attributes = [a for a in literal_props.attributes[2:] if a.name != "BoxAlignment"] other_attributes = [a for a in literal_props.attributes[2:] if a.name != "BoxAlignment"]
if other_attributes: if other_attributes:
@@ -800,10 +796,6 @@ class BIM_PT_text(Panel):
col = row.column(align=True) col = row.column(align=True)
alignment_label_row = col.row(align=True) alignment_label_row = col.row(align=True)
alignment_label_row.label(text=" Text box alignment:") alignment_label_row.label(text=" Text box alignment:")
if i < len(props.literal_apply_settings):
alignment_label_row.prop(
props.literal_apply_settings[i], "apply_box_alignment_to_all", text="", icon="COPYDOWN"
)
box_alignment_value = ( box_alignment_value = (
literal_props.attributes[ literal_props.attributes[
@@ -824,61 +816,52 @@ class BIM_PT_text(Panel):
props = tool.Drawing.get_text_props(obj) props = tool.Drawing.get_text_props(obj)
if props.is_editing: if props.is_editing:
self.draw_text_editing_ui(context) return self.draw_text_editing_ui(context)
else: text_data = DecoratorData.get_text_data(obj)
text_data = DecoratorData.get_text_data(obj)
row = self.layout.row() row = self.layout.row()
row.operator("bim.enable_editing_text", icon="GREASEPENCIL") row.operator("bim.enable_editing_text", icon="GREASEPENCIL")
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text="FontSize") row.label(text="FontSize")
click_op = row.operator( click_op = row.operator("bim.select_similar_text_literal_value", text=str(text_data["FontSize"]), emboss=False)
"bim.select_similar_text_literal_value", text=str(text_data["FontSize"]), emboss=False click_op.literal_value = str(text_data["FontSize"])
) click_op.attribute_type = "font_size"
click_op.literal_value = str(text_data["FontSize"]) click_op.display_text = str(text_data["FontSize"])
click_op.attribute_type = "font_size"
click_op.display_text = str(text_data["FontSize"])
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text="Newline_At") row.label(text="Newline_At")
click_op = row.operator( click_op = row.operator(
"bim.select_similar_text_literal_value", text=str(text_data["Newline_At"]), emboss=False "bim.select_similar_text_literal_value", text=str(text_data["Newline_At"]), emboss=False
) )
click_op.literal_value = str(text_data["Newline_At"]) click_op.literal_value = str(text_data["Newline_At"])
click_op.attribute_type = "newline" click_op.attribute_type = "newline"
click_op.display_text = str(text_data["Newline_At"]) click_op.display_text = str(text_data["Newline_At"])
row = self.layout.row(align=True)
row.label(text="Reverse_List")
row.label(text=str(text_data["Reverse_List"]))
row = self.layout.row(align=True)
row.label(text="List_Separator")
row.label(text=str(text_data["List_Separator"]))
for i, literal_data in enumerate(text_data["Literals"]): for i, literal_data in enumerate(text_data["Literals"]):
box = self.layout.box() box = self.layout.box()
box.label(text=f"Literal[{i}]:") box.label(text=f"Literal[{i}]:")
# Combine both approaches: clickable attributes from PR #7292 and display from PR #7106 # Combine both approaches: clickable attributes from PR #7292 and display from PR #7106
for attribute in literal_data: for attribute in literal_data:
row = box.row(align=True) row = box.row(align=True)
row.label(text=attribute) row.label(text=attribute)
click_op = row.operator( click_op = row.operator(
"bim.select_similar_text_literal_value", "bim.select_similar_text_literal_value",
text=str(literal_data[attribute]), text=str(literal_data[attribute]),
emboss=False, emboss=False,
) )
click_op.literal_value = str(literal_data[attribute]) click_op.literal_value = str(literal_data[attribute])
click_op.literal_index = i click_op.literal_index = i
if attribute == "Literal": if attribute == "Literal":
click_op.attribute_type = "literal" click_op.attribute_type = "literal"
elif attribute == "Path": elif attribute == "Path":
click_op.attribute_type = "path" click_op.attribute_type = "path"
elif attribute == "BoxAlignment": elif attribute == "BoxAlignment":
click_op.attribute_type = "box_alignment" click_op.attribute_type = "box_alignment"
else: else:
click_op.attribute_type = "text" click_op.attribute_type = "text"
click_op.display_text = str(literal_data[attribute]) click_op.display_text = str(literal_data[attribute])
class BIM_UL_drawinglist(bpy.types.UIList): class BIM_UL_drawinglist(bpy.types.UIList):
+35 -6
View File
@@ -38,13 +38,42 @@ def disable_editing_text(drawing: type[tool.Drawing], obj: bpy.types.Object) ->
drawing.disable_editing_text(obj) drawing.disable_editing_text(obj)
def edit_text(drawing: type[tool.Drawing], attribute_obj: bpy.types.Object, apply_objs: list[bpy.types.Object]) -> None: def edit_text(drawing: type[tool.Drawing], obj: bpy.types.Object) -> None:
literal_attributes = drawing.export_text_literal_attributes(attribute_obj) literal_attributes = drawing.export_text_literal_attributes(obj)
drawing.edit_text_font_size(obj, drawing.export_font_size(obj))
drawing.edit_text_wrap_length(obj, drawing.export_wrap_length(obj))
drawing.edit_text_symbol(obj, drawing.export_symbol(obj))
drawing.edit_text_literals(obj, literal_attributes)
drawing.disable_editing_text(obj)
def copy_text_to_selection(
drawing: type[tool.Drawing],
attribute: Literal["FONT_SIZE", "ALIGNMENT", "WRAP_LENGTH", "SYMBOL", "LITERALS"],
attribute_obj: bpy.types.Object,
apply_objs: list[bpy.types.Object],
) -> None:
if attribute == "FONT_SIZE":
data = drawing.export_font_size(attribute_obj)
elif attribute == "ALIGNMENT":
data = drawing.export_alignment(attribute_obj)
elif attribute == "WRAP_LENGTH":
data = drawing.export_wrap_length(attribute_obj)
elif attribute == "SYMBOL":
data = drawing.export_symbol(attribute_obj)
elif attribute == "LITERALS":
data = drawing.export_text_literal_attributes(attribute_obj)
for obj in apply_objs: for obj in apply_objs:
drawing.edit_text_literals(obj, literal_attributes) if attribute == "FONT_SIZE":
# TODO: font size should be part of a separate set of formatting controls, not part of text editing drawing.edit_text_font_size(obj, data)
drawing.update_text_size_pset(obj) elif attribute == "ALIGNMENT":
drawing.update_text_annotation_properties(obj) drawing.edit_text_alignment(obj, data)
elif attribute == "WRAP_LENGTH":
drawing.edit_text_wrap_length(obj, data)
elif attribute == "SYMBOL":
drawing.edit_text_symbol(obj, data)
elif attribute == "LITERALS":
drawing.edit_text_literals(obj, data)
drawing.disable_editing_text(obj) drawing.disable_editing_text(obj)
+5 -2
View File
@@ -334,6 +334,11 @@ class Drawing:
def disable_editing_sheets(cls): pass def disable_editing_sheets(cls): pass
def disable_editing_text(cls, obj): pass def disable_editing_text(cls, obj): pass
def does_file_exist(cls, uri): pass def does_file_exist(cls, uri): pass
def edit_text_alignment(cls, obj, alignment): pass
def edit_text_font_size(cls, obj, size): pass
def edit_text_literals(cls, obj, literals): pass
def edit_text_symbol(cls, obj, symbol): pass
def edit_text_wrap_length(cls, obj, wrap_length): pass
def enable_editing(cls, obj): pass def enable_editing(cls, obj): pass
def enable_editing_assigned_product(cls, obj): pass def enable_editing_assigned_product(cls, obj): pass
def enable_editing_drawings(cls): pass def enable_editing_drawings(cls): pass
@@ -403,8 +408,6 @@ class Drawing:
def show_decorations(cls): pass def show_decorations(cls): pass
def sync_object_placement(cls, obj): pass def sync_object_placement(cls, obj): pass
def update_embedded_svg_location(cls, uri, old_location, new_location): pass def update_embedded_svg_location(cls, uri, old_location, new_location): pass
def update_text_annotation_properties(cls, obj): pass
def update_text_size_pset(cls, obj): pass
@interface @interface
+41 -32
View File
@@ -608,6 +608,25 @@ class Drawing(bonsai.core.tool.Drawing):
literals.append(literal_data) literals.append(literal_data)
return literals return literals
@classmethod
def export_font_size(cls, obj: bpy.types.Object) -> str:
return float(cls.get_text_props(obj).font_size)
@classmethod
def export_alignment(cls, obj: bpy.types.Object) -> str:
props = cls.get_text_props(obj)
if (alignment := props.align_vertical + "-" + props.align_horizontal) == "middle-middle":
return "center"
return alignment
@classmethod
def export_wrap_length(cls, obj: bpy.types.Object) -> str:
return cls.get_text_props(obj).newline_at
@classmethod
def export_symbol(cls, obj: bpy.types.Object) -> str:
return cls.get_text_props(obj).get_symbol()
@classmethod @classmethod
def create_annotation_context( def create_annotation_context(
cls, target_view: str, object_type: Optional[str] = None cls, target_view: str, object_type: Optional[str] = None
@@ -1181,8 +1200,6 @@ class Drawing(bonsai.core.tool.Drawing):
props.font_size = str(text_data["FontSize"]) props.font_size = str(text_data["FontSize"])
props.newline_at = text_data["Newline_At"] props.newline_at = text_data["Newline_At"]
props.set_symbol(text_data["Symbol"]) props.set_symbol(text_data["Symbol"])
props.reverse_list = text_data["Reverse_List"]
props.list_separator = text_data["List_Separator"]
@classmethod @classmethod
def import_assigned_product(cls, obj: bpy.types.Object) -> None: def import_assigned_product(cls, obj: bpy.types.Object) -> None:
@@ -1279,7 +1296,7 @@ class Drawing(bonsai.core.tool.Drawing):
props.should_draw_decorations = True props.should_draw_decorations = True
@classmethod @classmethod
def update_text_size_pset(cls, obj: bpy.types.Object) -> None: def edit_text_font_size(cls, obj: bpy.types.Object, font_size: float) -> None:
"""updates pset `EPset_Annotation.Classes` value """updates pset `EPset_Annotation.Classes` value
based on current font size from `obj.BIMTextProperties.font_size` based on current font size from `obj.BIMTextProperties.font_size`
""" """
@@ -1289,8 +1306,9 @@ class Drawing(bonsai.core.tool.Drawing):
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
assert element assert element
# updating text font size in EPset_Annotation.Classes # updating text font size in EPset_Annotation.Classes
font_size = float(props.font_size) print("we got", font_size, repr(font_size))
font_size_str = next((key for key in FONT_SIZES if FONT_SIZES[key] == font_size), None) font_size_str = next((key for key in FONT_SIZES if FONT_SIZES[key] == font_size), None)
print("so", font_size_str)
classes = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Classes") classes = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Classes")
assert isinstance(classes, Union[str, None]) assert isinstance(classes, Union[str, None])
classes_split = classes.split() if classes else [] classes_split = classes.split() if classes else []
@@ -1310,34 +1328,32 @@ class Drawing(bonsai.core.tool.Drawing):
pset = tool.Pset.get_element_pset(element, "EPset_Annotation") pset = tool.Pset.get_element_pset(element, "EPset_Annotation")
if not pset: if not pset:
pset = ifcopenshell.api.pset.add_pset(ifc_file, product=element, name="EPset_Annotation") pset = ifcopenshell.api.pset.add_pset(ifc_file, product=element, name="EPset_Annotation")
ifcopenshell.api.pset.edit_pset( ifcopenshell.api.pset.edit_pset(ifc_file, pset=pset, properties={"Classes": classes})
ifc_file,
pset=pset,
properties={"Classes": classes},
)
@classmethod @classmethod
def update_text_annotation_properties(cls, obj: bpy.types.Object) -> None: def edit_text_wrap_length(cls, obj: bpy.types.Object, wrap_length: int) -> None:
"""Update all EPset_Annotation properties from the text props"""
props = cls.get_text_props(obj)
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
assert element
ifc_file = tool.Ifc.get() ifc_file = tool.Ifc.get()
pset = tool.Pset.get_element_pset(element, "EPset_Annotation") pset = tool.Pset.get_element_pset(element, "EPset_Annotation")
if not pset: if not pset:
pset = ifcopenshell.api.pset.add_pset(ifc_file, product=element, name="EPset_Annotation") pset = ifcopenshell.api.pset.add_pset(ifc_file, product=element, name="EPset_Annotation")
ifcopenshell.api.pset.edit_pset(ifc_file, pset=pset, properties={"Newline_At": wrap_length})
ifcopenshell.api.pset.edit_pset( @classmethod
ifc_file, def edit_text_symbol(cls, obj: bpy.types.Object, symbol: str) -> None:
pset=pset, element = tool.Ifc.get_entity(obj)
properties={ ifc_file = tool.Ifc.get()
"Newline_At": int(props.newline_at), pset = tool.Pset.get_element_pset(element, "EPset_Annotation")
"Symbol": props.get_symbol(), if not pset:
"Reverse_List": props.reverse_list, pset = ifcopenshell.api.pset.add_pset(ifc_file, product=element, name="EPset_Annotation")
"List_Separator": props.list_separator or "", ifcopenshell.api.pset.edit_pset(ifc_file, pset=pset, properties={"Symbol": symbol})
},
) @classmethod
def edit_text_alignment(cls, obj: bpy.types.Object, alignment: str) -> None:
ifc_literals = cls.get_text_literal(obj, return_list=True)
for ifc_literal in ifc_literals or []:
if ifc_literal.is_a("IfcTextLiteralWithExtent"):
ifc_literal.BoxAlignment = alignment
# TODO below this point is highly experimental prototype code with no tests # TODO below this point is highly experimental prototype code with no tests
@@ -2081,13 +2097,9 @@ class Drawing(bonsai.core.tool.Drawing):
cls, cls,
text: str, text: str,
product: Optional[ifcopenshell.entity_instance] = None, product: Optional[ifcopenshell.entity_instance] = None,
reverse_list: bool = False,
list_separator: str = ", ",
) -> str: ) -> str:
if not product: if not product:
return text return text
if list_separator:
list_separator = list_separator.encode().decode("unicode_escape")
for command in re.findall("``.*?``", text): for command in re.findall("``.*?``", text):
original_command = command original_command = command
@@ -2102,10 +2114,7 @@ class Drawing(bonsai.core.tool.Drawing):
for variable in re.findall("{{.*?}}", text): for variable in re.findall("{{.*?}}", text):
value = ifcopenshell.util.selector.get_element_value(product, variable[2:-2]) value = ifcopenshell.util.selector.get_element_value(product, variable[2:-2])
if isinstance(value, (list, tuple)): if isinstance(value, (list, tuple)):
if reverse_list: value = ", ".join(str(v) for v in value)
value = list_separator.join(str(v) for v in reversed(value))
else:
value = list_separator.join(str(v) for v in value)
text = text.replace(variable, str(value)) text = text.replace(variable, str(value))
return text return text