forked from 1b-agent/opencadstudio-upstream
Rearchitect SDF text from a document-wide collector to per-entity production during tessellation, so each entity owns its glyph quads and composite/nested text is covered for free (no re-explosion, no per-frame document walk). - WireModel gains `text_verts`: glyph quads ride on the wire, so they are cached by the tess memo, cloned on hit, and transformed by the block expand loop exactly like `points`. - Text arm (tessellate.rs) builds the quads when SDF is on and suppresses the strokes; render gathers text_verts from the viewport wire set (`gather_text_verts`, cached on wire content id) — the old collector is removed. Covers model, dimension, table, block-internal and paper-sheet text uniformly. - MLEADER text (lays out via layout_mtext, not the Text arm) gets its own SDF branch driven by the per-run GlyphRun. - Pick box comes from the rendered glyph quads (true text extent), not entity_aabb which mis-places the box for MTEXT; composite paths no longer clobber the empty text wire's tight AABB (dim pick box hugs the text). - Selection / hover highlight via a text-highlight overlay (upload_text_ highlight, keyed on selection_generation) mirroring the selected-wire xray overlay; base glyphs stay neutral so a deselect leaves no stale tint. - Debug: env OCS_TEXT_BOX draws each run's glyph-bounds rectangle. Co-Authored-By: Claude Opus 4.8 <[email protected]>
94 lines
3.0 KiB
Rust
94 lines
3.0 KiB
Rust
// DIST command — measure distance and angle between two picked points.
|
||
|
||
use glam::DVec3;
|
||
|
||
use crate::command::{CadCommand, CmdResult};
|
||
use crate::scene::model::wire_model::WireModel;
|
||
|
||
pub struct DistCommand {
|
||
// Keep the picked point in full f64 precision. Downcasting to f32 here
|
||
// loses ~0.03–0.06 units at survey-scale coordinates (e.g. eastings near
|
||
// 5e5), which made snapped-endpoint measurements read off by that much.
|
||
first: Option<DVec3>,
|
||
}
|
||
|
||
impl DistCommand {
|
||
pub fn new() -> Self {
|
||
Self { first: None }
|
||
}
|
||
}
|
||
|
||
impl CadCommand for DistCommand {
|
||
fn name(&self) -> &'static str {
|
||
"DIST"
|
||
}
|
||
|
||
fn prompt(&self) -> String {
|
||
if self.first.is_none() {
|
||
"DIST Specify first point:".into()
|
||
} else {
|
||
"DIST Specify second point:".into()
|
||
}
|
||
}
|
||
|
||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||
if let Some(p1) = self.first {
|
||
let delta = pt - p1;
|
||
let dist = delta.length();
|
||
let dx = delta.x;
|
||
let dy = delta.y; // drawing plane is world XY
|
||
let dz = delta.z; // elevation
|
||
|
||
// Angle in XY plane — degrees from +X
|
||
let angle_xy = dy.atan2(dx).to_degrees();
|
||
// Angle from XY plane toward Z (elevation angle)
|
||
let dist_xy = dx.hypot(dy);
|
||
let angle_z = dz.atan2(dist_xy).to_degrees();
|
||
|
||
let msg = format!(
|
||
"Distance = {dist:.4}, Angle in XY Plane = {angle_xy:.4}°, Angle from XY Plane = {angle_z:.4}°\n Delta X = {dx:.4}, Delta Y = {dy:.4}, Delta Z = {dz:.4}",
|
||
);
|
||
CmdResult::Measurement(msg)
|
||
} else {
|
||
self.first = Some(pt);
|
||
CmdResult::NeedPoint
|
||
}
|
||
}
|
||
|
||
fn on_enter(&mut self) -> CmdResult {
|
||
CmdResult::Cancel
|
||
}
|
||
|
||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||
let p1 = self.first?;
|
||
// The preview wire is purely visual, so f32 vertices are fine here.
|
||
Some(WireModel {
|
||
text_verts: Vec::new(),
|
||
name: "dist_preview".into(),
|
||
points: vec![
|
||
[p1.x as f32, p1.y as f32, p1.z as f32],
|
||
[pt.x as f32, pt.y as f32, pt.z as f32],
|
||
],
|
||
points_low: Vec::new(),
|
||
color: WireModel::CYAN,
|
||
selected: false,
|
||
pattern_length: 0.0,
|
||
pattern: [0.0; 8],
|
||
line_weight_px: 1.0,
|
||
snap_pts: vec![],
|
||
tangent_geoms: vec![],
|
||
aci: 0,
|
||
key_vertices: vec![],
|
||
aabb: WireModel::UNBOUNDED_AABB,
|
||
plinegen: true,
|
||
vp_scissor: None,
|
||
fill_tris: vec![],
|
||
fill_tris_low: Vec::new(),
|
||
})
|
||
}
|
||
}
|
||
|
||
|
||
// ── Autocomplete registry ─────────────────────────────────
|
||
inventory::submit!(crate::command::CommandRegistration { names: &["DI", "DIST"] }); // DistCommand
|