refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
//! `dynamic` arms and helpers, split out of the original `update.rs` (#mechanical decomposition).
|
|
|
|
|
|
|
|
|
|
|
|
#![allow(unused_imports)]
|
|
|
|
|
|
use super::util::*;
|
2026-06-27 11:50:11 +03:00
|
|
|
|
use super::{format_size, VIEWCUBE_HIT_SIZE};
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
use crate::app::helpers::{
|
|
|
|
|
|
ortho_constrain, parse_coord, polar_constrain_near, ucs_rotate_vec, ucs_to_wcs, ucs_z_axis,
|
|
|
|
|
|
CoordKind,
|
|
|
|
|
|
};
|
|
|
|
|
|
use crate::app::{Message, OpenCADStudio, POLY_START_DELAY_MS};
|
|
|
|
|
|
use crate::modules::ModuleEvent;
|
|
|
|
|
|
use crate::scene::pick::grip::{find_hit_grip, find_hit_grip_paper, find_hit_grip_rte, GripEdit};
|
|
|
|
|
|
use crate::scene::model::object::GripApply;
|
|
|
|
|
|
use crate::scene::{
|
2026-06-27 11:50:11 +03:00
|
|
|
|
self, hover_id, CubeRegion, Scene, VIEWCUBE_DRAW_PX, VIEWCUBE_PAD, VIEWCUBE_PX,
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
};
|
|
|
|
|
|
use crate::ui::PropertiesPanel;
|
|
|
|
|
|
use acadrust::types::Color as AcadColor;
|
|
|
|
|
|
use acadrust::{EntityType as AcadEntityType, Handle};
|
|
|
|
|
|
use iced::time::Instant;
|
|
|
|
|
|
use iced::{mouse, Point, Task};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl OpenCADStudio {
|
|
|
|
|
|
/// Rebuild the active tab's dynamic-input field set to match what the
|
|
|
|
|
|
/// command is currently asking for. Called on cursor move and after
|
|
|
|
|
|
/// command-state changes. The field set only changes shape when the
|
|
|
|
|
|
/// command's `dyn_field()` or the presence of a base point changes;
|
|
|
|
|
|
/// existing typed buffers survive an unchanged shape.
|
|
|
|
|
|
pub(in crate::app) fn sync_dyn_fields(&mut self) {
|
|
|
|
|
|
use crate::app::document::{DynComponent, DynFieldEntry};
|
|
|
|
|
|
let i = self.active_tab;
|
|
|
|
|
|
if !self.dyn_input || self.tabs[i].active_cmd.is_none() {
|
|
|
|
|
|
self.tabs[i].dyn_fields.clear();
|
|
|
|
|
|
self.tabs[i].dyn_active = 0;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
// A command may describe its step explicitly via `dyn_spec()` — that
|
|
|
|
|
|
// takes full control of the boxes, guide and anchor. Otherwise fall
|
|
|
|
|
|
// back to the legacy `dyn_field()` shaping below.
|
|
|
|
|
|
if let Some(spec) = self.tabs[i].active_cmd.as_ref().and_then(|c| c.dyn_spec()) {
|
|
|
|
|
|
self.apply_dyn_spec(i, spec);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
let field = self.tabs[i]
|
|
|
|
|
|
.active_cmd
|
|
|
|
|
|
.as_ref()
|
|
|
|
|
|
.map(|c| c.dyn_field())
|
|
|
|
|
|
.unwrap_or(crate::command::DynField::Point);
|
|
|
|
|
|
// A text-input step reads a single scalar. The overlay shows one box
|
|
|
|
|
|
// the user types into (or, when the command supplies a live value,
|
|
|
|
|
|
// sets by moving the cursor); on commit the host routes it to
|
|
|
|
|
|
// `on_text_input` instead of `on_point`. A distance prompt keeps the
|
|
|
|
|
|
// `Distance` box (so a perpendicular-distance live value reads
|
|
|
|
|
|
// naturally); everything else uses the typed-only `Scalar` box.
|
|
|
|
|
|
let wants_text = self.tabs[i]
|
|
|
|
|
|
.active_cmd
|
|
|
|
|
|
.as_ref()
|
|
|
|
|
|
.map(|c| c.wants_text_input())
|
|
|
|
|
|
.unwrap_or(false);
|
|
|
|
|
|
// A point step that also accepts keyword letters (PLINE A/L/C…) keeps
|
|
|
|
|
|
// its polar boxes: only letters reach the command line, digits stay
|
|
|
|
|
|
// coordinates. So such a step is NOT treated as a text-only prompt.
|
|
|
|
|
|
let point_keywords = self.tabs[i]
|
|
|
|
|
|
.active_cmd
|
|
|
|
|
|
.as_ref()
|
|
|
|
|
|
.map(|c| c.point_step_accepts_keywords())
|
|
|
|
|
|
.unwrap_or(false);
|
|
|
|
|
|
let wants_text = wants_text && !point_keywords;
|
|
|
|
|
|
// A step that hit-tests for an object (entity / structure pick) has no
|
|
|
|
|
|
// coordinate to enter — clicks select, they don't place a point. Show
|
|
|
|
|
|
// no coordinate box so the cursor stays clean and typed option keywords
|
|
|
|
|
|
// (e.g. FILLET's "R") reach the command line instead of an X/Y field.
|
|
|
|
|
|
let picks_object = self.tabs[i]
|
|
|
|
|
|
.active_cmd
|
|
|
|
|
|
.as_ref()
|
|
|
|
|
|
.map(|c| c.needs_entity_pick() || c.needs_structure_point_pick())
|
|
|
|
|
|
.unwrap_or(false);
|
|
|
|
|
|
let has_base = self.last_point.is_some();
|
|
|
|
|
|
// While aligned to an OTRACK ray, the point step reads a single
|
|
|
|
|
|
// distance along the ray (issue #69) — show one Distance box.
|
|
|
|
|
|
let otrack_dist = self.otrack_active.is_some()
|
|
|
|
|
|
&& !wants_text
|
|
|
|
|
|
&& matches!(field, crate::command::DynField::Point);
|
|
|
|
|
|
let default: Vec<DynComponent> = match field {
|
|
|
|
|
|
_ if otrack_dist => vec![DynComponent::Distance],
|
|
|
|
|
|
crate::command::DynField::Distance => vec![DynComponent::Distance],
|
|
|
|
|
|
crate::command::DynField::Angle => vec![DynComponent::Angle],
|
|
|
|
|
|
crate::command::DynField::Scalar => vec![DynComponent::Scalar],
|
|
|
|
|
|
// A text prompt with the default `Point` field reads free text /
|
|
|
|
|
|
// a name / a keyword (which may itself contain digits) from the
|
|
|
|
|
|
// command line — show no scalar box, so digits reach the command
|
|
|
|
|
|
// line instead of being captured into a dyn buffer.
|
|
|
|
|
|
crate::command::DynField::Point if wants_text || picks_object => vec![],
|
|
|
|
|
|
crate::command::DynField::Point if has_base => {
|
|
|
|
|
|
vec![DynComponent::Distance, DynComponent::Angle]
|
|
|
|
|
|
}
|
|
|
|
|
|
crate::command::DynField::Point => vec![DynComponent::X, DynComponent::Y],
|
|
|
|
|
|
};
|
|
|
|
|
|
// Multiple shapes can satisfy the same command request — e.g. a
|
|
|
|
|
|
// `Point` is happy with either `[Distance, Angle]` (polar) or
|
|
|
|
|
|
// `[X, Y]` / `[X, Y, Z]` (cartesian). If the user already
|
|
|
|
|
|
// reshaped via `,` (see #35) the existing set is still a valid
|
|
|
|
|
|
// Point configuration and must not be reverted on every mouse
|
|
|
|
|
|
// move.
|
|
|
|
|
|
let current: Vec<DynComponent> = self.tabs[i]
|
|
|
|
|
|
.dyn_fields
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.map(|f| f.component)
|
|
|
|
|
|
.collect();
|
|
|
|
|
|
// Only treat a cartesian / polar variant as "good enough to keep"
|
|
|
|
|
|
// when the user explicitly reshaped via `,`. Otherwise we follow
|
|
|
|
|
|
// the command's default so e.g. clicking the first point of LINE
|
|
|
|
|
|
// flips a stale `[X, Y]` (from before there was a base) over to
|
|
|
|
|
|
// the polar `[Distance, Angle]` the prompt actually wants.
|
|
|
|
|
|
let current_is_acceptable = if self.dyn_user_reshaped && !wants_text && !picks_object {
|
|
|
|
|
|
match field {
|
|
|
|
|
|
crate::command::DynField::Distance => {
|
|
|
|
|
|
matches!(current.as_slice(), [DynComponent::Distance])
|
|
|
|
|
|
}
|
|
|
|
|
|
crate::command::DynField::Angle => {
|
|
|
|
|
|
matches!(current.as_slice(), [DynComponent::Angle])
|
|
|
|
|
|
}
|
|
|
|
|
|
crate::command::DynField::Scalar => {
|
|
|
|
|
|
matches!(current.as_slice(), [DynComponent::Scalar])
|
|
|
|
|
|
}
|
|
|
|
|
|
crate::command::DynField::Point => matches!(
|
|
|
|
|
|
current.as_slice(),
|
|
|
|
|
|
[DynComponent::Distance, DynComponent::Angle]
|
|
|
|
|
|
| [DynComponent::X, DynComponent::Y]
|
|
|
|
|
|
| [DynComponent::X, DynComponent::Y, DynComponent::Z]
|
|
|
|
|
|
),
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
current == default
|
|
|
|
|
|
};
|
|
|
|
|
|
if !current_is_acceptable {
|
|
|
|
|
|
self.tabs[i].dyn_fields = default.into_iter().map(DynFieldEntry::new).collect();
|
|
|
|
|
|
self.tabs[i].dyn_active = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
// Derive the guide + anchor for the legacy field set so the overlay
|
|
|
|
|
|
// draws the right construction without each command opting in.
|
|
|
|
|
|
let comps: Vec<DynComponent> =
|
|
|
|
|
|
self.tabs[i].dyn_fields.iter().map(|f| f.component).collect();
|
|
|
|
|
|
self.tabs[i].dyn_guide = match comps.as_slice() {
|
|
|
|
|
|
[DynComponent::Distance, DynComponent::Angle] | [DynComponent::Angle] => {
|
|
|
|
|
|
crate::command::DynGuide::Polar
|
|
|
|
|
|
}
|
|
|
|
|
|
[DynComponent::Distance] => crate::command::DynGuide::Radius,
|
|
|
|
|
|
_ => crate::command::DynGuide::None,
|
|
|
|
|
|
};
|
|
|
|
|
|
self.tabs[i].dyn_anchor = self.last_point;
|
|
|
|
|
|
self.tabs[i].dyn_ref = None;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Apply an explicit per-step [`DynSpec`](crate::command::DynSpec): rebuild
|
|
|
|
|
|
/// the boxes from its roles (preserving typed buffers when the role set is
|
|
|
|
|
|
/// unchanged), and set the guide + anchor.
|
|
|
|
|
|
|
|
|
|
|
|
pub(in crate::app) fn apply_dyn_spec(&mut self, i: usize, spec: crate::command::DynSpec) {
|
|
|
|
|
|
use crate::app::document::DynFieldEntry;
|
|
|
|
|
|
let new_roles: Vec<crate::command::DynRole> =
|
|
|
|
|
|
spec.fields.iter().map(|f| f.role).collect();
|
|
|
|
|
|
let cur_roles: Vec<crate::command::DynRole> =
|
|
|
|
|
|
self.tabs[i].dyn_fields.iter().map(|f| f.role).collect();
|
|
|
|
|
|
if cur_roles != new_roles {
|
|
|
|
|
|
self.tabs[i].dyn_fields =
|
|
|
|
|
|
spec.fields.iter().map(|f| DynFieldEntry::from_role(f.role)).collect();
|
|
|
|
|
|
self.tabs[i].dyn_active = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
self.tabs[i].dyn_guide = spec.guide;
|
|
|
|
|
|
self.tabs[i].dyn_anchor = match spec.anchor {
|
|
|
|
|
|
crate::command::DynAnchor::LastPoint => self.last_point,
|
fix(precision): keep typed coordinates exact in f64 (#311)
Typed values were quantized to the f32 grid before reaching the
entity (e.g. a length of 2000.8 committed as 2000.8020), worsening
with magnitude. The interaction point layer was all glam::Vec3 (f32).
Two-layer fix:
1. Coordinate backbone -> f64. last_point, last_cursor_world,
dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord,
the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance
paths, command-line coordinate entry and the coordinate readout all
compute in f64. Screen/GPU/alignment stays f32 (snap engine, render
geometry, projection, UCS-icon/viewcube drawing) with casts only at
those boundaries.
2. Per-command committed storage -> DVec3. on_point already received a
DVec3, but many command impls narrowed it back to f32 (let pt =
pt.as_vec3(); Vec<Vec3> fields) and re-widened at build, quantizing
the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN,
INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud,
wipeout, attdef, table, tolerance, array center, mview, plot_window,
plus the QDIM_PLACE handler and qdim_collect_points. Preview,
hit-test and rubber-band paths keep f32 at the boundary.
Hatch manual boundaries are left for a follow-up: HatchModel.boundary
is structurally Arc<Vec<[f32;2]>> and needs a model-level widen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:26:40 +03:00
|
|
|
|
crate::command::DynAnchor::Point(p) => Some(p),
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
};
|
fix(precision): keep typed coordinates exact in f64 (#311)
Typed values were quantized to the f32 grid before reaching the
entity (e.g. a length of 2000.8 committed as 2000.8020), worsening
with magnitude. The interaction point layer was all glam::Vec3 (f32).
Two-layer fix:
1. Coordinate backbone -> f64. last_point, last_cursor_world,
dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord,
the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance
paths, command-line coordinate entry and the coordinate readout all
compute in f64. Screen/GPU/alignment stays f32 (snap engine, render
geometry, projection, UCS-icon/viewcube drawing) with casts only at
those boundaries.
2. Per-command committed storage -> DVec3. on_point already received a
DVec3, but many command impls narrowed it back to f32 (let pt =
pt.as_vec3(); Vec<Vec3> fields) and re-widened at build, quantizing
the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN,
INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud,
wipeout, attdef, table, tolerance, array center, mview, plot_window,
plus the QDIM_PLACE handler and qdim_collect_points. Preview,
hit-test and rubber-band paths keep f32 at the boundary.
Hatch manual boundaries are left for a follow-up: HatchModel.boundary
is structurally Arc<Vec<[f32;2]>> and needs a model-level widen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:26:40 +03:00
|
|
|
|
self.tabs[i].dyn_ref = spec.ref_point;
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Track cursor dwell over a selected entity's grip. Sets
|
|
|
|
|
|
/// `grip_hover` while the cursor sits within `GRIP_THRESHOLD_PX` of
|
|
|
|
|
|
/// a grip and opens `grip_popup` once the dwell exceeds the
|
|
|
|
|
|
/// threshold. Cursor drift clears both.
|
|
|
|
|
|
/// After the active Model tile changes, mirror its stored visual style
|
|
|
|
|
|
/// into the tab so the picker shows it and the tile renders with it
|
|
|
|
|
|
/// (the active tile draws with the tab's live render mode).
|
|
|
|
|
|
|
|
|
|
|
|
/// Resolve the world point implied by the current dynamic-input field
|
|
|
|
|
|
/// values. Locked fields use their typed buffer; the rest fall back to
|
|
|
|
|
|
/// the live cursor-derived value. Returns `None` when the field set
|
|
|
|
|
|
/// isn't one we know how to turn into a point.
|
|
|
|
|
|
/// Hand the active command the current UCS (as a UCS→wire affine) so
|
|
|
|
|
|
/// axis-aligned constructions build square to the user's coordinate system.
|
|
|
|
|
|
/// No-op for commands that don't override `set_ucs`.
|
|
|
|
|
|
pub(in crate::app) fn push_ucs_to_cmd(&mut self, i: usize) {
|
|
|
|
|
|
let ucs = self.tabs[i].ucs_wire_affine();
|
|
|
|
|
|
if let Some(c) = self.tabs[i].active_cmd.as_mut() {
|
|
|
|
|
|
c.set_ucs(ucs);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-08 20:39:40 +03:00
|
|
|
|
/// Push the live Ctrl state into tab `i`'s active command before it builds a
|
|
|
|
|
|
/// preview/commit (arc-direction flip on `ARC_CONT`, etc.).
|
|
|
|
|
|
pub(in crate::app) fn push_ctrl_to_cmd(&mut self, i: usize) {
|
|
|
|
|
|
let ctrl = self.ctrl_down;
|
|
|
|
|
|
if let Some(c) = self.tabs[i].active_cmd.as_mut() {
|
|
|
|
|
|
c.set_ctrl(ctrl);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Cache the endpoint + exit tangent of a just-committed line/arc so a
|
|
|
|
|
|
/// following `ARC_CONT` starts tangentially from where drawing ended. Set to
|
|
|
|
|
|
/// `None` for any other entity kind. Uses `last_point` (the final pick) to
|
|
|
|
|
|
/// pick the endpoint the pen actually finished on.
|
|
|
|
|
|
pub(in crate::app) fn update_cont_anchor(&mut self, entity: &acadrust::EntityType) {
|
fix(precision): keep typed coordinates exact in f64 (#311)
Typed values were quantized to the f32 grid before reaching the
entity (e.g. a length of 2000.8 committed as 2000.8020), worsening
with magnitude. The interaction point layer was all glam::Vec3 (f32).
Two-layer fix:
1. Coordinate backbone -> f64. last_point, last_cursor_world,
dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord,
the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance
paths, command-line coordinate entry and the coordinate readout all
compute in f64. Screen/GPU/alignment stays f32 (snap engine, render
geometry, projection, UCS-icon/viewcube drawing) with casts only at
those boundaries.
2. Per-command committed storage -> DVec3. on_point already received a
DVec3, but many command impls narrowed it back to f32 (let pt =
pt.as_vec3(); Vec<Vec3> fields) and re-widened at build, quantizing
the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN,
INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud,
wipeout, attdef, table, tolerance, array center, mview, plot_window,
plus the QDIM_PLACE handler and qdim_collect_points. Preview,
hit-test and rubber-band paths keep f32 at the boundary.
Hatch manual boundaries are left for a follow-up: HatchModel.boundary
is structurally Arc<Vec<[f32;2]>> and needs a model-level widen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:26:40 +03:00
|
|
|
|
let last = self.last_point;
|
2026-07-08 20:39:40 +03:00
|
|
|
|
self.cont_anchor = crate::modules::draw::draw::arc::continue_anchor(entity, last);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
fix(precision): keep typed coordinates exact in f64 (#311)
Typed values were quantized to the f32 grid before reaching the
entity (e.g. a length of 2000.8 committed as 2000.8020), worsening
with magnitude. The interaction point layer was all glam::Vec3 (f32).
Two-layer fix:
1. Coordinate backbone -> f64. last_point, last_cursor_world,
dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord,
the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance
paths, command-line coordinate entry and the coordinate readout all
compute in f64. Screen/GPU/alignment stays f32 (snap engine, render
geometry, projection, UCS-icon/viewcube drawing) with casts only at
those boundaries.
2. Per-command committed storage -> DVec3. on_point already received a
DVec3, but many command impls narrowed it back to f32 (let pt =
pt.as_vec3(); Vec<Vec3> fields) and re-widened at build, quantizing
the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN,
INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud,
wipeout, attdef, table, tolerance, array center, mview, plot_window,
plus the QDIM_PLACE handler and qdim_collect_points. Preview,
hit-test and rubber-band paths keep f32 at the boundary.
Hatch manual boundaries are left for a follow-up: HatchModel.boundary
is structurally Arc<Vec<[f32;2]>> and needs a model-level widen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:26:40 +03:00
|
|
|
|
pub(in crate::app) fn dyn_resolve_point(&self) -> Option<glam::DVec3> {
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
use crate::app::document::DynComponent;
|
|
|
|
|
|
let i = self.active_tab;
|
|
|
|
|
|
let fields = &self.tabs[i].dyn_fields;
|
|
|
|
|
|
if fields.is_empty() {
|
|
|
|
|
|
return None;
|
|
|
|
|
|
}
|
|
|
|
|
|
let w = self.tabs[i].last_cursor_world;
|
|
|
|
|
|
let base = self.tabs[i]
|
|
|
|
|
|
.dyn_anchor
|
|
|
|
|
|
.or(self.last_point)
|
fix(precision): keep typed coordinates exact in f64 (#311)
Typed values were quantized to the f32 grid before reaching the
entity (e.g. a length of 2000.8 committed as 2000.8020), worsening
with magnitude. The interaction point layer was all glam::Vec3 (f32).
Two-layer fix:
1. Coordinate backbone -> f64. last_point, last_cursor_world,
dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord,
the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance
paths, command-line coordinate entry and the coordinate readout all
compute in f64. Screen/GPU/alignment stays f32 (snap engine, render
geometry, projection, UCS-icon/viewcube drawing) with casts only at
those boundaries.
2. Per-command committed storage -> DVec3. on_point already received a
DVec3, but many command impls narrowed it back to f32 (let pt =
pt.as_vec3(); Vec<Vec3> fields) and re-widened at build, quantizing
the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN,
INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud,
wipeout, attdef, table, tolerance, array center, mview, plot_window,
plus the QDIM_PLACE handler and qdim_collect_points. Preview,
hit-test and rubber-band paths keep f32 at the boundary.
Hatch manual boundaries are left for a follow-up: HatchModel.boundary
is structurally Arc<Vec<[f32;2]>> and needs a model-level widen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:26:40 +03:00
|
|
|
|
.unwrap_or(glam::DVec3::ZERO);
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
// Buffer value parsed as f32 (de-scaled by the role so a typed diameter
|
|
|
|
|
|
// becomes a radius), or the supplied geometric live value. Width/Height
|
2026-07-04 22:27:59 +03:00
|
|
|
|
// are shown unsigned, so a typed value normally takes the sign of the
|
|
|
|
|
|
// cursor's delta on that axis (`live` is the signed delta in the
|
|
|
|
|
|
// cartesian arms). But once the user separates the two values with `,`
|
|
|
|
|
|
// — e.g. the rectangle opposite corner entered as "50,50" — the entry
|
|
|
|
|
|
// is a cartesian coordinate pair, not cursor-signed W/H dimensions, so
|
|
|
|
|
|
// the typed sign must stay literal. `,` is the only thing that sets
|
|
|
|
|
|
// `dyn_user_reshaped`, so it cleanly distinguishes comma-cartesian entry
|
|
|
|
|
|
// from Tab-separated dimension entry (#269).
|
|
|
|
|
|
let comma_cartesian = self.dyn_user_reshaped;
|
fix(precision): keep typed coordinates exact in f64 (#311)
Typed values were quantized to the f32 grid before reaching the
entity (e.g. a length of 2000.8 committed as 2000.8020), worsening
with magnitude. The interaction point layer was all glam::Vec3 (f32).
Two-layer fix:
1. Coordinate backbone -> f64. last_point, last_cursor_world,
dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord,
the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance
paths, command-line coordinate entry and the coordinate readout all
compute in f64. Screen/GPU/alignment stays f32 (snap engine, render
geometry, projection, UCS-icon/viewcube drawing) with casts only at
those boundaries.
2. Per-command committed storage -> DVec3. on_point already received a
DVec3, but many command impls narrowed it back to f32 (let pt =
pt.as_vec3(); Vec<Vec3> fields) and re-widened at build, quantizing
the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN,
INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud,
wipeout, attdef, table, tolerance, array center, mview, plot_window,
plus the QDIM_PLACE handler and qdim_collect_points. Preview,
hit-test and rubber-band paths keep f32 at the boundary.
Hatch manual boundaries are left for a follow-up: HatchModel.boundary
is structurally Arc<Vec<[f32;2]>> and needs a model-level widen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:26:40 +03:00
|
|
|
|
let val = |idx: usize, live: f64| -> f64 {
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
match fields[idx]
|
|
|
|
|
|
.buffer
|
|
|
|
|
|
.as_ref()
|
|
|
|
|
|
.map(|s| s.trim().replace(',', "."))
|
fix(precision): keep typed coordinates exact in f64 (#311)
Typed values were quantized to the f32 grid before reaching the
entity (e.g. a length of 2000.8 committed as 2000.8020), worsening
with magnitude. The interaction point layer was all glam::Vec3 (f32).
Two-layer fix:
1. Coordinate backbone -> f64. last_point, last_cursor_world,
dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord,
the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance
paths, command-line coordinate entry and the coordinate readout all
compute in f64. Screen/GPU/alignment stays f32 (snap engine, render
geometry, projection, UCS-icon/viewcube drawing) with casts only at
those boundaries.
2. Per-command committed storage -> DVec3. on_point already received a
DVec3, but many command impls narrowed it back to f32 (let pt =
pt.as_vec3(); Vec<Vec3> fields) and re-widened at build, quantizing
the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN,
INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud,
wipeout, attdef, table, tolerance, array center, mview, plot_window,
plus the QDIM_PLACE handler and qdim_collect_points. Preview,
hit-test and rubber-band paths keep f32 at the boundary.
Hatch manual boundaries are left for a follow-up: HatchModel.boundary
is structurally Arc<Vec<[f32;2]>> and needs a model-level widen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:26:40 +03:00
|
|
|
|
.and_then(|s| crate::app::expr_eval::eval_number(&s))
|
|
|
|
|
|
.map(|v| v / fields[idx].role.value_scale() as f64)
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
{
|
|
|
|
|
|
Some(v) => {
|
|
|
|
|
|
if matches!(
|
|
|
|
|
|
fields[idx].role,
|
|
|
|
|
|
crate::command::DynRole::Width | crate::command::DynRole::Height
|
2026-07-04 22:27:59 +03:00
|
|
|
|
) && !comma_cartesian
|
|
|
|
|
|
{
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
v.abs().copysign(live)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
v
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
None => live,
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// Work in the active UCS frame: the cursor delta from the base is
|
|
|
|
|
|
// rotated into UCS, so typed cartesian/polar values are interpreted in
|
|
|
|
|
|
// the user's coordinate system and mapped back to world on return.
|
|
|
|
|
|
// (The delta is offset-invariant, so only the rotation matters.)
|
|
|
|
|
|
let xf = self.tabs[i].ucs_xform();
|
|
|
|
|
|
let d_ucs = xf.vec_to_ucs(w - base);
|
|
|
|
|
|
let dx = d_ucs.x;
|
|
|
|
|
|
let dy = d_ucs.y;
|
|
|
|
|
|
let dz = d_ucs.z;
|
|
|
|
|
|
let live_d = (dx * dx + dy * dy).sqrt();
|
|
|
|
|
|
let live_a = dy.atan2(dx); // radians, in the UCS plane
|
|
|
|
|
|
// A typed angle is shown unsigned (0..180); give it the sign of the
|
|
|
|
|
|
// cursor's current side so an entry made below the X axis sweeps
|
2026-07-21 11:55:17 +03:00
|
|
|
|
// downward to match the arc instead of mirroring up. An EXPLICIT
|
|
|
|
|
|
// `-`/`+` prefix keeps its literal sign though — "-30" must mean
|
|
|
|
|
|
// −30° (= 330°) regardless of where the cursor sits (#417).
|
|
|
|
|
|
// Untyped → live.
|
fix(precision): keep typed coordinates exact in f64 (#311)
Typed values were quantized to the f32 grid before reaching the
entity (e.g. a length of 2000.8 committed as 2000.8020), worsening
with magnitude. The interaction point layer was all glam::Vec3 (f32).
Two-layer fix:
1. Coordinate backbone -> f64. last_point, last_cursor_world,
dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord,
the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance
paths, command-line coordinate entry and the coordinate readout all
compute in f64. Screen/GPU/alignment stays f32 (snap engine, render
geometry, projection, UCS-icon/viewcube drawing) with casts only at
those boundaries.
2. Per-command committed storage -> DVec3. on_point already received a
DVec3, but many command impls narrowed it back to f32 (let pt =
pt.as_vec3(); Vec<Vec3> fields) and re-widened at build, quantizing
the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN,
INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud,
wipeout, attdef, table, tolerance, array center, mview, plot_window,
plus the QDIM_PLACE handler and qdim_collect_points. Preview,
hit-test and rubber-band paths keep f32 at the boundary.
Hatch manual boundaries are left for a follow-up: HatchModel.boundary
is structurally Arc<Vec<[f32;2]>> and needs a model-level widen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:26:40 +03:00
|
|
|
|
let angle_rad = |idx: usize| -> f64 {
|
2026-07-21 11:55:17 +03:00
|
|
|
|
let raw = fields[idx]
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
.buffer
|
|
|
|
|
|
.as_ref()
|
2026-07-21 11:55:17 +03:00
|
|
|
|
.map(|s| s.trim().replace(',', "."));
|
|
|
|
|
|
let explicit_sign = raw
|
|
|
|
|
|
.as_deref()
|
|
|
|
|
|
.is_some_and(|s| s.starts_with('-') || s.starts_with('+'));
|
|
|
|
|
|
match raw.and_then(|s| crate::app::expr_eval::eval_number(&s)) {
|
|
|
|
|
|
Some(v) if explicit_sign => v.to_radians(),
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
Some(mag) => mag.abs().to_radians().copysign(dy),
|
|
|
|
|
|
None => live_a,
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
let comps: Vec<DynComponent> = fields.iter().map(|f| f.component).collect();
|
|
|
|
|
|
// Perpendicular offset: a single distance measured square to the
|
|
|
|
|
|
// reference line (anchor → dyn_ref). The committed point lies on the
|
|
|
|
|
|
// perpendicular through the anchor at that offset; the command projects
|
|
|
|
|
|
// it. Untyped tracks the cursor's signed offset; typed takes the
|
|
|
|
|
|
// cursor's side.
|
|
|
|
|
|
if let (Some(ref_pt), [DynComponent::Distance]) =
|
|
|
|
|
|
(self.tabs[i].dyn_ref, comps.as_slice())
|
|
|
|
|
|
{
|
|
|
|
|
|
let axis = (ref_pt - base).normalize_or_zero();
|
fix(precision): keep typed coordinates exact in f64 (#311)
Typed values were quantized to the f32 grid before reaching the
entity (e.g. a length of 2000.8 committed as 2000.8020), worsening
with magnitude. The interaction point layer was all glam::Vec3 (f32).
Two-layer fix:
1. Coordinate backbone -> f64. last_point, last_cursor_world,
dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord,
the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance
paths, command-line coordinate entry and the coordinate readout all
compute in f64. Screen/GPU/alignment stays f32 (snap engine, render
geometry, projection, UCS-icon/viewcube drawing) with casts only at
those boundaries.
2. Per-command committed storage -> DVec3. on_point already received a
DVec3, but many command impls narrowed it back to f32 (let pt =
pt.as_vec3(); Vec<Vec3> fields) and re-widened at build, quantizing
the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN,
INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud,
wipeout, attdef, table, tolerance, array center, mview, plot_window,
plus the QDIM_PLACE handler and qdim_collect_points. Preview,
hit-test and rubber-band paths keep f32 at the boundary.
Hatch manual boundaries are left for a follow-up: HatchModel.boundary
is structurally Arc<Vec<[f32;2]>> and needs a model-level widen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:26:40 +03:00
|
|
|
|
let perp = glam::DVec3::new(-axis.y, axis.x, 0.0);
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
let signed = (w - base).dot(perp);
|
|
|
|
|
|
let typed = fields[0]
|
|
|
|
|
|
.buffer
|
|
|
|
|
|
.as_ref()
|
|
|
|
|
|
.map(|s| s.trim().replace(',', "."))
|
fix(precision): keep typed coordinates exact in f64 (#311)
Typed values were quantized to the f32 grid before reaching the
entity (e.g. a length of 2000.8 committed as 2000.8020), worsening
with magnitude. The interaction point layer was all glam::Vec3 (f32).
Two-layer fix:
1. Coordinate backbone -> f64. last_point, last_cursor_world,
dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord,
the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance
paths, command-line coordinate entry and the coordinate readout all
compute in f64. Screen/GPU/alignment stays f32 (snap engine, render
geometry, projection, UCS-icon/viewcube drawing) with casts only at
those boundaries.
2. Per-command committed storage -> DVec3. on_point already received a
DVec3, but many command impls narrowed it back to f32 (let pt =
pt.as_vec3(); Vec<Vec3> fields) and re-widened at build, quantizing
the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN,
INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud,
wipeout, attdef, table, tolerance, array center, mview, plot_window,
plus the QDIM_PLACE handler and qdim_collect_points. Preview,
hit-test and rubber-band paths keep f32 at the boundary.
Hatch manual boundaries are left for a follow-up: HatchModel.boundary
is structurally Arc<Vec<[f32;2]>> and needs a model-level widen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:26:40 +03:00
|
|
|
|
.and_then(|s| crate::app::expr_eval::eval_number(&s));
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
let h = match typed {
|
|
|
|
|
|
Some(v) => v.abs().copysign(signed),
|
|
|
|
|
|
None => signed,
|
|
|
|
|
|
};
|
|
|
|
|
|
return Some(base + perp * h);
|
|
|
|
|
|
}
|
|
|
|
|
|
// DYN-on defaults to RELATIVE coordinates when a base point is set
|
|
|
|
|
|
// (see #26 / #35). The live cartesian fallback is the cursor
|
|
|
|
|
|
// position relative to base; typed values are relative deltas.
|
|
|
|
|
|
let has_base = self.last_point.is_some();
|
|
|
|
|
|
// Relative result: base + the typed UCS-frame offset mapped to world.
|
fix(precision): keep typed coordinates exact in f64 (#311)
Typed values were quantized to the f32 grid before reaching the
entity (e.g. a length of 2000.8 committed as 2000.8020), worsening
with magnitude. The interaction point layer was all glam::Vec3 (f32).
Two-layer fix:
1. Coordinate backbone -> f64. last_point, last_cursor_world,
dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord,
the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance
paths, command-line coordinate entry and the coordinate readout all
compute in f64. Screen/GPU/alignment stays f32 (snap engine, render
geometry, projection, UCS-icon/viewcube drawing) with casts only at
those boundaries.
2. Per-command committed storage -> DVec3. on_point already received a
DVec3, but many command impls narrowed it back to f32 (let pt =
pt.as_vec3(); Vec<Vec3> fields) and re-widened at build, quantizing
the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN,
INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud,
wipeout, attdef, table, tolerance, array center, mview, plot_window,
plus the QDIM_PLACE handler and qdim_collect_points. Preview,
hit-test and rubber-band paths keep f32 at the boundary.
Hatch manual boundaries are left for a follow-up: HatchModel.boundary
is structurally Arc<Vec<[f32;2]>> and needs a model-level widen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:26:40 +03:00
|
|
|
|
let rel = |off_ucs: glam::DVec3| base + xf.vec_to_wcs(off_ucs);
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
match comps.as_slice() {
|
|
|
|
|
|
[DynComponent::X, DynComponent::Y] if has_base => {
|
fix(precision): keep typed coordinates exact in f64 (#311)
Typed values were quantized to the f32 grid before reaching the
entity (e.g. a length of 2000.8 committed as 2000.8020), worsening
with magnitude. The interaction point layer was all glam::Vec3 (f32).
Two-layer fix:
1. Coordinate backbone -> f64. last_point, last_cursor_world,
dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord,
the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance
paths, command-line coordinate entry and the coordinate readout all
compute in f64. Screen/GPU/alignment stays f32 (snap engine, render
geometry, projection, UCS-icon/viewcube drawing) with casts only at
those boundaries.
2. Per-command committed storage -> DVec3. on_point already received a
DVec3, but many command impls narrowed it back to f32 (let pt =
pt.as_vec3(); Vec<Vec3> fields) and re-widened at build, quantizing
the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN,
INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud,
wipeout, attdef, table, tolerance, array center, mview, plot_window,
plus the QDIM_PLACE handler and qdim_collect_points. Preview,
hit-test and rubber-band paths keep f32 at the boundary.
Hatch manual boundaries are left for a follow-up: HatchModel.boundary
is structurally Arc<Vec<[f32;2]>> and needs a model-level widen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:26:40 +03:00
|
|
|
|
Some(rel(glam::DVec3::new(val(0, dx), val(1, dy), 0.0)))
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
}
|
|
|
|
|
|
[DynComponent::X, DynComponent::Y] => {
|
fix(precision): keep typed coordinates exact in f64 (#311)
Typed values were quantized to the f32 grid before reaching the
entity (e.g. a length of 2000.8 committed as 2000.8020), worsening
with magnitude. The interaction point layer was all glam::Vec3 (f32).
Two-layer fix:
1. Coordinate backbone -> f64. last_point, last_cursor_world,
dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord,
the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance
paths, command-line coordinate entry and the coordinate readout all
compute in f64. Screen/GPU/alignment stays f32 (snap engine, render
geometry, projection, UCS-icon/viewcube drawing) with casts only at
those boundaries.
2. Per-command committed storage -> DVec3. on_point already received a
DVec3, but many command impls narrowed it back to f32 (let pt =
pt.as_vec3(); Vec<Vec3> fields) and re-widened at build, quantizing
the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN,
INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud,
wipeout, attdef, table, tolerance, array center, mview, plot_window,
plus the QDIM_PLACE handler and qdim_collect_points. Preview,
hit-test and rubber-band paths keep f32 at the boundary.
Hatch manual boundaries are left for a follow-up: HatchModel.boundary
is structurally Arc<Vec<[f32;2]>> and needs a model-level widen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:26:40 +03:00
|
|
|
|
Some(glam::DVec3::new(val(0, w.x), val(1, w.y), base.z))
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
}
|
|
|
|
|
|
[DynComponent::X, DynComponent::Y, DynComponent::Z] if has_base => {
|
fix(precision): keep typed coordinates exact in f64 (#311)
Typed values were quantized to the f32 grid before reaching the
entity (e.g. a length of 2000.8 committed as 2000.8020), worsening
with magnitude. The interaction point layer was all glam::Vec3 (f32).
Two-layer fix:
1. Coordinate backbone -> f64. last_point, last_cursor_world,
dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord,
the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance
paths, command-line coordinate entry and the coordinate readout all
compute in f64. Screen/GPU/alignment stays f32 (snap engine, render
geometry, projection, UCS-icon/viewcube drawing) with casts only at
those boundaries.
2. Per-command committed storage -> DVec3. on_point already received a
DVec3, but many command impls narrowed it back to f32 (let pt =
pt.as_vec3(); Vec<Vec3> fields) and re-widened at build, quantizing
the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN,
INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud,
wipeout, attdef, table, tolerance, array center, mview, plot_window,
plus the QDIM_PLACE handler and qdim_collect_points. Preview,
hit-test and rubber-band paths keep f32 at the boundary.
Hatch manual boundaries are left for a follow-up: HatchModel.boundary
is structurally Arc<Vec<[f32;2]>> and needs a model-level widen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:26:40 +03:00
|
|
|
|
Some(rel(glam::DVec3::new(val(0, dx), val(1, dy), val(2, dz))))
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
}
|
|
|
|
|
|
[DynComponent::X, DynComponent::Y, DynComponent::Z] => {
|
fix(precision): keep typed coordinates exact in f64 (#311)
Typed values were quantized to the f32 grid before reaching the
entity (e.g. a length of 2000.8 committed as 2000.8020), worsening
with magnitude. The interaction point layer was all glam::Vec3 (f32).
Two-layer fix:
1. Coordinate backbone -> f64. last_point, last_cursor_world,
dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord,
the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance
paths, command-line coordinate entry and the coordinate readout all
compute in f64. Screen/GPU/alignment stays f32 (snap engine, render
geometry, projection, UCS-icon/viewcube drawing) with casts only at
those boundaries.
2. Per-command committed storage -> DVec3. on_point already received a
DVec3, but many command impls narrowed it back to f32 (let pt =
pt.as_vec3(); Vec<Vec3> fields) and re-widened at build, quantizing
the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN,
INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud,
wipeout, attdef, table, tolerance, array center, mview, plot_window,
plus the QDIM_PLACE handler and qdim_collect_points. Preview,
hit-test and rubber-band paths keep f32 at the boundary.
Hatch manual boundaries are left for a follow-up: HatchModel.boundary
is structurally Arc<Vec<[f32;2]>> and needs a model-level widen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:26:40 +03:00
|
|
|
|
Some(glam::DVec3::new(val(0, w.x), val(1, w.y), val(2, base.z)))
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
}
|
|
|
|
|
|
[DynComponent::Distance, DynComponent::Angle] => {
|
|
|
|
|
|
let d = val(0, live_d);
|
|
|
|
|
|
let a = angle_rad(1);
|
fix(precision): keep typed coordinates exact in f64 (#311)
Typed values were quantized to the f32 grid before reaching the
entity (e.g. a length of 2000.8 committed as 2000.8020), worsening
with magnitude. The interaction point layer was all glam::Vec3 (f32).
Two-layer fix:
1. Coordinate backbone -> f64. last_point, last_cursor_world,
dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord,
the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance
paths, command-line coordinate entry and the coordinate readout all
compute in f64. Screen/GPU/alignment stays f32 (snap engine, render
geometry, projection, UCS-icon/viewcube drawing) with casts only at
those boundaries.
2. Per-command committed storage -> DVec3. on_point already received a
DVec3, but many command impls narrowed it back to f32 (let pt =
pt.as_vec3(); Vec<Vec3> fields) and re-widened at build, quantizing
the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN,
INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud,
wipeout, attdef, table, tolerance, array center, mview, plot_window,
plus the QDIM_PLACE handler and qdim_collect_points. Preview,
hit-test and rubber-band paths keep f32 at the boundary.
Hatch manual boundaries are left for a follow-up: HatchModel.boundary
is structurally Arc<Vec<[f32;2]>> and needs a model-level widen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:26:40 +03:00
|
|
|
|
Some(rel(glam::DVec3::new(d * a.cos(), d * a.sin(), 0.0)))
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
}
|
|
|
|
|
|
[DynComponent::Distance] => {
|
|
|
|
|
|
// Keep the cursor's direction (in UCS), override the magnitude.
|
fix(precision): keep typed coordinates exact in f64 (#311)
Typed values were quantized to the f32 grid before reaching the
entity (e.g. a length of 2000.8 committed as 2000.8020), worsening
with magnitude. The interaction point layer was all glam::Vec3 (f32).
Two-layer fix:
1. Coordinate backbone -> f64. last_point, last_cursor_world,
dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord,
the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance
paths, command-line coordinate entry and the coordinate readout all
compute in f64. Screen/GPU/alignment stays f32 (snap engine, render
geometry, projection, UCS-icon/viewcube drawing) with casts only at
those boundaries.
2. Per-command committed storage -> DVec3. on_point already received a
DVec3, but many command impls narrowed it back to f32 (let pt =
pt.as_vec3(); Vec<Vec3> fields) and re-widened at build, quantizing
the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN,
INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud,
wipeout, attdef, table, tolerance, array center, mview, plot_window,
plus the QDIM_PLACE handler and qdim_collect_points. Preview,
hit-test and rubber-band paths keep f32 at the boundary.
Hatch manual boundaries are left for a follow-up: HatchModel.boundary
is structurally Arc<Vec<[f32;2]>> and needs a model-level widen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:26:40 +03:00
|
|
|
|
let dir = glam::DVec3::new(dx, dy, 0.0).normalize_or(glam::DVec3::X);
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
Some(rel(dir * val(0, live_d)))
|
|
|
|
|
|
}
|
|
|
|
|
|
[DynComponent::Angle] => {
|
|
|
|
|
|
// Standalone angle (e.g. ROTATE): the typed value is an
|
|
|
|
|
|
// absolute CCW angle in the UCS plane, not a cursor-signed
|
|
|
|
|
|
// magnitude — keep it literal. Only the polar Distance+Angle
|
|
|
|
|
|
// pair uses the cursor-signed `angle_rad`.
|
|
|
|
|
|
let a = val(0, live_a.to_degrees()).to_radians();
|
fix(precision): keep typed coordinates exact in f64 (#311)
Typed values were quantized to the f32 grid before reaching the
entity (e.g. a length of 2000.8 committed as 2000.8020), worsening
with magnitude. The interaction point layer was all glam::Vec3 (f32).
Two-layer fix:
1. Coordinate backbone -> f64. last_point, last_cursor_world,
dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord,
the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance
paths, command-line coordinate entry and the coordinate readout all
compute in f64. Screen/GPU/alignment stays f32 (snap engine, render
geometry, projection, UCS-icon/viewcube drawing) with casts only at
those boundaries.
2. Per-command committed storage -> DVec3. on_point already received a
DVec3, but many command impls narrowed it back to f32 (let pt =
pt.as_vec3(); Vec<Vec3> fields) and re-widened at build, quantizing
the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN,
INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud,
wipeout, attdef, table, tolerance, array center, mview, plot_window,
plus the QDIM_PLACE handler and qdim_collect_points. Preview,
hit-test and rubber-band paths keep f32 at the boundary.
Hatch manual boundaries are left for a follow-up: HatchModel.boundary
is structurally Arc<Vec<[f32;2]>> and needs a model-level widen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:26:40 +03:00
|
|
|
|
Some(rel(glam::DVec3::new(live_d * a.cos(), live_d * a.sin(), 0.0)))
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
}
|
|
|
|
|
|
_ => None,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Handle `,` while a dynamic-input field set is showing. Locks the
|
|
|
|
|
|
/// current field's buffer if it has one, then either advances within
|
|
|
|
|
|
/// the existing field set or reshapes it: a polar `[Distance, Angle]`
|
|
|
|
|
|
/// configuration becomes cartesian `[X(buf), Y]`, and a cartesian
|
|
|
|
|
|
/// `[X, Y]` configuration extends to `[X, Y, Z]`. Default fallthrough
|
|
|
|
|
|
/// is "advance to next field", matching `Tab`. See #35.
|
|
|
|
|
|
|
|
|
|
|
|
pub(in crate::app) fn dyn_comma_advance(&mut self) {
|
|
|
|
|
|
use crate::app::document::{DynComponent, DynFieldEntry};
|
|
|
|
|
|
let i = self.active_tab;
|
|
|
|
|
|
if self.tabs[i].dyn_fields.is_empty() {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
// The user picked a shape — `sync_dyn_fields` preserves it until
|
|
|
|
|
|
// the next commit / command-start clears the flag.
|
|
|
|
|
|
self.dyn_user_reshaped = true;
|
|
|
|
|
|
let active = self.tabs[i]
|
|
|
|
|
|
.dyn_active
|
|
|
|
|
|
.min(self.tabs[i].dyn_fields.len() - 1);
|
|
|
|
|
|
let comps: Vec<DynComponent> = self.tabs[i]
|
|
|
|
|
|
.dyn_fields
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.map(|f| f.component)
|
|
|
|
|
|
.collect();
|
|
|
|
|
|
let cur_buf = self.tabs[i].dyn_fields[active].buffer.clone();
|
|
|
|
|
|
match (comps.as_slice(), active) {
|
|
|
|
|
|
// First polar field — `,` switches to cartesian, locking the
|
|
|
|
|
|
// typed value as X.
|
|
|
|
|
|
([DynComponent::Distance, DynComponent::Angle], 0) | ([DynComponent::Distance], 0) => {
|
|
|
|
|
|
let mut x_field = DynFieldEntry::new(DynComponent::X);
|
|
|
|
|
|
x_field.buffer = cur_buf;
|
|
|
|
|
|
self.tabs[i].dyn_fields = vec![x_field, DynFieldEntry::new(DynComponent::Y)];
|
|
|
|
|
|
self.tabs[i].dyn_active = 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
// Already cartesian X (first field) — just advance to Y.
|
|
|
|
|
|
([DynComponent::X, DynComponent::Y], 0)
|
|
|
|
|
|
| ([DynComponent::X, DynComponent::Y, DynComponent::Z], 0) => {
|
|
|
|
|
|
self.tabs[i].dyn_active = 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
// Cartesian Y — extend to 3-D by appending Z.
|
|
|
|
|
|
([DynComponent::X, DynComponent::Y], 1) => {
|
|
|
|
|
|
self.tabs[i]
|
|
|
|
|
|
.dyn_fields
|
|
|
|
|
|
.push(DynFieldEntry::new(DynComponent::Z));
|
|
|
|
|
|
self.tabs[i].dyn_active = 2;
|
|
|
|
|
|
}
|
|
|
|
|
|
// Cartesian Y in the 3-D set — advance to Z.
|
|
|
|
|
|
([DynComponent::X, DynComponent::Y, DynComponent::Z], 1) => {
|
|
|
|
|
|
self.tabs[i].dyn_active = 2;
|
|
|
|
|
|
}
|
|
|
|
|
|
// Z, Angle, or any singleton: nothing further to advance to.
|
|
|
|
|
|
_ => {}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// If dynamic input has at least one locked (typed) field, resolve the
|
|
|
|
|
|
/// implied point, feed it to the active command as a point pick, reset
|
|
|
|
|
|
/// the field buffers, and return the resulting task. Returns `None`
|
|
|
|
|
|
/// when there is nothing typed, so the caller falls back to its normal
|
|
|
|
|
|
/// Enter handling.
|
|
|
|
|
|
/// Give a bare typed angle the sign of the cursor's side relative to the
|
|
|
|
|
|
/// step's reference direction, so a commit-as-text angle rotates/sweeps the
|
|
|
|
|
|
/// way the cursor is dragging. Only applies to steps with an `Angle` field;
|
|
|
|
|
|
/// an explicit `+`/`-` is left untouched. Returns the (possibly re-signed)
|
|
|
|
|
|
/// text to feed `on_text_input`.
|
|
|
|
|
|
|
|
|
|
|
|
pub(in crate::app) fn dyn_sign_angle_text(&self, i: usize, text: String) -> String {
|
|
|
|
|
|
let has_angle = self.tabs[i]
|
|
|
|
|
|
.dyn_fields
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.any(|f| f.role == crate::command::DynRole::Angle);
|
|
|
|
|
|
let t = text.trim();
|
|
|
|
|
|
if !has_angle || t.is_empty() || t.starts_with('-') || t.starts_with('+') {
|
|
|
|
|
|
return text;
|
|
|
|
|
|
}
|
|
|
|
|
|
if t.parse::<f32>().is_err() {
|
|
|
|
|
|
return text;
|
|
|
|
|
|
}
|
|
|
|
|
|
let anchor = self.tabs[i]
|
|
|
|
|
|
.dyn_anchor
|
|
|
|
|
|
.or(self.last_point)
|
fix(precision): keep typed coordinates exact in f64 (#311)
Typed values were quantized to the f32 grid before reaching the
entity (e.g. a length of 2000.8 committed as 2000.8020), worsening
with magnitude. The interaction point layer was all glam::Vec3 (f32).
Two-layer fix:
1. Coordinate backbone -> f64. last_point, last_cursor_world,
dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord,
the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance
paths, command-line coordinate entry and the coordinate readout all
compute in f64. Screen/GPU/alignment stays f32 (snap engine, render
geometry, projection, UCS-icon/viewcube drawing) with casts only at
those boundaries.
2. Per-command committed storage -> DVec3. on_point already received a
DVec3, but many command impls narrowed it back to f32 (let pt =
pt.as_vec3(); Vec<Vec3> fields) and re-widened at build, quantizing
the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN,
INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud,
wipeout, attdef, table, tolerance, array center, mview, plot_window,
plus the QDIM_PLACE handler and qdim_collect_points. Preview,
hit-test and rubber-band paths keep f32 at the boundary.
Hatch manual boundaries are left for a follow-up: HatchModel.boundary
is structurally Arc<Vec<[f32;2]>> and needs a model-level widen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:26:40 +03:00
|
|
|
|
.unwrap_or(glam::DVec3::ZERO);
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
let cur = self.tabs[i].last_cursor_world;
|
|
|
|
|
|
let a_cur = (cur.y - anchor.y).atan2(cur.x - anchor.x);
|
|
|
|
|
|
let a_ref = self.tabs[i]
|
|
|
|
|
|
.dyn_ref
|
|
|
|
|
|
.map(|r| (r.y - anchor.y).atan2(r.x - anchor.x))
|
|
|
|
|
|
.unwrap_or(0.0);
|
|
|
|
|
|
let mut d = a_cur - a_ref;
|
fix(precision): keep typed coordinates exact in f64 (#311)
Typed values were quantized to the f32 grid before reaching the
entity (e.g. a length of 2000.8 committed as 2000.8020), worsening
with magnitude. The interaction point layer was all glam::Vec3 (f32).
Two-layer fix:
1. Coordinate backbone -> f64. last_point, last_cursor_world,
dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord,
the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance
paths, command-line coordinate entry and the coordinate readout all
compute in f64. Screen/GPU/alignment stays f32 (snap engine, render
geometry, projection, UCS-icon/viewcube drawing) with casts only at
those boundaries.
2. Per-command committed storage -> DVec3. on_point already received a
DVec3, but many command impls narrowed it back to f32 (let pt =
pt.as_vec3(); Vec<Vec3> fields) and re-widened at build, quantizing
the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN,
INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud,
wipeout, attdef, table, tolerance, array center, mview, plot_window,
plus the QDIM_PLACE handler and qdim_collect_points. Preview,
hit-test and rubber-band paths keep f32 at the boundary.
Hatch manual boundaries are left for a follow-up: HatchModel.boundary
is structurally Arc<Vec<[f32;2]>> and needs a model-level widen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:26:40 +03:00
|
|
|
|
while d > std::f64::consts::PI {
|
|
|
|
|
|
d -= std::f64::consts::TAU;
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
}
|
fix(precision): keep typed coordinates exact in f64 (#311)
Typed values were quantized to the f32 grid before reaching the
entity (e.g. a length of 2000.8 committed as 2000.8020), worsening
with magnitude. The interaction point layer was all glam::Vec3 (f32).
Two-layer fix:
1. Coordinate backbone -> f64. last_point, last_cursor_world,
dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord,
the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance
paths, command-line coordinate entry and the coordinate readout all
compute in f64. Screen/GPU/alignment stays f32 (snap engine, render
geometry, projection, UCS-icon/viewcube drawing) with casts only at
those boundaries.
2. Per-command committed storage -> DVec3. on_point already received a
DVec3, but many command impls narrowed it back to f32 (let pt =
pt.as_vec3(); Vec<Vec3> fields) and re-widened at build, quantizing
the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN,
INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud,
wipeout, attdef, table, tolerance, array center, mview, plot_window,
plus the QDIM_PLACE handler and qdim_collect_points. Preview,
hit-test and rubber-band paths keep f32 at the boundary.
Hatch manual boundaries are left for a follow-up: HatchModel.boundary
is structurally Arc<Vec<[f32;2]>> and needs a model-level widen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:26:40 +03:00
|
|
|
|
while d <= -std::f64::consts::PI {
|
|
|
|
|
|
d += std::f64::consts::TAU;
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
}
|
|
|
|
|
|
if d < 0.0 {
|
|
|
|
|
|
format!("-{t}")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
text
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub(in crate::app) fn try_dyn_commit(&mut self) -> Option<Task<Message>> {
|
|
|
|
|
|
let i = self.active_tab;
|
|
|
|
|
|
if !self.dyn_input
|
|
|
|
|
|
|| self.tabs[i].active_cmd.is_none()
|
|
|
|
|
|
|| self.tabs[i].dyn_fields.is_empty()
|
|
|
|
|
|
|| !self.tabs[i].dyn_fields.iter().any(|f| f.locked())
|
|
|
|
|
|
{
|
|
|
|
|
|
return None;
|
|
|
|
|
|
}
|
|
|
|
|
|
// OTRACK: while aligned to a tracking ray, a typed value is a distance
|
|
|
|
|
|
// along the ray from the tracking point (issue #69).
|
|
|
|
|
|
if let Some((base, dir)) = self.otrack_active {
|
|
|
|
|
|
let wants_text = self.tabs[i]
|
|
|
|
|
|
.active_cmd
|
|
|
|
|
|
.as_ref()
|
|
|
|
|
|
.map(|c| c.wants_text_input())
|
|
|
|
|
|
.unwrap_or(false);
|
|
|
|
|
|
if !wants_text {
|
|
|
|
|
|
if let Some(text) = self.tabs[i]
|
|
|
|
|
|
.dyn_fields
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.find_map(|f| f.buffer.clone())
|
|
|
|
|
|
{
|
|
|
|
|
|
if let Some(dist) = crate::app::expr_eval::eval_number(text.trim()) {
|
fix(precision): keep typed coordinates exact in f64 (#311)
Typed values were quantized to the f32 grid before reaching the
entity (e.g. a length of 2000.8 committed as 2000.8020), worsening
with magnitude. The interaction point layer was all glam::Vec3 (f32).
Two-layer fix:
1. Coordinate backbone -> f64. last_point, last_cursor_world,
dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord,
the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance
paths, command-line coordinate entry and the coordinate readout all
compute in f64. Screen/GPU/alignment stays f32 (snap engine, render
geometry, projection, UCS-icon/viewcube drawing) with casts only at
those boundaries.
2. Per-command committed storage -> DVec3. on_point already received a
DVec3, but many command impls narrowed it back to f32 (let pt =
pt.as_vec3(); Vec<Vec3> fields) and re-widened at build, quantizing
the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN,
INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud,
wipeout, attdef, table, tolerance, array center, mview, plot_window,
plus the QDIM_PLACE handler and qdim_collect_points. Preview,
hit-test and rubber-band paths keep f32 at the boundary.
Hatch manual boundaries are left for a follow-up: HatchModel.boundary
is structurally Arc<Vec<[f32;2]>> and needs a model-level widen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:26:40 +03:00
|
|
|
|
// Distance is typed, so resolve in f64: the tracking ray
|
|
|
|
|
|
// origin/dir come from the screen-space snapper (f32) but
|
|
|
|
|
|
// the magnitude the user entered stays exact.
|
|
|
|
|
|
let pt = base.as_dvec3() + dir.as_dvec3() * dist;
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
self.last_point = Some(pt);
|
|
|
|
|
|
for f in self.tabs[i].dyn_fields.iter_mut() {
|
|
|
|
|
|
f.buffer = None;
|
|
|
|
|
|
}
|
|
|
|
|
|
self.tabs[i].dyn_active = 0;
|
|
|
|
|
|
self.dyn_user_reshaped = false;
|
|
|
|
|
|
self.sync_dyn_fields();
|
|
|
|
|
|
self.reset_tracking_after_point();
|
|
|
|
|
|
self.push_ucs_to_cmd(i);
|
fix(precision): keep typed coordinates exact in f64 (#311)
Typed values were quantized to the f32 grid before reaching the
entity (e.g. a length of 2000.8 committed as 2000.8020), worsening
with magnitude. The interaction point layer was all glam::Vec3 (f32).
Two-layer fix:
1. Coordinate backbone -> f64. last_point, last_cursor_world,
dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord,
the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance
paths, command-line coordinate entry and the coordinate readout all
compute in f64. Screen/GPU/alignment stays f32 (snap engine, render
geometry, projection, UCS-icon/viewcube drawing) with casts only at
those boundaries.
2. Per-command committed storage -> DVec3. on_point already received a
DVec3, but many command impls narrowed it back to f32 (let pt =
pt.as_vec3(); Vec<Vec3> fields) and re-widened at build, quantizing
the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN,
INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud,
wipeout, attdef, table, tolerance, array center, mview, plot_window,
plus the QDIM_PLACE handler and qdim_collect_points. Preview,
hit-test and rubber-band paths keep f32 at the boundary.
Hatch manual boundaries are left for a follow-up: HatchModel.boundary
is structurally Arc<Vec<[f32;2]>> and needs a model-level widen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:26:40 +03:00
|
|
|
|
let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(pt));
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
let task = result.map(|r| self.apply_cmd_result(r))?;
|
|
|
|
|
|
self.refresh_active_cmd_preview(i);
|
|
|
|
|
|
return Some(task);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// A text-input step reads its single box as a string and commits via
|
|
|
|
|
|
// `on_text_input` (a count, radius, distance) rather than resolving a
|
|
|
|
|
|
// point. Only the typed buffer matters here — a mouse-driven live
|
|
|
|
|
|
// value commits through the viewport click, not Enter.
|
|
|
|
|
|
// A point-with-keywords step (PLINE) commits a typed distance/angle as
|
|
|
|
|
|
// a point, not as text, so it is excluded here.
|
|
|
|
|
|
let wants_text = self.tabs[i]
|
|
|
|
|
|
.active_cmd
|
|
|
|
|
|
.as_ref()
|
|
|
|
|
|
.map(|c| {
|
|
|
|
|
|
(c.wants_text_input() && !c.point_step_accepts_keywords())
|
|
|
|
|
|
|| c.dyn_commit_as_text()
|
|
|
|
|
|
})
|
|
|
|
|
|
.unwrap_or(false);
|
|
|
|
|
|
if wants_text {
|
|
|
|
|
|
let text = self.tabs[i]
|
|
|
|
|
|
.dyn_fields
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.find_map(|f| f.buffer.clone())
|
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
|
let text = crate::app::expr_eval::eval_to_string(text.trim());
|
|
|
|
|
|
// Shared rule: a bare angle typed into a commit-as-text step takes
|
|
|
|
|
|
// the sign of the cursor's side relative to the reference, so the
|
|
|
|
|
|
// committed direction matches the drag (the box shows magnitude
|
|
|
|
|
|
// only). Commands receive an already-signed string.
|
|
|
|
|
|
let text = self.dyn_sign_angle_text(i, text);
|
|
|
|
|
|
let result = self.tabs[i]
|
|
|
|
|
|
.active_cmd
|
|
|
|
|
|
.as_mut()
|
|
|
|
|
|
.and_then(|c| c.on_text_input(&text));
|
|
|
|
|
|
for f in self.tabs[i].dyn_fields.iter_mut() {
|
|
|
|
|
|
f.buffer = None;
|
|
|
|
|
|
}
|
|
|
|
|
|
self.tabs[i].dyn_active = 0;
|
|
|
|
|
|
self.sync_dyn_fields();
|
|
|
|
|
|
let prompt = self.tabs[i].active_cmd.as_ref().map(|c| c.prompt());
|
|
|
|
|
|
if let Some(p) = prompt {
|
|
|
|
|
|
self.command_line.push_info(&p);
|
|
|
|
|
|
}
|
|
|
|
|
|
self.refresh_active_cmd_preview(i);
|
|
|
|
|
|
return Some(match result {
|
|
|
|
|
|
Some(r) => self.apply_cmd_result(r),
|
|
|
|
|
|
None => self.focus_cmd_input(),
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
let pt = self.dyn_resolve_point()?;
|
|
|
|
|
|
self.last_point = Some(pt);
|
|
|
|
|
|
self.dyn_user_reshaped = false;
|
|
|
|
|
|
self.sync_dyn_fields();
|
|
|
|
|
|
self.reset_tracking_after_point();
|
|
|
|
|
|
self.push_ucs_to_cmd(i);
|
fix(precision): keep typed coordinates exact in f64 (#311)
Typed values were quantized to the f32 grid before reaching the
entity (e.g. a length of 2000.8 committed as 2000.8020), worsening
with magnitude. The interaction point layer was all glam::Vec3 (f32).
Two-layer fix:
1. Coordinate backbone -> f64. last_point, last_cursor_world,
dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord,
the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance
paths, command-line coordinate entry and the coordinate readout all
compute in f64. Screen/GPU/alignment stays f32 (snap engine, render
geometry, projection, UCS-icon/viewcube drawing) with casts only at
those boundaries.
2. Per-command committed storage -> DVec3. on_point already received a
DVec3, but many command impls narrowed it back to f32 (let pt =
pt.as_vec3(); Vec<Vec3> fields) and re-widened at build, quantizing
the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN,
INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud,
wipeout, attdef, table, tolerance, array center, mview, plot_window,
plus the QDIM_PLACE handler and qdim_collect_points. Preview,
hit-test and rubber-band paths keep f32 at the boundary.
Hatch manual boundaries are left for a follow-up: HatchModel.boundary
is structurally Arc<Vec<[f32;2]>> and needs a model-level widen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:26:40 +03:00
|
|
|
|
let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(pt));
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
for f in self.tabs[i].dyn_fields.iter_mut() {
|
|
|
|
|
|
f.buffer = None;
|
|
|
|
|
|
}
|
|
|
|
|
|
self.tabs[i].dyn_active = 0;
|
|
|
|
|
|
let task = result.map(|r| self.apply_cmd_result(r))?;
|
|
|
|
|
|
// Match the command-line path: refresh the rubber-band preview
|
|
|
|
|
|
// so the next segment immediately starts from the new
|
|
|
|
|
|
// last_point even though no mouse-move fires after a typed
|
|
|
|
|
|
// coordinate. See #32.
|
|
|
|
|
|
self.refresh_active_cmd_preview(i);
|
|
|
|
|
|
Some(task)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Mutable access to the currently selected table style.
|
|
|
|
|
|
|
|
|
|
|
|
/// Re-run the active command's preview hook against the current
|
|
|
|
|
|
/// cursor world position. Keyboard-driven point commits (typed
|
|
|
|
|
|
/// coordinates in the command line or dynamic input) don't fire a
|
|
|
|
|
|
/// mouse-move event, so without this the rubber-band preview keeps
|
|
|
|
|
|
/// dangling from the previous `last_point` until the user actually
|
|
|
|
|
|
/// moves the mouse. See #32.
|
|
|
|
|
|
pub(in crate::app) fn refresh_active_cmd_preview(&mut self, i: usize) {
|
|
|
|
|
|
if self.tabs[i].active_cmd.is_none() {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-07-21 23:58:05 +03:00
|
|
|
|
// Re-resolve through the locked dyn fields first so a freshly typed
|
|
|
|
|
|
// value reshapes the preview immediately, without waiting for the
|
|
|
|
|
|
// next mouse move (#356). Idempotent for the polar/cartesian arms, so
|
|
|
|
|
|
// feeding the already-resolved cursor back in is stable.
|
|
|
|
|
|
if self.tabs[i].dyn_fields.iter().any(|f| f.buffer.is_some()) {
|
|
|
|
|
|
if let Some(r) = self.dyn_resolve_point() {
|
|
|
|
|
|
self.tabs[i].last_cursor_world = r;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
let cur = self.tabs[i].last_cursor_world;
|
|
|
|
|
|
let previews = self.tabs[i]
|
|
|
|
|
|
.active_cmd
|
|
|
|
|
|
.as_mut()
|
fix(precision): keep typed coordinates exact in f64 (#311)
Typed values were quantized to the f32 grid before reaching the
entity (e.g. a length of 2000.8 committed as 2000.8020), worsening
with magnitude. The interaction point layer was all glam::Vec3 (f32).
Two-layer fix:
1. Coordinate backbone -> f64. last_point, last_cursor_world,
dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord,
the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance
paths, command-line coordinate entry and the coordinate readout all
compute in f64. Screen/GPU/alignment stays f32 (snap engine, render
geometry, projection, UCS-icon/viewcube drawing) with casts only at
those boundaries.
2. Per-command committed storage -> DVec3. on_point already received a
DVec3, but many command impls narrowed it back to f32 (let pt =
pt.as_vec3(); Vec<Vec3> fields) and re-widened at build, quantizing
the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN,
INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud,
wipeout, attdef, table, tolerance, array center, mview, plot_window,
plus the QDIM_PLACE handler and qdim_collect_points. Preview,
hit-test and rubber-band paths keep f32 at the boundary.
Hatch manual boundaries are left for a follow-up: HatchModel.boundary
is structurally Arc<Vec<[f32;2]>> and needs a model-level widen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:26:40 +03:00
|
|
|
|
.map(|c| c.on_preview_wires(cur))
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
|
self.tabs[i].scene.set_preview_wires(previews);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|