feat(ucs): orient text-bearing annotations to the active UCS

New annotations now place their text and reading geometry along the
active UCS X axis (identity = world), so they read square to the user's
coordinate system instead of world-horizontal:

- TEXT / MTEXT: rotation defaults to the UCS X angle
  (DocumentTab::ucs_rotation_angle, model space only).
- Linear dimensions: measure along the UCS axis the span is nearest, and
  bake the UCS X angle as the text rotation so the value reads square
  even under a style that forces horizontal text. DIMCONTINUE / DIMBASELINE
  derive their axis from the base dim's rotation (any angle), not a
  world H/V test.
- LEADER: the MText annotation rotates to the UCS, and the hookline runs
  along the leader's horizontal_direction (set to UCS X), used by the
  renderer instead of world horizontal.
- MLEADER: context.text_direction = UCS X drives both the text rotation
  and, in the renderer, the text side and dogleg/landing direction.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-06-22 18:18:35 +03:00
1 parent 0c2a97e17e
commit 33314a5889
10 files changed
+160 -73

No files matched your search

+11
View File
@@ -217,6 +217,17 @@ impl DocumentTab {
)
}
/// World-space rotation angle (radians) of the active UCS X axis — the
/// default rotation for new text-bearing objects so their text aligns to
/// the user's coordinate system. Zero outside model space / with no UCS.
pub(super) fn ucs_rotation_angle(&self) -> f64 {
if self.scene.current_layout != "Model" {
return 0.0;
}
let (_, x, ..) = self.ucs_xform().axes();
(x.y as f64).atan2(x.x as f64)
}
/// Grid origin (render/wire space) and UCS→world rotation for grid snap and
/// the grid overlay. Identity / origin-at-zero outside model space.
pub(super) fn ucs_grid_basis(&self) -> (glam::Vec3, glam::Mat4) {
+3 -1
View File
@@ -632,7 +632,7 @@ impl super::OpenCADStudio {
let i = self.active_tab;
let Some(ed) = self.mtext_editor.take() else { return false };
let body_empty = ed.content.text().trim().is_empty();
let mt = ed.build_mtext();
let mut mt = ed.build_mtext();
if body_empty {
// Empty content: drop a new entity; leave an edited one untouched.
self.refresh_properties();
@@ -661,6 +661,8 @@ impl super::OpenCADStudio {
self.tabs[i].scene.bump_geometry();
self.tabs[i].dirty = true;
} else {
// Align new MText to the active UCS (text runs along the UCS X axis).
mt.rotation = self.tabs[i].ucs_rotation_angle();
self.push_undo_snapshot(i, "MTEXT");
self.commit_entity(EntityType::MText(mt));
self.tabs[i].dirty = true;
+2
View File
@@ -225,6 +225,8 @@ impl super::OpenCADStudio {
if !cur_style.is_empty() {
t.style = cur_style;
}
// Align new text to the active UCS (baseline along the UCS X axis).
t.rotation = self.tabs[i].ucs_rotation_angle();
self.push_undo_snapshot(i, "TEXT");
self.commit_entity(EntityType::Text(t));
self.tabs[i].dirty = true;