2026-03-22 14:56:27 +03:00
|
|
|
use acadrust::entities::{AttachmentPoint, DrawingDirection, MText};
|
|
|
|
|
|
|
|
|
|
use crate::command::EntityTransform;
|
|
|
|
|
use crate::entities::common::{edit_prop as edit, ro_prop as ro, square_grip, triangle_grip};
|
|
|
|
|
use crate::entities::text_support::{
|
2026-05-31 02:33:47 +03:00
|
|
|
layout_mtext, resolve_text_style, GlyphBox, MTextRenderOpts, MTextVAnchor,
|
2026-03-22 14:56:27 +03:00
|
|
|
};
|
|
|
|
|
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckConvertible};
|
2026-06-16 11:33:46 +03:00
|
|
|
use crate::scene::convert::acad_to_truck::{TruckEntity, TruckObject};
|
|
|
|
|
use crate::scene::model::object::{GripApply, GripDef, PropSection, PropValue, Property};
|
|
|
|
|
use crate::scene::model::wire_model::SnapHint;
|
2026-03-22 14:56:27 +03:00
|
|
|
|
|
|
|
|
fn attachment_str(a: &AttachmentPoint) -> &'static str {
|
|
|
|
|
match a {
|
|
|
|
|
AttachmentPoint::TopLeft => "Top Left",
|
|
|
|
|
AttachmentPoint::TopCenter => "Top Center",
|
|
|
|
|
AttachmentPoint::TopRight => "Top Right",
|
|
|
|
|
AttachmentPoint::MiddleLeft => "Middle Left",
|
|
|
|
|
AttachmentPoint::MiddleCenter => "Middle Center",
|
|
|
|
|
AttachmentPoint::MiddleRight => "Middle Right",
|
|
|
|
|
AttachmentPoint::BottomLeft => "Bottom Left",
|
|
|
|
|
AttachmentPoint::BottomCenter => "Bottom Center",
|
|
|
|
|
AttachmentPoint::BottomRight => "Bottom Right",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn mtext_halign_str(a: &AttachmentPoint) -> &'static str {
|
|
|
|
|
match a {
|
|
|
|
|
AttachmentPoint::TopLeft | AttachmentPoint::MiddleLeft | AttachmentPoint::BottomLeft => {
|
|
|
|
|
"Left"
|
|
|
|
|
}
|
|
|
|
|
AttachmentPoint::TopCenter
|
|
|
|
|
| AttachmentPoint::MiddleCenter
|
|
|
|
|
| AttachmentPoint::BottomCenter => "Center",
|
|
|
|
|
AttachmentPoint::TopRight | AttachmentPoint::MiddleRight | AttachmentPoint::BottomRight => {
|
|
|
|
|
"Right"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn mtext_valign_str(a: &AttachmentPoint) -> &'static str {
|
|
|
|
|
match a {
|
|
|
|
|
AttachmentPoint::TopLeft | AttachmentPoint::TopCenter | AttachmentPoint::TopRight => "Top",
|
|
|
|
|
AttachmentPoint::MiddleLeft
|
|
|
|
|
| AttachmentPoint::MiddleCenter
|
|
|
|
|
| AttachmentPoint::MiddleRight => "Middle",
|
|
|
|
|
AttachmentPoint::BottomLeft
|
|
|
|
|
| AttachmentPoint::BottomCenter
|
|
|
|
|
| AttachmentPoint::BottomRight => "Bottom",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn mtext_attachment_from_align(h: &str, v: &str) -> Option<AttachmentPoint> {
|
|
|
|
|
Some(match (h, v) {
|
|
|
|
|
("Left", "Top") => AttachmentPoint::TopLeft,
|
|
|
|
|
("Center", "Top") => AttachmentPoint::TopCenter,
|
|
|
|
|
("Right", "Top") => AttachmentPoint::TopRight,
|
|
|
|
|
("Left", "Middle") => AttachmentPoint::MiddleLeft,
|
|
|
|
|
("Center", "Middle") => AttachmentPoint::MiddleCenter,
|
|
|
|
|
("Right", "Middle") => AttachmentPoint::MiddleRight,
|
|
|
|
|
("Left", "Bottom") => AttachmentPoint::BottomLeft,
|
|
|
|
|
("Center", "Bottom") => AttachmentPoint::BottomCenter,
|
|
|
|
|
("Right", "Bottom") => AttachmentPoint::BottomRight,
|
|
|
|
|
_ => return None,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn drawing_dir_str(d: &DrawingDirection) -> &'static str {
|
|
|
|
|
match d {
|
|
|
|
|
DrawingDirection::LeftToRight => "Left to Right",
|
|
|
|
|
DrawingDirection::TopToBottom => "Top to Bottom",
|
|
|
|
|
DrawingDirection::ByStyle => "By Style",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-31 02:33:47 +03:00
|
|
|
/// Per-visible-character world-space boxes for the MText editor's
|
|
|
|
|
/// click-to-select preview. Uses the exact same layout opts as `to_truck`
|
|
|
|
|
/// so the boxes line up with the rendered glyphs.
|
|
|
|
|
pub fn glyph_boxes(t: &MText, document: &acadrust::CadDocument) -> Vec<GlyphBox> {
|
|
|
|
|
let resolved_style = resolve_text_style(&t.style, document);
|
|
|
|
|
let attach_h_anchor: f32 = match t.attachment_point {
|
|
|
|
|
AttachmentPoint::TopCenter
|
|
|
|
|
| AttachmentPoint::MiddleCenter
|
|
|
|
|
| AttachmentPoint::BottomCenter => 0.5,
|
|
|
|
|
AttachmentPoint::TopRight | AttachmentPoint::MiddleRight | AttachmentPoint::BottomRight => {
|
|
|
|
|
1.0
|
|
|
|
|
}
|
|
|
|
|
_ => 0.0,
|
|
|
|
|
};
|
|
|
|
|
let v_anchor = match t.attachment_point {
|
|
|
|
|
AttachmentPoint::TopLeft | AttachmentPoint::TopCenter | AttachmentPoint::TopRight => {
|
|
|
|
|
MTextVAnchor::Top
|
|
|
|
|
}
|
|
|
|
|
AttachmentPoint::MiddleLeft
|
|
|
|
|
| AttachmentPoint::MiddleCenter
|
|
|
|
|
| AttachmentPoint::MiddleRight => MTextVAnchor::Middle,
|
|
|
|
|
AttachmentPoint::BottomLeft
|
|
|
|
|
| AttachmentPoint::BottomCenter
|
|
|
|
|
| AttachmentPoint::BottomRight => MTextVAnchor::Bottom,
|
|
|
|
|
};
|
|
|
|
|
let rotation = if resolved_style.is_upside_down {
|
|
|
|
|
t.rotation as f32 + std::f32::consts::PI
|
|
|
|
|
} else {
|
|
|
|
|
t.rotation as f32
|
|
|
|
|
};
|
|
|
|
|
let layout = layout_mtext(&MTextRenderOpts {
|
|
|
|
|
value: &t.value,
|
2026-06-11 00:04:37 +03:00
|
|
|
insertion: [
|
|
|
|
|
t.insertion_point.x,
|
|
|
|
|
t.insertion_point.y,
|
|
|
|
|
t.insertion_point.z,
|
|
|
|
|
],
|
2026-05-31 02:33:47 +03:00
|
|
|
height: t.height as f32,
|
|
|
|
|
rect_w: t.rectangle_width as f32,
|
|
|
|
|
rotation,
|
|
|
|
|
style: &resolved_style,
|
|
|
|
|
attach_h_anchor,
|
|
|
|
|
v_anchor,
|
|
|
|
|
line_spacing_factor: t.line_spacing_factor as f32,
|
|
|
|
|
vertical_text: matches!(t.drawing_direction, DrawingDirection::TopToBottom),
|
|
|
|
|
want_glyph_boxes: true,
|
|
|
|
|
});
|
|
|
|
|
layout.glyph_boxes
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-22 14:56:27 +03:00
|
|
|
fn to_truck(t: &MText, document: &acadrust::CadDocument) -> TruckEntity {
|
|
|
|
|
let resolved_style = resolve_text_style(&t.style, document);
|
2026-05-21 23:42:22 +03:00
|
|
|
let attach_h_anchor: f32 = match t.attachment_point {
|
2026-03-22 14:56:27 +03:00
|
|
|
AttachmentPoint::TopCenter
|
|
|
|
|
| AttachmentPoint::MiddleCenter
|
|
|
|
|
| AttachmentPoint::BottomCenter => 0.5,
|
|
|
|
|
AttachmentPoint::TopRight | AttachmentPoint::MiddleRight | AttachmentPoint::BottomRight => {
|
|
|
|
|
1.0
|
|
|
|
|
}
|
|
|
|
|
_ => 0.0,
|
|
|
|
|
};
|
feat(text): route every text-bearing entity through the run-aware MText pipeline
Lift the MText parser + renderer into a reusable `layout_mtext` helper in
`text_support` and re-point the remaining text-bearing entities at it, so
every inline format code (`\f`, `\C`/`\c`, `\H`, `\W`, `\Q`, `\T`, `\A`,
`\p…`, decorations, stacked fractions, paragraph indents, tab stops, …)
reaches the stroke output uniformly.
Per-entity changes:
- TABLE cell ([entities/table.rs]) — anchor + alignment math now feeds
`layout_mtext`; `\C7;Red`-style codes no longer render as literal
glyph noise.
- MLEADER MText content (both entity path in [entities/multileader.rs]
and the LOD-aware path in [scene/tessellate.rs]) — drops the
`strip_mtext_codes` + per-line `tessellate_text_ex` chain; the LOD 0
baseline / LOD 1 greek rect / LOD 2 stroke ladder and the text-frame /
bg-fill rectangle now read line widths from the layout.
- ATTRIB / ATTDEF — when `mtext_flag` is MultiLine / ConstantMultiLine
the value goes through the MText pipeline (SingleLine stays on the
Text-style anchor maths because the DXF spec disallows codes there).
- DIMENSION text_override — `dimension_text_entity` emits a synthetic
MText (instead of Text) whenever the value contains MText escapes;
the LOD ladder via `tessellate_entity_dim_text` handles both kinds.
Infrastructure:
- `MTextVAnchor` gains `MiddleOfTopLine`, `MiddleOfBottomLine`,
`BottomOfTopLine` so MLEADER's 9 attachment variants all map cleanly.
- `MTextLayout` returns `line_widths` / `line_height` / `v_offset` so
callers that draw chrome (frames, fills, baselines, greek rects)
share the same numbers as the rendered strokes.
- `mtext_line_count` re-routed through the new parser so LOD row counts
and rendered row counts can never disagree.
- Legacy helpers (`strip_mtext_codes`, `split_mtext_lines`,
`measure_mtext_chars`, `word_wrap`, `cxf::measure_text`, the
`v_offset_for_attachment` MLEADER offset table, `MTextLine::plain_text`)
deleted — all callers migrated.
Audit (TEXT_FORMAT_CODE_INTEGRATION.md) removed; its plan is implemented
here.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 05:53:01 +03:00
|
|
|
let v_anchor = match t.attachment_point {
|
|
|
|
|
AttachmentPoint::TopLeft | AttachmentPoint::TopCenter | AttachmentPoint::TopRight => {
|
|
|
|
|
MTextVAnchor::Top
|
|
|
|
|
}
|
|
|
|
|
AttachmentPoint::MiddleLeft
|
|
|
|
|
| AttachmentPoint::MiddleCenter
|
|
|
|
|
| AttachmentPoint::MiddleRight => MTextVAnchor::Middle,
|
|
|
|
|
AttachmentPoint::BottomLeft
|
|
|
|
|
| AttachmentPoint::BottomCenter
|
|
|
|
|
| AttachmentPoint::BottomRight => MTextVAnchor::Bottom,
|
|
|
|
|
};
|
|
|
|
|
let rotation = if resolved_style.is_upside_down {
|
2026-05-21 12:38:03 +03:00
|
|
|
t.rotation as f32 + std::f32::consts::PI
|
|
|
|
|
} else {
|
|
|
|
|
t.rotation as f32
|
|
|
|
|
};
|
feat(text): route every text-bearing entity through the run-aware MText pipeline
Lift the MText parser + renderer into a reusable `layout_mtext` helper in
`text_support` and re-point the remaining text-bearing entities at it, so
every inline format code (`\f`, `\C`/`\c`, `\H`, `\W`, `\Q`, `\T`, `\A`,
`\p…`, decorations, stacked fractions, paragraph indents, tab stops, …)
reaches the stroke output uniformly.
Per-entity changes:
- TABLE cell ([entities/table.rs]) — anchor + alignment math now feeds
`layout_mtext`; `\C7;Red`-style codes no longer render as literal
glyph noise.
- MLEADER MText content (both entity path in [entities/multileader.rs]
and the LOD-aware path in [scene/tessellate.rs]) — drops the
`strip_mtext_codes` + per-line `tessellate_text_ex` chain; the LOD 0
baseline / LOD 1 greek rect / LOD 2 stroke ladder and the text-frame /
bg-fill rectangle now read line widths from the layout.
- ATTRIB / ATTDEF — when `mtext_flag` is MultiLine / ConstantMultiLine
the value goes through the MText pipeline (SingleLine stays on the
Text-style anchor maths because the DXF spec disallows codes there).
- DIMENSION text_override — `dimension_text_entity` emits a synthetic
MText (instead of Text) whenever the value contains MText escapes;
the LOD ladder via `tessellate_entity_dim_text` handles both kinds.
Infrastructure:
- `MTextVAnchor` gains `MiddleOfTopLine`, `MiddleOfBottomLine`,
`BottomOfTopLine` so MLEADER's 9 attachment variants all map cleanly.
- `MTextLayout` returns `line_widths` / `line_height` / `v_offset` so
callers that draw chrome (frames, fills, baselines, greek rects)
share the same numbers as the rendered strokes.
- `mtext_line_count` re-routed through the new parser so LOD row counts
and rendered row counts can never disagree.
- Legacy helpers (`strip_mtext_codes`, `split_mtext_lines`,
`measure_mtext_chars`, `word_wrap`, `cxf::measure_text`, the
`v_offset_for_attachment` MLEADER offset table, `MTextLine::plain_text`)
deleted — all callers migrated.
Audit (TEXT_FORMAT_CODE_INTEGRATION.md) removed; its plan is implemented
here.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 05:53:01 +03:00
|
|
|
let layout = layout_mtext(&MTextRenderOpts {
|
|
|
|
|
value: &t.value,
|
2026-06-11 00:04:37 +03:00
|
|
|
insertion: [
|
|
|
|
|
t.insertion_point.x,
|
|
|
|
|
t.insertion_point.y,
|
|
|
|
|
t.insertion_point.z,
|
|
|
|
|
],
|
feat(text): route every text-bearing entity through the run-aware MText pipeline
Lift the MText parser + renderer into a reusable `layout_mtext` helper in
`text_support` and re-point the remaining text-bearing entities at it, so
every inline format code (`\f`, `\C`/`\c`, `\H`, `\W`, `\Q`, `\T`, `\A`,
`\p…`, decorations, stacked fractions, paragraph indents, tab stops, …)
reaches the stroke output uniformly.
Per-entity changes:
- TABLE cell ([entities/table.rs]) — anchor + alignment math now feeds
`layout_mtext`; `\C7;Red`-style codes no longer render as literal
glyph noise.
- MLEADER MText content (both entity path in [entities/multileader.rs]
and the LOD-aware path in [scene/tessellate.rs]) — drops the
`strip_mtext_codes` + per-line `tessellate_text_ex` chain; the LOD 0
baseline / LOD 1 greek rect / LOD 2 stroke ladder and the text-frame /
bg-fill rectangle now read line widths from the layout.
- ATTRIB / ATTDEF — when `mtext_flag` is MultiLine / ConstantMultiLine
the value goes through the MText pipeline (SingleLine stays on the
Text-style anchor maths because the DXF spec disallows codes there).
- DIMENSION text_override — `dimension_text_entity` emits a synthetic
MText (instead of Text) whenever the value contains MText escapes;
the LOD ladder via `tessellate_entity_dim_text` handles both kinds.
Infrastructure:
- `MTextVAnchor` gains `MiddleOfTopLine`, `MiddleOfBottomLine`,
`BottomOfTopLine` so MLEADER's 9 attachment variants all map cleanly.
- `MTextLayout` returns `line_widths` / `line_height` / `v_offset` so
callers that draw chrome (frames, fills, baselines, greek rects)
share the same numbers as the rendered strokes.
- `mtext_line_count` re-routed through the new parser so LOD row counts
and rendered row counts can never disagree.
- Legacy helpers (`strip_mtext_codes`, `split_mtext_lines`,
`measure_mtext_chars`, `word_wrap`, `cxf::measure_text`, the
`v_offset_for_attachment` MLEADER offset table, `MTextLine::plain_text`)
deleted — all callers migrated.
Audit (TEXT_FORMAT_CODE_INTEGRATION.md) removed; its plan is implemented
here.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 05:53:01 +03:00
|
|
|
height: t.height as f32,
|
|
|
|
|
rect_w: t.rectangle_width as f32,
|
|
|
|
|
rotation,
|
|
|
|
|
style: &resolved_style,
|
|
|
|
|
attach_h_anchor,
|
|
|
|
|
v_anchor,
|
|
|
|
|
line_spacing_factor: t.line_spacing_factor as f32,
|
|
|
|
|
vertical_text: matches!(t.drawing_direction, DrawingDirection::TopToBottom),
|
2026-05-31 02:33:47 +03:00
|
|
|
want_glyph_boxes: false,
|
feat(text): route every text-bearing entity through the run-aware MText pipeline
Lift the MText parser + renderer into a reusable `layout_mtext` helper in
`text_support` and re-point the remaining text-bearing entities at it, so
every inline format code (`\f`, `\C`/`\c`, `\H`, `\W`, `\Q`, `\T`, `\A`,
`\p…`, decorations, stacked fractions, paragraph indents, tab stops, …)
reaches the stroke output uniformly.
Per-entity changes:
- TABLE cell ([entities/table.rs]) — anchor + alignment math now feeds
`layout_mtext`; `\C7;Red`-style codes no longer render as literal
glyph noise.
- MLEADER MText content (both entity path in [entities/multileader.rs]
and the LOD-aware path in [scene/tessellate.rs]) — drops the
`strip_mtext_codes` + per-line `tessellate_text_ex` chain; the LOD 0
baseline / LOD 1 greek rect / LOD 2 stroke ladder and the text-frame /
bg-fill rectangle now read line widths from the layout.
- ATTRIB / ATTDEF — when `mtext_flag` is MultiLine / ConstantMultiLine
the value goes through the MText pipeline (SingleLine stays on the
Text-style anchor maths because the DXF spec disallows codes there).
- DIMENSION text_override — `dimension_text_entity` emits a synthetic
MText (instead of Text) whenever the value contains MText escapes;
the LOD ladder via `tessellate_entity_dim_text` handles both kinds.
Infrastructure:
- `MTextVAnchor` gains `MiddleOfTopLine`, `MiddleOfBottomLine`,
`BottomOfTopLine` so MLEADER's 9 attachment variants all map cleanly.
- `MTextLayout` returns `line_widths` / `line_height` / `v_offset` so
callers that draw chrome (frames, fills, baselines, greek rects)
share the same numbers as the rendered strokes.
- `mtext_line_count` re-routed through the new parser so LOD row counts
and rendered row counts can never disagree.
- Legacy helpers (`strip_mtext_codes`, `split_mtext_lines`,
`measure_mtext_chars`, `word_wrap`, `cxf::measure_text`, the
`v_offset_for_attachment` MLEADER offset table, `MTextLine::plain_text`)
deleted — all callers migrated.
Audit (TEXT_FORMAT_CODE_INTEGRATION.md) removed; its plan is implemented
here.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 05:53:01 +03:00
|
|
|
});
|
refactor(snap): producers emit f64 snap points (world_offset removal P3)
TruckEntity.snap_pts is now DVec3 and every entity producer builds snap
candidates straight from the acadrust f64 coordinates instead of casting
to f32 first (text, mtext, arc, circle, ellipse, point, shape, solid,
mline, mtext, attribute, tolerance, table, underlay, ole2frame, mesh,
multileader, dimension, block-cache). offset_snap_pts stays f64
throughout. Still offset-relative (inert) — but snap precision is no
longer capped at ~0.5 m by an early f32 cast, which fixes a latent
UTM-scale snap error and sets up the absolute-coordinate switch.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-23 22:10:29 +03:00
|
|
|
let insertion = glam::DVec3::new(
|
|
|
|
|
t.insertion_point.x,
|
|
|
|
|
t.insertion_point.y,
|
|
|
|
|
t.insertion_point.z,
|
feat(text): route every text-bearing entity through the run-aware MText pipeline
Lift the MText parser + renderer into a reusable `layout_mtext` helper in
`text_support` and re-point the remaining text-bearing entities at it, so
every inline format code (`\f`, `\C`/`\c`, `\H`, `\W`, `\Q`, `\T`, `\A`,
`\p…`, decorations, stacked fractions, paragraph indents, tab stops, …)
reaches the stroke output uniformly.
Per-entity changes:
- TABLE cell ([entities/table.rs]) — anchor + alignment math now feeds
`layout_mtext`; `\C7;Red`-style codes no longer render as literal
glyph noise.
- MLEADER MText content (both entity path in [entities/multileader.rs]
and the LOD-aware path in [scene/tessellate.rs]) — drops the
`strip_mtext_codes` + per-line `tessellate_text_ex` chain; the LOD 0
baseline / LOD 1 greek rect / LOD 2 stroke ladder and the text-frame /
bg-fill rectangle now read line widths from the layout.
- ATTRIB / ATTDEF — when `mtext_flag` is MultiLine / ConstantMultiLine
the value goes through the MText pipeline (SingleLine stays on the
Text-style anchor maths because the DXF spec disallows codes there).
- DIMENSION text_override — `dimension_text_entity` emits a synthetic
MText (instead of Text) whenever the value contains MText escapes;
the LOD ladder via `tessellate_entity_dim_text` handles both kinds.
Infrastructure:
- `MTextVAnchor` gains `MiddleOfTopLine`, `MiddleOfBottomLine`,
`BottomOfTopLine` so MLEADER's 9 attachment variants all map cleanly.
- `MTextLayout` returns `line_widths` / `line_height` / `v_offset` so
callers that draw chrome (frames, fills, baselines, greek rects)
share the same numbers as the rendered strokes.
- `mtext_line_count` re-routed through the new parser so LOD row counts
and rendered row counts can never disagree.
- Legacy helpers (`strip_mtext_codes`, `split_mtext_lines`,
`measure_mtext_chars`, `word_wrap`, `cxf::measure_text`, the
`v_offset_for_attachment` MLEADER offset table, `MTextLine::plain_text`)
deleted — all callers migrated.
Audit (TEXT_FORMAT_CODE_INTEGRATION.md) removed; its plan is implemented
here.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 05:53:01 +03:00
|
|
|
);
|
2026-03-22 14:56:27 +03:00
|
|
|
TruckEntity {
|
feat(text): route every text-bearing entity through the run-aware MText pipeline
Lift the MText parser + renderer into a reusable `layout_mtext` helper in
`text_support` and re-point the remaining text-bearing entities at it, so
every inline format code (`\f`, `\C`/`\c`, `\H`, `\W`, `\Q`, `\T`, `\A`,
`\p…`, decorations, stacked fractions, paragraph indents, tab stops, …)
reaches the stroke output uniformly.
Per-entity changes:
- TABLE cell ([entities/table.rs]) — anchor + alignment math now feeds
`layout_mtext`; `\C7;Red`-style codes no longer render as literal
glyph noise.
- MLEADER MText content (both entity path in [entities/multileader.rs]
and the LOD-aware path in [scene/tessellate.rs]) — drops the
`strip_mtext_codes` + per-line `tessellate_text_ex` chain; the LOD 0
baseline / LOD 1 greek rect / LOD 2 stroke ladder and the text-frame /
bg-fill rectangle now read line widths from the layout.
- ATTRIB / ATTDEF — when `mtext_flag` is MultiLine / ConstantMultiLine
the value goes through the MText pipeline (SingleLine stays on the
Text-style anchor maths because the DXF spec disallows codes there).
- DIMENSION text_override — `dimension_text_entity` emits a synthetic
MText (instead of Text) whenever the value contains MText escapes;
the LOD ladder via `tessellate_entity_dim_text` handles both kinds.
Infrastructure:
- `MTextVAnchor` gains `MiddleOfTopLine`, `MiddleOfBottomLine`,
`BottomOfTopLine` so MLEADER's 9 attachment variants all map cleanly.
- `MTextLayout` returns `line_widths` / `line_height` / `v_offset` so
callers that draw chrome (frames, fills, baselines, greek rects)
share the same numbers as the rendered strokes.
- `mtext_line_count` re-routed through the new parser so LOD row counts
and rendered row counts can never disagree.
- Legacy helpers (`strip_mtext_codes`, `split_mtext_lines`,
`measure_mtext_chars`, `word_wrap`, `cxf::measure_text`, the
`v_offset_for_attachment` MLEADER offset table, `MTextLine::plain_text`)
deleted — all callers migrated.
Audit (TEXT_FORMAT_CODE_INTEGRATION.md) removed; its plan is implemented
here.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 05:53:01 +03:00
|
|
|
object: TruckObject::Text(layout.strokes),
|
2026-03-22 14:56:27 +03:00
|
|
|
snap_pts: vec![(insertion, SnapHint::Insertion)],
|
|
|
|
|
tangent_geoms: vec![],
|
|
|
|
|
key_vertices: vec![],
|
2026-05-06 23:24:26 +03:00
|
|
|
fill_tris: vec![],
|
2026-03-22 14:56:27 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn grips(t: &MText) -> Vec<GripDef> {
|
2026-06-11 00:04:37 +03:00
|
|
|
let p = glam::DVec3::new(
|
|
|
|
|
t.insertion_point.x,
|
|
|
|
|
t.insertion_point.y,
|
|
|
|
|
t.insertion_point.z,
|
2026-03-22 14:56:27 +03:00
|
|
|
);
|
2026-06-11 00:04:37 +03:00
|
|
|
let dir = glam::DVec3::new(t.rotation.cos(), t.rotation.sin(), 0.0);
|
|
|
|
|
let width_grip = p + dir * t.rectangle_width.max(0.0);
|
2026-03-22 14:56:27 +03:00
|
|
|
vec![square_grip(0, p), triangle_grip(1, width_grip)]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn properties(t: &MText, text_style_names: &[String]) -> PropSection {
|
|
|
|
|
PropSection {
|
|
|
|
|
title: "Geometry".into(),
|
|
|
|
|
props: vec![
|
|
|
|
|
edit("Insert X", "ins_x", t.insertion_point.x),
|
|
|
|
|
edit("Insert Y", "ins_y", t.insertion_point.y),
|
|
|
|
|
edit("Insert Z", "ins_z", t.insertion_point.z),
|
|
|
|
|
edit("Height", "height", t.height),
|
|
|
|
|
edit("Width", "rect_w", t.rectangle_width),
|
|
|
|
|
edit("Rect Height", "rect_h", t.rectangle_height.unwrap_or(0.0)),
|
|
|
|
|
edit("Rotation", "rotation", t.rotation.to_degrees()),
|
|
|
|
|
edit("Line Spacing", "line_spacing", t.line_spacing_factor),
|
|
|
|
|
Property {
|
|
|
|
|
label: "H-Align".into(),
|
|
|
|
|
field: "h_align",
|
|
|
|
|
value: PropValue::Choice {
|
|
|
|
|
selected: mtext_halign_str(&t.attachment_point).to_string(),
|
|
|
|
|
options: ["Left", "Center", "Right"]
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(str::to_string)
|
|
|
|
|
.collect(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Property {
|
|
|
|
|
label: "V-Align".into(),
|
|
|
|
|
field: "v_align",
|
|
|
|
|
value: PropValue::Choice {
|
|
|
|
|
selected: mtext_valign_str(&t.attachment_point).to_string(),
|
|
|
|
|
options: ["Top", "Middle", "Bottom"]
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(str::to_string)
|
|
|
|
|
.collect(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
ro(
|
|
|
|
|
"Attachment",
|
|
|
|
|
"attachment",
|
|
|
|
|
attachment_str(&t.attachment_point).to_string(),
|
|
|
|
|
),
|
|
|
|
|
ro(
|
|
|
|
|
"Direction",
|
|
|
|
|
"direction",
|
|
|
|
|
drawing_dir_str(&t.drawing_direction).to_string(),
|
|
|
|
|
),
|
|
|
|
|
Property {
|
|
|
|
|
label: "Content".into(),
|
|
|
|
|
field: "content",
|
|
|
|
|
value: PropValue::EditText(t.value.clone()),
|
|
|
|
|
},
|
|
|
|
|
Property {
|
|
|
|
|
label: "Style".into(),
|
|
|
|
|
field: "style",
|
|
|
|
|
value: PropValue::Choice {
|
|
|
|
|
selected: if t.style.trim().is_empty() {
|
|
|
|
|
"Standard".into()
|
|
|
|
|
} else {
|
|
|
|
|
t.style.clone()
|
|
|
|
|
},
|
|
|
|
|
options: text_style_names.to_vec(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apply_geom_prop(t: &mut MText, field: &str, value: &str) {
|
|
|
|
|
match field {
|
|
|
|
|
"content" => {
|
|
|
|
|
t.value = value.to_string();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
"style" => {
|
|
|
|
|
t.style = value.to_string();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
"h_align" => {
|
|
|
|
|
if let Some(next) =
|
|
|
|
|
mtext_attachment_from_align(value, mtext_valign_str(&t.attachment_point))
|
|
|
|
|
{
|
|
|
|
|
t.attachment_point = next;
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
"v_align" => {
|
|
|
|
|
if let Some(next) =
|
|
|
|
|
mtext_attachment_from_align(mtext_halign_str(&t.attachment_point), value)
|
|
|
|
|
{
|
|
|
|
|
t.attachment_point = next;
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
let Some(v) = crate::entities::common::parse_f64(value) else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
match field {
|
|
|
|
|
"ins_x" => t.insertion_point.x = v,
|
|
|
|
|
"ins_y" => t.insertion_point.y = v,
|
|
|
|
|
"ins_z" => t.insertion_point.z = v,
|
|
|
|
|
"height" if v > 0.0 => t.height = v,
|
|
|
|
|
"rect_w" if v > 0.0 => t.rectangle_width = v,
|
|
|
|
|
"rect_h" if v > 0.0 => t.rectangle_height = Some(v),
|
|
|
|
|
"rotation" => t.rotation = v.to_radians(),
|
|
|
|
|
"line_spacing" if v > 0.0 => t.line_spacing_factor = v,
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apply_grip(t: &mut MText, grip_id: usize, apply: GripApply) {
|
|
|
|
|
match (grip_id, apply) {
|
|
|
|
|
(0, GripApply::Absolute(p)) => {
|
|
|
|
|
t.insertion_point.x = p.x as f64;
|
|
|
|
|
t.insertion_point.y = p.y as f64;
|
|
|
|
|
t.insertion_point.z = p.z as f64;
|
|
|
|
|
}
|
|
|
|
|
(0, GripApply::Translate(d)) => {
|
|
|
|
|
t.insertion_point.x += d.x as f64;
|
|
|
|
|
t.insertion_point.y += d.y as f64;
|
|
|
|
|
t.insertion_point.z += d.z as f64;
|
|
|
|
|
}
|
|
|
|
|
(1, GripApply::Absolute(p)) => {
|
|
|
|
|
let dir_x = t.rotation.cos();
|
|
|
|
|
let dir_y = t.rotation.sin();
|
|
|
|
|
let dx = p.x as f64 - t.insertion_point.x;
|
|
|
|
|
let dy = p.y as f64 - t.insertion_point.y;
|
|
|
|
|
let projected = dx * dir_x + dy * dir_y;
|
|
|
|
|
t.rectangle_width = projected.max(0.01);
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apply_transform(t: &mut MText, tr: &EntityTransform) {
|
2026-06-16 11:33:46 +03:00
|
|
|
crate::scene::view::transform::apply_standard_entity_transform(t, tr, |entity, p1, p2| {
|
|
|
|
|
crate::scene::view::transform::reflect_xy_point(
|
2026-03-22 14:56:27 +03:00
|
|
|
&mut entity.insertion_point.x,
|
|
|
|
|
&mut entity.insertion_point.y,
|
|
|
|
|
p1,
|
|
|
|
|
p2,
|
|
|
|
|
);
|
|
|
|
|
let dx = (p2.x - p1.x) as f64;
|
|
|
|
|
let dy = (p2.y - p1.y) as f64;
|
|
|
|
|
let line_angle = dy.atan2(dx);
|
|
|
|
|
entity.rotation = 2.0 * line_angle - entity.rotation;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl TruckConvertible for MText {
|
|
|
|
|
fn to_truck(&self, document: &acadrust::CadDocument) -> Option<TruckEntity> {
|
|
|
|
|
Some(to_truck(self, document))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Grippable for MText {
|
|
|
|
|
fn grips(&self) -> Vec<GripDef> {
|
|
|
|
|
grips(self)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
|
|
|
|
apply_grip(self, grip_id, apply);
|
|
|
|
|
}
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
|
2026-06-16 11:33:46 +03:00
|
|
|
fn grip_menu(&self, grip_id: usize) -> Vec<crate::scene::model::object::GripMenuItem> {
|
|
|
|
|
use crate::scene::model::object::{GripMenuAction, GripMenuItem};
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
if grip_id == 0 {
|
|
|
|
|
// Insertion point
|
|
|
|
|
vec![
|
2026-06-11 00:04:37 +03:00
|
|
|
GripMenuItem {
|
|
|
|
|
label: "Stretch",
|
|
|
|
|
action: GripMenuAction::Stretch,
|
|
|
|
|
},
|
|
|
|
|
GripMenuItem {
|
|
|
|
|
label: "Move with Text",
|
|
|
|
|
action: GripMenuAction::MoveWithText,
|
|
|
|
|
},
|
|
|
|
|
GripMenuItem {
|
|
|
|
|
label: "Rotate",
|
|
|
|
|
action: GripMenuAction::RotateText,
|
|
|
|
|
},
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
]
|
|
|
|
|
} else {
|
|
|
|
|
// Width grip
|
2026-06-11 00:04:37 +03:00
|
|
|
vec![GripMenuItem {
|
|
|
|
|
label: "Stretch",
|
|
|
|
|
action: GripMenuAction::Stretch,
|
|
|
|
|
}]
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-16 11:33:46 +03:00
|
|
|
fn apply_grip_menu(&mut self, _grip_id: usize, _action: crate::scene::model::object::GripMenuAction) {
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
// Rotate needs a follow-up angle handled by
|
|
|
|
|
// `apply_grip_menu_value`; Move-with-Text is the default drag.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn grip_menu_value_prompt(
|
|
|
|
|
&self,
|
|
|
|
|
_grip_id: usize,
|
2026-06-16 11:33:46 +03:00
|
|
|
action: crate::scene::model::object::GripMenuAction,
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
) -> Option<&'static str> {
|
2026-06-16 11:33:46 +03:00
|
|
|
use crate::scene::model::object::GripMenuAction as A;
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
match action {
|
|
|
|
|
A::RotateText => Some("Rotation (deg)"),
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apply_grip_menu_value(
|
|
|
|
|
&mut self,
|
|
|
|
|
_grip_id: usize,
|
2026-06-16 11:33:46 +03:00
|
|
|
action: crate::scene::model::object::GripMenuAction,
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
value: f64,
|
|
|
|
|
) {
|
2026-06-16 11:33:46 +03:00
|
|
|
use crate::scene::model::object::GripMenuAction as A;
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
if matches!(action, A::RotateText) {
|
|
|
|
|
self.rotation = value.to_radians();
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-22 14:56:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PropertyEditable for MText {
|
|
|
|
|
fn geometry_properties(&self, text_style_names: &[String]) -> PropSection {
|
|
|
|
|
properties(self, text_style_names)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apply_geom_prop(&mut self, field: &str, value: &str) {
|
|
|
|
|
apply_geom_prop(self, field, value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Transformable for MText {
|
|
|
|
|
fn apply_transform(&mut self, t: &EntityTransform) {
|
|
|
|
|
apply_transform(self, t);
|
|
|
|
|
}
|
|
|
|
|
}
|
refactor(commands): per-entity MassProps + TextContent traits
Two free helpers in app/commands.rs lived as big match-on-EntityType
blocks: massprop_entity (Circle/Arc/Line/LwPolyline/Ellipse area +
perimeter + centroid, ~140 lines) and entity_text_content /
replace_entity_text (Text/MText/AttributeDefinition/AttributeEntity).
Move each to its own trait in entities/traits.rs and implement
per-entity:
MassPropsCalc — Circle, Arc, Line, LwPolyline, Ellipse
TextContent — Text, MText, AttributeDefinition, AttributeEntity
EntityTypeOps gets dispatch shims that route to the trait via the
existing macro, so callsites just call entity.mass_props() /
entity.text_content() / entity.replace_text(search, rep).
The remaining free helpers in commands.rs (entity_list_details,
entity_extra_info, entity_type_name, build_data_extraction_csv) are
single-command formatting helpers tightly coupled to LIST / EXTRACT /
text-format output. Distributing them as per-entity methods would
inflate every entity file with command-side formatting concerns
without making the command-side any cleaner — left in place.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 23:30:34 +03:00
|
|
|
|
|
|
|
|
impl crate::entities::traits::TextContent for acadrust::entities::MText {
|
|
|
|
|
fn text_content(&self) -> Option<String> {
|
|
|
|
|
Some(self.value.clone())
|
|
|
|
|
}
|
|
|
|
|
fn replace_text(&mut self, search: &str, rep: &str) {
|
|
|
|
|
let search_lc = search.to_lowercase();
|
|
|
|
|
if self.value.to_lowercase().contains(&search_lc) {
|
|
|
|
|
self.value = self.value.replace(search, rep);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|