forked from 1b-agent/opencadstudio-upstream
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>
This commit is contained in:
1 parent
8ed10ceb2a
commit
57710eec2b
47 files changed
+699
-606
No files matched your search
@@ -137,7 +137,7 @@ impl OpenCADStudio {
|
||||
};
|
||||
self.last_point = Some(wcs);
|
||||
self.push_ucs_to_cmd(i);
|
||||
let _ = self.feed_command(StepInput::Point(wcs.as_dvec3()));
|
||||
let _ = self.feed_command(StepInput::Point(wcs));
|
||||
} else {
|
||||
let _ = self.feed_command(StepInput::Text(token.to_string()));
|
||||
}
|
||||
@@ -1156,7 +1156,7 @@ impl OpenCADStudio {
|
||||
.scene
|
||||
.document
|
||||
.get_entity(handle)
|
||||
.and_then(|e| break_entity(e, p1.as_vec3(), p2.as_vec3()));
|
||||
.and_then(|e| break_entity(e, p1, p2));
|
||||
match replacement {
|
||||
Some(frags) => {
|
||||
let label = self.history_label_from_active_cmd(i, "BREAK");
|
||||
|
||||
+19
-19
@@ -468,7 +468,7 @@ impl OpenCADStudio {
|
||||
// dimension-line point and the gathered geometry now selected. Build
|
||||
// a continuous chain of linear dimensions across the endpoints.
|
||||
cmd if cmd.starts_with("QDIM_PLACE ") => {
|
||||
let nums: Vec<f32> = cmd
|
||||
let nums: Vec<f64> = cmd
|
||||
.split_whitespace()
|
||||
.skip(1)
|
||||
.filter_map(|s| s.parse().ok())
|
||||
@@ -476,14 +476,14 @@ impl OpenCADStudio {
|
||||
if nums.len() < 3 {
|
||||
return Some(Task::none());
|
||||
}
|
||||
let place = glam::Vec3::new(nums[0], nums[1], nums[2]);
|
||||
let place = glam::DVec3::new(nums[0], nums[1], nums[2]);
|
||||
let handles: Vec<acadrust::Handle> = self.tabs[i]
|
||||
.scene
|
||||
.selected_entities()
|
||||
.iter()
|
||||
.map(|(h, _)| *h)
|
||||
.collect();
|
||||
let mut pts: Vec<glam::Vec3> = Vec::new();
|
||||
let mut pts: Vec<glam::DVec3> = Vec::new();
|
||||
for h in &handles {
|
||||
if let Some(e) = self.tabs[i].scene.document.get_entity(*h) {
|
||||
qdim_collect_points(e, &mut pts);
|
||||
@@ -498,10 +498,10 @@ impl OpenCADStudio {
|
||||
// span dimensions horizontally (ordered by X), else vertically.
|
||||
let (minx, maxx) = pts
|
||||
.iter()
|
||||
.fold((f32::MAX, f32::MIN), |(a, b), p| (a.min(p.x), b.max(p.x)));
|
||||
.fold((f64::MAX, f64::MIN), |(a, b), p| (a.min(p.x), b.max(p.x)));
|
||||
let (miny, maxy) = pts
|
||||
.iter()
|
||||
.fold((f32::MAX, f32::MIN), |(a, b), p| (a.min(p.y), b.max(p.y)));
|
||||
.fold((f64::MAX, f64::MIN), |(a, b), p| (a.min(p.y), b.max(p.y)));
|
||||
let horizontal = (maxx - minx) >= (maxy - miny);
|
||||
if horizontal {
|
||||
pts.sort_by(|a, b| a.x.total_cmp(&b.x));
|
||||
@@ -516,8 +516,8 @@ impl OpenCADStudio {
|
||||
return Some(Task::none());
|
||||
}
|
||||
self.push_undo_snapshot(i, "QDIM");
|
||||
let v = |p: glam::Vec3| {
|
||||
acadrust::types::Vector3::new(p.x as f64, p.y as f64, p.z as f64)
|
||||
let v = |p: glam::DVec3| {
|
||||
acadrust::types::Vector3::new(p.x, p.y, p.z)
|
||||
};
|
||||
let mut made = 0usize;
|
||||
for w in pts.windows(2) {
|
||||
@@ -530,9 +530,9 @@ impl OpenCADStudio {
|
||||
};
|
||||
// Dim line passes through the picked perpendicular position.
|
||||
let def = if horizontal {
|
||||
glam::Vec3::new((p1.x + p2.x) * 0.5, place.y, 0.0)
|
||||
glam::DVec3::new((p1.x + p2.x) * 0.5, place.y, 0.0)
|
||||
} else {
|
||||
glam::Vec3::new(place.x, (p1.y + p2.y) * 0.5, 0.0)
|
||||
glam::DVec3::new(place.x, (p1.y + p2.y) * 0.5, 0.0)
|
||||
};
|
||||
dim.definition_point = v(def);
|
||||
dim.base.definition_point = v(def);
|
||||
@@ -1124,9 +1124,9 @@ fn find_last_linear_dim(
|
||||
|
||||
/// Collect candidate dimension endpoints from an entity for QDIM — line ends,
|
||||
/// polyline vertices, arc endpoints — in world space.
|
||||
fn qdim_collect_points(e: &acadrust::EntityType, out: &mut Vec<glam::Vec3>) {
|
||||
fn qdim_collect_points(e: &acadrust::EntityType, out: &mut Vec<glam::DVec3>) {
|
||||
use acadrust::EntityType as ET;
|
||||
let p = |v: &acadrust::types::Vector3| glam::Vec3::new(v.x as f32, v.y as f32, v.z as f32);
|
||||
let p = |v: &acadrust::types::Vector3| glam::DVec3::new(v.x, v.y, v.z);
|
||||
match e {
|
||||
ET::Line(l) => {
|
||||
out.push(p(&l.start));
|
||||
@@ -1134,10 +1134,10 @@ fn qdim_collect_points(e: &acadrust::EntityType, out: &mut Vec<glam::Vec3>) {
|
||||
}
|
||||
ET::LwPolyline(pl) => {
|
||||
for v in &pl.vertices {
|
||||
out.push(glam::Vec3::new(
|
||||
v.location.x as f32,
|
||||
v.location.y as f32,
|
||||
pl.elevation as f32,
|
||||
out.push(glam::DVec3::new(
|
||||
v.location.x,
|
||||
v.location.y,
|
||||
pl.elevation,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1148,10 +1148,10 @@ fn qdim_collect_points(e: &acadrust::EntityType, out: &mut Vec<glam::Vec3>) {
|
||||
}
|
||||
ET::Arc(a) => {
|
||||
for &ang in &[a.start_angle, a.end_angle] {
|
||||
out.push(glam::Vec3::new(
|
||||
(a.center.x + a.radius * ang.cos()) as f32,
|
||||
(a.center.y + a.radius * ang.sin()) as f32,
|
||||
a.center.z as f32,
|
||||
out.push(glam::DVec3::new(
|
||||
a.center.x + a.radius * ang.cos(),
|
||||
a.center.y + a.radius * ang.sin(),
|
||||
a.center.z,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,13 +282,9 @@ impl OpenCADStudio {
|
||||
let current = self.tabs[i].active_ucs.as_ref();
|
||||
let origin = current
|
||||
.map(|u| {
|
||||
glam::Vec3::new(
|
||||
u.origin.x as f32,
|
||||
u.origin.y as f32,
|
||||
u.origin.z as f32,
|
||||
)
|
||||
glam::DVec3::new(u.origin.x, u.origin.y, u.origin.z)
|
||||
})
|
||||
.unwrap_or(glam::Vec3::ZERO);
|
||||
.unwrap_or(glam::DVec3::ZERO);
|
||||
let mut new_ucs = ucs_rotated_z(origin, rad);
|
||||
// If already had axes, compose rotation on top
|
||||
if let Some(ref ucs) = self.tabs[i].active_ucs {
|
||||
@@ -302,7 +298,7 @@ impl OpenCADStudio {
|
||||
ucs.y_axis.y as f32,
|
||||
ucs.y_axis.z as f32,
|
||||
);
|
||||
let z_ax = ucs_z_axis(ucs);
|
||||
let z_ax = ucs_z_axis(ucs).as_vec3();
|
||||
let rot = glam::Quat::from_axis_angle(z_ax, rad);
|
||||
let nx = rot * old_x;
|
||||
let ny = rot * old_y;
|
||||
|
||||
+7
-7
@@ -169,7 +169,7 @@ pub(super) struct DocumentTab {
|
||||
pub(super) wireframe: bool,
|
||||
pub(super) render_mode: acadrust::entities::ViewportRenderMode,
|
||||
pub(super) visual_style: String,
|
||||
pub(super) last_cursor_world: glam::Vec3,
|
||||
pub(super) last_cursor_world: glam::DVec3,
|
||||
pub(super) last_cursor_screen: iced::Point,
|
||||
/// Base point (`App::last_point`) projected to viewport pixels, refreshed
|
||||
/// on cursor move. Lets the dynamic-input overlay place the distance label
|
||||
@@ -185,10 +185,10 @@ pub(super) struct DocumentTab {
|
||||
pub(super) dyn_guide: crate::command::DynGuide,
|
||||
/// World-space anchor the current step's values are measured from. `None`
|
||||
/// falls back to `App::last_point`.
|
||||
pub(super) dyn_anchor: Option<glam::Vec3>,
|
||||
pub(super) dyn_anchor: Option<glam::DVec3>,
|
||||
/// Far end of a reference line through `dyn_anchor` (for the `Perp` guide:
|
||||
/// the base edge / major axis the offset is measured square to).
|
||||
pub(super) dyn_ref: Option<glam::Vec3>,
|
||||
pub(super) dyn_ref: Option<glam::DVec3>,
|
||||
/// `dyn_ref` projected to viewport pixels.
|
||||
pub(super) dyn_ref_screen: Option<iced::Point>,
|
||||
/// Index of the field that TAB has focused (the one keystrokes edit).
|
||||
@@ -360,9 +360,9 @@ impl DocumentTab {
|
||||
let (o, x, y, z) = self.ucs_xform().axes();
|
||||
let origin = glam::Vec3::new(o.x as f32, o.y as f32, o.z as f32);
|
||||
glam::Mat4::from_cols(
|
||||
x.extend(0.0),
|
||||
y.extend(0.0),
|
||||
z.extend(0.0),
|
||||
x.as_vec3().extend(0.0),
|
||||
y.as_vec3().extend(0.0),
|
||||
z.as_vec3().extend(0.0),
|
||||
origin.extend(1.0),
|
||||
)
|
||||
}
|
||||
@@ -421,7 +421,7 @@ impl DocumentTab {
|
||||
wireframe: false,
|
||||
render_mode: acadrust::entities::ViewportRenderMode::Wireframe2D,
|
||||
visual_style: "Wireframe 2D".into(),
|
||||
last_cursor_world: glam::Vec3::ZERO,
|
||||
last_cursor_world: glam::DVec3::ZERO,
|
||||
last_cursor_screen: iced::Point::ORIGIN,
|
||||
last_point_screen: None,
|
||||
dyn_fields: Vec::new(),
|
||||
|
||||
+50
-50
@@ -20,7 +20,7 @@ pub(super) enum CoordKind {
|
||||
/// "x,y,z" → Vec3(x, y, z)
|
||||
/// A leading `@` marks the value relative to the last point; a leading
|
||||
/// `#` forces absolute. Separators: comma or semicolon.
|
||||
pub(super) fn parse_coord(text: &str) -> Option<(glam::Vec3, CoordKind)> {
|
||||
pub(super) fn parse_coord(text: &str) -> Option<(glam::DVec3, CoordKind)> {
|
||||
let trimmed = text.trim();
|
||||
let (kind, rest) = if let Some(r) = trimmed.strip_prefix('@') {
|
||||
(CoordKind::Relative, r)
|
||||
@@ -29,14 +29,14 @@ pub(super) fn parse_coord(text: &str) -> Option<(glam::Vec3, CoordKind)> {
|
||||
} else {
|
||||
(CoordKind::Default, trimmed)
|
||||
};
|
||||
let parts: Vec<f32> = rest
|
||||
let parts: Vec<f64> = rest
|
||||
.split(|c| c == ',' || c == ';')
|
||||
.map(|s| s.trim())
|
||||
.filter_map(|s| s.parse().ok())
|
||||
.collect();
|
||||
match parts.as_slice() {
|
||||
[x, y] => Some((glam::Vec3::new(*x, *y, 0.0), kind)),
|
||||
[x, y, z] => Some((glam::Vec3::new(*x, *y, *z), kind)),
|
||||
[x, y] => Some((glam::DVec3::new(*x, *y, 0.0), kind)),
|
||||
[x, y, z] => Some((glam::DVec3::new(*x, *y, *z), kind)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -54,29 +54,28 @@ pub(super) fn parse_coord(text: &str) -> Option<(glam::Vec3, CoordKind)> {
|
||||
/// is just the transpose (the dot products in `to_ucs`); no matrix inversion.
|
||||
#[derive(Clone, Copy)]
|
||||
pub(super) struct UcsXform {
|
||||
origin: glam::Vec3,
|
||||
x: glam::Vec3,
|
||||
y: glam::Vec3,
|
||||
z: glam::Vec3,
|
||||
origin: glam::DVec3,
|
||||
x: glam::DVec3,
|
||||
y: glam::DVec3,
|
||||
z: glam::DVec3,
|
||||
}
|
||||
|
||||
impl UcsXform {
|
||||
/// Plain WCS — no active UCS.
|
||||
pub(super) fn identity() -> Self {
|
||||
Self {
|
||||
origin: glam::Vec3::ZERO,
|
||||
x: glam::Vec3::X,
|
||||
y: glam::Vec3::Y,
|
||||
z: glam::Vec3::Z,
|
||||
origin: glam::DVec3::ZERO,
|
||||
x: glam::DVec3::X,
|
||||
y: glam::DVec3::Y,
|
||||
z: glam::DVec3::Z,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn from_ucs(ucs: &Ucs) -> Self {
|
||||
let v =
|
||||
|a: acadrust::types::Vector3| glam::Vec3::new(a.x as f32, a.y as f32, a.z as f32);
|
||||
let x = v(ucs.x_axis).normalize_or(glam::Vec3::X);
|
||||
let y = v(ucs.y_axis).normalize_or(glam::Vec3::Y);
|
||||
let z = x.cross(y).normalize_or(glam::Vec3::Z);
|
||||
let v = |a: acadrust::types::Vector3| glam::DVec3::new(a.x, a.y, a.z);
|
||||
let x = v(ucs.x_axis).normalize_or(glam::DVec3::X);
|
||||
let y = v(ucs.y_axis).normalize_or(glam::DVec3::Y);
|
||||
let z = x.cross(y).normalize_or(glam::DVec3::Z);
|
||||
Self { origin: v(ucs.origin), x, y, z }
|
||||
}
|
||||
|
||||
@@ -86,45 +85,46 @@ impl UcsXform {
|
||||
|
||||
/// True when this is plain WCS — lets callers skip the conversion.
|
||||
pub(super) fn is_identity(&self) -> bool {
|
||||
self.origin == glam::Vec3::ZERO
|
||||
&& self.x == glam::Vec3::X
|
||||
&& self.y == glam::Vec3::Y
|
||||
&& self.z == glam::Vec3::Z
|
||||
self.origin == glam::DVec3::ZERO
|
||||
&& self.x == glam::DVec3::X
|
||||
&& self.y == glam::DVec3::Y
|
||||
&& self.z == glam::DVec3::Z
|
||||
}
|
||||
|
||||
/// UCS point → WCS.
|
||||
pub(super) fn to_wcs(&self, p: glam::Vec3) -> glam::Vec3 {
|
||||
pub(super) fn to_wcs(&self, p: glam::DVec3) -> glam::DVec3 {
|
||||
self.origin + self.x * p.x + self.y * p.y + self.z * p.z
|
||||
}
|
||||
|
||||
/// WCS point → UCS.
|
||||
pub(super) fn to_ucs(&self, p: glam::Vec3) -> glam::Vec3 {
|
||||
pub(super) fn to_ucs(&self, p: glam::DVec3) -> glam::DVec3 {
|
||||
let d = p - self.origin;
|
||||
glam::Vec3::new(d.dot(self.x), d.dot(self.y), d.dot(self.z))
|
||||
glam::DVec3::new(d.dot(self.x), d.dot(self.y), d.dot(self.z))
|
||||
}
|
||||
|
||||
/// UCS direction → WCS (rotation only, no origin shift).
|
||||
pub(super) fn vec_to_wcs(&self, v: glam::Vec3) -> glam::Vec3 {
|
||||
pub(super) fn vec_to_wcs(&self, v: glam::DVec3) -> glam::DVec3 {
|
||||
self.x * v.x + self.y * v.y + self.z * v.z
|
||||
}
|
||||
|
||||
/// WCS direction → UCS (rotation only, no origin shift).
|
||||
pub(super) fn vec_to_ucs(&self, v: glam::Vec3) -> glam::Vec3 {
|
||||
glam::Vec3::new(v.dot(self.x), v.dot(self.y), v.dot(self.z))
|
||||
pub(super) fn vec_to_ucs(&self, v: glam::DVec3) -> glam::DVec3 {
|
||||
glam::DVec3::new(v.dot(self.x), v.dot(self.y), v.dot(self.z))
|
||||
}
|
||||
|
||||
/// `(origin, x, y, z)` axes in WCS — for drawing the UCS icon.
|
||||
pub(super) fn axes(&self) -> (glam::Vec3, glam::Vec3, glam::Vec3, glam::Vec3) {
|
||||
pub(super) fn axes(&self) -> (glam::DVec3, glam::DVec3, glam::DVec3, glam::DVec3) {
|
||||
(self.origin, self.x, self.y, self.z)
|
||||
}
|
||||
|
||||
/// UCS→world rotation matrix (columns = UCS axes). For consumers that take
|
||||
/// a `Mat4` rotation directly (ViewCube, OTRACK ray directions).
|
||||
/// a `Mat4` rotation directly (ViewCube, OTRACK ray directions). The GPU /
|
||||
/// screen layer is f32, so the axes downcast here at the boundary.
|
||||
pub(super) fn rotation_mat(&self) -> glam::Mat4 {
|
||||
glam::Mat4::from_cols(
|
||||
self.x.extend(0.0),
|
||||
self.y.extend(0.0),
|
||||
self.z.extend(0.0),
|
||||
self.x.as_vec3().extend(0.0),
|
||||
self.y.as_vec3().extend(0.0),
|
||||
self.z.as_vec3().extend(0.0),
|
||||
glam::Vec4::W,
|
||||
)
|
||||
}
|
||||
@@ -135,26 +135,26 @@ impl UcsXform {
|
||||
/// Rotate a UCS-local offset into WCS without applying the origin
|
||||
/// translation — used for relative coordinate entry, where only the
|
||||
/// axis orientation matters, not the UCS origin.
|
||||
pub(super) fn ucs_rotate_vec(offset: glam::Vec3, ucs: &Ucs) -> glam::Vec3 {
|
||||
pub(super) fn ucs_rotate_vec(offset: glam::DVec3, ucs: &Ucs) -> glam::DVec3 {
|
||||
UcsXform::from_ucs(ucs).vec_to_wcs(offset)
|
||||
}
|
||||
|
||||
/// Convert a point from UCS local coordinates to WCS.
|
||||
pub(super) fn ucs_to_wcs(pt: glam::Vec3, ucs: &Ucs) -> glam::Vec3 {
|
||||
pub(super) fn ucs_to_wcs(pt: glam::DVec3, ucs: &Ucs) -> glam::DVec3 {
|
||||
UcsXform::from_ucs(ucs).to_wcs(pt)
|
||||
}
|
||||
|
||||
/// Return the normalised Z axis of a UCS (cross product of X and Y axes).
|
||||
pub(super) fn ucs_z_axis(ucs: &Ucs) -> glam::Vec3 {
|
||||
pub(super) fn ucs_z_axis(ucs: &Ucs) -> glam::DVec3 {
|
||||
UcsXform::from_ucs(ucs).axes().3
|
||||
}
|
||||
|
||||
/// Build a UCS with `origin` and axes rotated by `angle_z_rad` around the Z axis.
|
||||
pub(super) fn ucs_rotated_z(origin: glam::Vec3, angle_z: f32) -> Ucs {
|
||||
pub(super) fn ucs_rotated_z(origin: glam::DVec3, angle_z: f32) -> Ucs {
|
||||
let cos = angle_z.cos() as f64;
|
||||
let sin = angle_z.sin() as f64;
|
||||
let mut ucs = Ucs::new("*ACTIVE*");
|
||||
ucs.origin = acadrust::types::Vector3::new(origin.x as f64, origin.y as f64, origin.z as f64);
|
||||
ucs.origin = acadrust::types::Vector3::new(origin.x, origin.y, origin.z);
|
||||
ucs.x_axis = acadrust::types::Vector3::new(cos, sin, 0.0);
|
||||
ucs.y_axis = acadrust::types::Vector3::new(-sin, cos, 0.0);
|
||||
ucs
|
||||
@@ -181,15 +181,15 @@ pub(super) fn grid_plane_from_camera(pitch: f32, yaw: f32) -> GridPlane {
|
||||
/// Constrain `pt` to the nearest 90° direction from `base`, in the active UCS
|
||||
/// plane — ortho follows the user's coordinate system, not world axes. `xf` is
|
||||
/// identity for plain WCS, so the world-XY behaviour is unchanged there.
|
||||
pub(super) fn ortho_constrain(pt: glam::Vec3, base: glam::Vec3, xf: &UcsXform) -> glam::Vec3 {
|
||||
pub(super) fn ortho_constrain(pt: glam::DVec3, base: glam::DVec3, xf: &UcsXform) -> glam::DVec3 {
|
||||
let p = xf.to_ucs(pt);
|
||||
let b = xf.to_ucs(base);
|
||||
let dx = (p.x - b.x).abs();
|
||||
let dy = (p.y - b.y).abs();
|
||||
let c = if dx >= dy {
|
||||
glam::Vec3::new(p.x, b.y, p.z)
|
||||
glam::DVec3::new(p.x, b.y, p.z)
|
||||
} else {
|
||||
glam::Vec3::new(b.x, p.y, p.z)
|
||||
glam::DVec3::new(b.x, p.y, p.z)
|
||||
};
|
||||
xf.to_wcs(c)
|
||||
}
|
||||
@@ -197,11 +197,11 @@ pub(super) fn ortho_constrain(pt: glam::Vec3, base: glam::Vec3, xf: &UcsXform) -
|
||||
/// Constrain `pt` to the nearest polar angle multiple from `base`, measured in
|
||||
/// the active UCS plane (identity `xf` = world XY, Z-up).
|
||||
pub(super) fn polar_constrain(
|
||||
pt: glam::Vec3,
|
||||
base: glam::Vec3,
|
||||
pt: glam::DVec3,
|
||||
base: glam::DVec3,
|
||||
step_deg: f32,
|
||||
xf: &UcsXform,
|
||||
) -> glam::Vec3 {
|
||||
) -> glam::DVec3 {
|
||||
let p = xf.to_ucs(pt);
|
||||
let b = xf.to_ucs(base);
|
||||
let dx = p.x - b.x;
|
||||
@@ -210,10 +210,10 @@ pub(super) fn polar_constrain(
|
||||
if dist < 1e-6 {
|
||||
return pt;
|
||||
}
|
||||
let step = step_deg.to_radians();
|
||||
let step = (step_deg as f64).to_radians();
|
||||
let angle = dy.atan2(dx);
|
||||
let snapped = (angle / step).round() * step;
|
||||
xf.to_wcs(glam::Vec3::new(
|
||||
xf.to_wcs(glam::DVec3::new(
|
||||
b.x + dist * snapped.cos(),
|
||||
b.y + dist * snapped.sin(),
|
||||
p.z,
|
||||
@@ -224,18 +224,18 @@ pub(super) fn polar_constrain(
|
||||
/// screen pixels of the nearest polar ray; otherwise the cursor is left free
|
||||
/// so POLAR behaves as if off when pointing away from every angle (issue #70).
|
||||
pub(super) fn polar_constrain_near(
|
||||
pt: glam::Vec3,
|
||||
base: glam::Vec3,
|
||||
pt: glam::DVec3,
|
||||
base: glam::DVec3,
|
||||
step_deg: f32,
|
||||
view_rot: glam::Mat4,
|
||||
eye: glam::DVec3,
|
||||
bounds: iced::Rectangle,
|
||||
tol_px: f32,
|
||||
xf: &UcsXform,
|
||||
) -> glam::Vec3 {
|
||||
) -> glam::DVec3 {
|
||||
let snapped = polar_constrain(pt, base, step_deg, xf);
|
||||
let to_screen = |w: glam::Vec3| {
|
||||
let ndc = view_rot.project_point3((w.as_dvec3() - eye).as_vec3());
|
||||
let to_screen = |w: glam::DVec3| {
|
||||
let ndc = view_rot.project_point3((w - eye).as_vec3());
|
||||
(
|
||||
(ndc.x + 1.0) * 0.5 * bounds.width,
|
||||
(1.0 - ndc.y) * 0.5 * bounds.height,
|
||||
|
||||
+1
-1
@@ -416,7 +416,7 @@ pub(super) struct OpenCADStudio {
|
||||
/// Whether the layout/paper-space tabs are shown at the bottom (LAYOUTTAB).
|
||||
show_layout_tabs: bool,
|
||||
/// Last point committed by a drawing command — used as ortho/polar base.
|
||||
last_point: Option<glam::Vec3>,
|
||||
last_point: Option<glam::DVec3>,
|
||||
/// Endpoint + unit exit-tangent of the most recently drawn line/arc, so
|
||||
/// `ARC_CONT` (Arc → Continue) can start tangentially from where drawing
|
||||
/// ended. `None` once a non-line/arc entity is committed.
|
||||
|
||||
@@ -283,13 +283,16 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
// (issue #69).
|
||||
if let Some((base, dir)) = self.otrack_active {
|
||||
if let Some(dist) = crate::app::expr_eval::eval_number(text.trim()) {
|
||||
let pt = base + dir * dist as f32;
|
||||
// Typed distance along the tracking ray: keep the
|
||||
// magnitude exact in f64 (ray origin/dir are the
|
||||
// screen-space snapper's f32).
|
||||
let pt = base.as_dvec3() + dir.as_dvec3() * dist;
|
||||
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);
|
||||
let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(pt.as_dvec3()));
|
||||
let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(pt));
|
||||
if let Some(r) = result {
|
||||
let task = self.apply_cmd_result(r);
|
||||
self.refresh_active_cmd_preview(i);
|
||||
@@ -331,7 +334,7 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
self.sync_dyn_fields();
|
||||
self.reset_tracking_after_point();
|
||||
self.push_ucs_to_cmd(i);
|
||||
let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(wcs_pt.as_dvec3()));
|
||||
let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(wcs_pt));
|
||||
if let Some(r) = result {
|
||||
let task = self.apply_cmd_result(r);
|
||||
// The rubber-band preview that the command
|
||||
|
||||
+32
-31
@@ -171,9 +171,9 @@ impl OpenCADStudio {
|
||||
self.tabs[i].dyn_guide = spec.guide;
|
||||
self.tabs[i].dyn_anchor = match spec.anchor {
|
||||
crate::command::DynAnchor::LastPoint => self.last_point,
|
||||
crate::command::DynAnchor::Point(p) => Some(p.as_vec3()),
|
||||
crate::command::DynAnchor::Point(p) => Some(p),
|
||||
};
|
||||
self.tabs[i].dyn_ref = spec.ref_point.map(|v| v.as_vec3());
|
||||
self.tabs[i].dyn_ref = spec.ref_point;
|
||||
}
|
||||
|
||||
/// Track cursor dwell over a selected entity's grip. Sets
|
||||
@@ -212,13 +212,11 @@ impl OpenCADStudio {
|
||||
/// `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) {
|
||||
let last = self
|
||||
.last_point
|
||||
.map(|v| glam::DVec3::new(v.x as f64, v.y as f64, v.z as f64));
|
||||
let last = self.last_point;
|
||||
self.cont_anchor = crate::modules::draw::draw::arc::continue_anchor(entity, last);
|
||||
}
|
||||
|
||||
pub(in crate::app) fn dyn_resolve_point(&self) -> Option<glam::Vec3> {
|
||||
pub(in crate::app) fn dyn_resolve_point(&self) -> Option<glam::DVec3> {
|
||||
use crate::app::document::DynComponent;
|
||||
let i = self.active_tab;
|
||||
let fields = &self.tabs[i].dyn_fields;
|
||||
@@ -229,7 +227,7 @@ impl OpenCADStudio {
|
||||
let base = self.tabs[i]
|
||||
.dyn_anchor
|
||||
.or(self.last_point)
|
||||
.unwrap_or(glam::Vec3::ZERO);
|
||||
.unwrap_or(glam::DVec3::ZERO);
|
||||
// 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
|
||||
// are shown unsigned, so a typed value normally takes the sign of the
|
||||
@@ -241,13 +239,13 @@ impl OpenCADStudio {
|
||||
// `dyn_user_reshaped`, so it cleanly distinguishes comma-cartesian entry
|
||||
// from Tab-separated dimension entry (#269).
|
||||
let comma_cartesian = self.dyn_user_reshaped;
|
||||
let val = |idx: usize, live: f32| -> f32 {
|
||||
let val = |idx: usize, live: f64| -> f64 {
|
||||
match fields[idx]
|
||||
.buffer
|
||||
.as_ref()
|
||||
.map(|s| s.trim().replace(',', "."))
|
||||
.and_then(|s| crate::app::expr_eval::eval_number(&s).map(|v| v as f32))
|
||||
.map(|v| v / fields[idx].role.value_scale())
|
||||
.and_then(|s| crate::app::expr_eval::eval_number(&s))
|
||||
.map(|v| v / fields[idx].role.value_scale() as f64)
|
||||
{
|
||||
Some(v) => {
|
||||
if matches!(
|
||||
@@ -277,12 +275,12 @@ impl OpenCADStudio {
|
||||
// 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
|
||||
// downward to match the arc instead of mirroring up. Untyped → live.
|
||||
let angle_rad = |idx: usize| -> f32 {
|
||||
let angle_rad = |idx: usize| -> f64 {
|
||||
match fields[idx]
|
||||
.buffer
|
||||
.as_ref()
|
||||
.map(|s| s.trim().replace(',', "."))
|
||||
.and_then(|s| crate::app::expr_eval::eval_number(&s).map(|v| v as f32))
|
||||
.and_then(|s| crate::app::expr_eval::eval_number(&s))
|
||||
{
|
||||
Some(mag) => mag.abs().to_radians().copysign(dy),
|
||||
None => live_a,
|
||||
@@ -298,13 +296,13 @@ impl OpenCADStudio {
|
||||
(self.tabs[i].dyn_ref, comps.as_slice())
|
||||
{
|
||||
let axis = (ref_pt - base).normalize_or_zero();
|
||||
let perp = glam::Vec3::new(-axis.y, axis.x, 0.0);
|
||||
let perp = glam::DVec3::new(-axis.y, axis.x, 0.0);
|
||||
let signed = (w - base).dot(perp);
|
||||
let typed = fields[0]
|
||||
.buffer
|
||||
.as_ref()
|
||||
.map(|s| s.trim().replace(',', "."))
|
||||
.and_then(|s| crate::app::expr_eval::eval_number(&s).map(|v| v as f32));
|
||||
.and_then(|s| crate::app::expr_eval::eval_number(&s));
|
||||
let h = match typed {
|
||||
Some(v) => v.abs().copysign(signed),
|
||||
None => signed,
|
||||
@@ -316,28 +314,28 @@ impl OpenCADStudio {
|
||||
// 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.
|
||||
let rel = |off_ucs: glam::Vec3| base + xf.vec_to_wcs(off_ucs);
|
||||
let rel = |off_ucs: glam::DVec3| base + xf.vec_to_wcs(off_ucs);
|
||||
match comps.as_slice() {
|
||||
[DynComponent::X, DynComponent::Y] if has_base => {
|
||||
Some(rel(glam::Vec3::new(val(0, dx), val(1, dy), 0.0)))
|
||||
Some(rel(glam::DVec3::new(val(0, dx), val(1, dy), 0.0)))
|
||||
}
|
||||
[DynComponent::X, DynComponent::Y] => {
|
||||
Some(glam::Vec3::new(val(0, w.x), val(1, w.y), base.z))
|
||||
Some(glam::DVec3::new(val(0, w.x), val(1, w.y), base.z))
|
||||
}
|
||||
[DynComponent::X, DynComponent::Y, DynComponent::Z] if has_base => {
|
||||
Some(rel(glam::Vec3::new(val(0, dx), val(1, dy), val(2, dz))))
|
||||
Some(rel(glam::DVec3::new(val(0, dx), val(1, dy), val(2, dz))))
|
||||
}
|
||||
[DynComponent::X, DynComponent::Y, DynComponent::Z] => {
|
||||
Some(glam::Vec3::new(val(0, w.x), val(1, w.y), val(2, base.z)))
|
||||
Some(glam::DVec3::new(val(0, w.x), val(1, w.y), val(2, base.z)))
|
||||
}
|
||||
[DynComponent::Distance, DynComponent::Angle] => {
|
||||
let d = val(0, live_d);
|
||||
let a = angle_rad(1);
|
||||
Some(rel(glam::Vec3::new(d * a.cos(), d * a.sin(), 0.0)))
|
||||
Some(rel(glam::DVec3::new(d * a.cos(), d * a.sin(), 0.0)))
|
||||
}
|
||||
[DynComponent::Distance] => {
|
||||
// Keep the cursor's direction (in UCS), override the magnitude.
|
||||
let dir = glam::Vec3::new(dx, dy, 0.0).normalize_or(glam::Vec3::X);
|
||||
let dir = glam::DVec3::new(dx, dy, 0.0).normalize_or(glam::DVec3::X);
|
||||
Some(rel(dir * val(0, live_d)))
|
||||
}
|
||||
[DynComponent::Angle] => {
|
||||
@@ -346,7 +344,7 @@ impl OpenCADStudio {
|
||||
// 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();
|
||||
Some(rel(glam::Vec3::new(live_d * a.cos(), live_d * a.sin(), 0.0)))
|
||||
Some(rel(glam::DVec3::new(live_d * a.cos(), live_d * a.sin(), 0.0)))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
@@ -433,7 +431,7 @@ impl OpenCADStudio {
|
||||
let anchor = self.tabs[i]
|
||||
.dyn_anchor
|
||||
.or(self.last_point)
|
||||
.unwrap_or(glam::Vec3::ZERO);
|
||||
.unwrap_or(glam::DVec3::ZERO);
|
||||
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]
|
||||
@@ -441,11 +439,11 @@ impl OpenCADStudio {
|
||||
.map(|r| (r.y - anchor.y).atan2(r.x - anchor.x))
|
||||
.unwrap_or(0.0);
|
||||
let mut d = a_cur - a_ref;
|
||||
while d > std::f32::consts::PI {
|
||||
d -= std::f32::consts::TAU;
|
||||
while d > std::f64::consts::PI {
|
||||
d -= std::f64::consts::TAU;
|
||||
}
|
||||
while d <= -std::f32::consts::PI {
|
||||
d += std::f32::consts::TAU;
|
||||
while d <= -std::f64::consts::PI {
|
||||
d += std::f64::consts::TAU;
|
||||
}
|
||||
if d < 0.0 {
|
||||
format!("-{t}")
|
||||
@@ -479,7 +477,10 @@ impl OpenCADStudio {
|
||||
.find_map(|f| f.buffer.clone())
|
||||
{
|
||||
if let Some(dist) = crate::app::expr_eval::eval_number(text.trim()) {
|
||||
let pt = base + dir * dist as f32;
|
||||
// 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;
|
||||
self.last_point = Some(pt);
|
||||
for f in self.tabs[i].dyn_fields.iter_mut() {
|
||||
f.buffer = None;
|
||||
@@ -489,7 +490,7 @@ impl OpenCADStudio {
|
||||
self.sync_dyn_fields();
|
||||
self.reset_tracking_after_point();
|
||||
self.push_ucs_to_cmd(i);
|
||||
let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(pt.as_dvec3()));
|
||||
let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(pt));
|
||||
let task = result.map(|r| self.apply_cmd_result(r))?;
|
||||
self.refresh_active_cmd_preview(i);
|
||||
return Some(task);
|
||||
@@ -548,7 +549,7 @@ impl OpenCADStudio {
|
||||
self.sync_dyn_fields();
|
||||
self.reset_tracking_after_point();
|
||||
self.push_ucs_to_cmd(i);
|
||||
let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(pt.as_dvec3()));
|
||||
let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(pt));
|
||||
for f in self.tabs[i].dyn_fields.iter_mut() {
|
||||
f.buffer = None;
|
||||
}
|
||||
@@ -578,7 +579,7 @@ impl OpenCADStudio {
|
||||
let previews = self.tabs[i]
|
||||
.active_cmd
|
||||
.as_mut()
|
||||
.map(|c| c.on_preview_wires(cur.as_dvec3()))
|
||||
.map(|c| c.on_preview_wires(cur))
|
||||
.unwrap_or_default();
|
||||
self.tabs[i].scene.set_preview_wires(previews);
|
||||
}
|
||||
|
||||
+36
-30
@@ -190,7 +190,7 @@ impl OpenCADStudio {
|
||||
)
|
||||
});
|
||||
let pick = |cam: &crate::scene::view::camera::Camera| match plane {
|
||||
Some((normal, origin)) => cam.pick_on_plane(p, bounds, normal, origin),
|
||||
Some((normal, origin)) => cam.pick_on_plane(p, bounds, normal.as_vec3(), origin),
|
||||
None => cam.pick_on_target_plane(p, bounds),
|
||||
};
|
||||
match edit_cam {
|
||||
@@ -592,7 +592,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
sel.orbit_pivot = self.tabs[i]
|
||||
.scene
|
||||
.orbit_pivot()
|
||||
.or(Some(self.tabs[i].last_cursor_world.as_dvec3()));
|
||||
.or(Some(self.tabs[i].last_cursor_world));
|
||||
}
|
||||
let pivot = sel.orbit_pivot;
|
||||
drop(sel);
|
||||
@@ -768,13 +768,13 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
}
|
||||
|
||||
if snap_hit.is_none() {
|
||||
let base = grip.origin_world.as_vec3();
|
||||
let base = grip.origin_world;
|
||||
let ucs_xf = self.tabs[i].ucs_xform();
|
||||
if self.ortho_mode {
|
||||
snapped = ortho_constrain(snapped.as_vec3(), base, &ucs_xf).as_dvec3();
|
||||
snapped = ortho_constrain(snapped, base, &ucs_xf);
|
||||
} else if self.polar_mode {
|
||||
snapped = polar_constrain_near(
|
||||
snapped.as_vec3(),
|
||||
snapped,
|
||||
base,
|
||||
self.polar_increment_deg,
|
||||
view_rot,
|
||||
@@ -782,8 +782,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
bounds,
|
||||
self.snapper.osnap_radius_px,
|
||||
&ucs_xf,
|
||||
)
|
||||
.as_dvec3();
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -835,7 +834,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
height: vp_size.1,
|
||||
};
|
||||
let world = self.cursor_model_point(i, &edit_cam, p, bounds);
|
||||
self.tabs[i].last_cursor_world = world.as_vec3();
|
||||
self.tabs[i].last_cursor_world = world;
|
||||
}
|
||||
|
||||
// Rollover highlight: when idle (no active command, no
|
||||
@@ -936,7 +935,10 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
)
|
||||
} else {
|
||||
let (go, gr) = self.tabs[i].ucs_grid_basis();
|
||||
self.snapper.from_point = self.last_point;
|
||||
// The snapper is a screen-space (f32) engine; the f64
|
||||
// base only matters for typed-input precision, so hand it
|
||||
// the downcast point here.
|
||||
self.snapper.from_point = self.last_point.map(|p| p.as_vec3());
|
||||
self.snapper
|
||||
.snap(snap_cursor, p, &all_wires[..], view_rot, eye, bounds, go, gr)
|
||||
};
|
||||
@@ -980,7 +982,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
eye,
|
||||
bounds,
|
||||
step,
|
||||
self.last_point,
|
||||
self.last_point.map(|p| p.as_vec3()),
|
||||
self.ortho_mode && !is_window_corner,
|
||||
ucs,
|
||||
)
|
||||
@@ -997,7 +999,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
if self.tabs[i].snap_result.is_none() && otrack_hit.is_none() {
|
||||
if let Some(par) = self.snapper.parallel_snap(
|
||||
cursor_world.as_vec3(),
|
||||
self.last_point,
|
||||
self.last_point.map(|p| p.as_vec3()),
|
||||
view_rot,
|
||||
eye,
|
||||
bounds,
|
||||
@@ -1005,7 +1007,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
if let (Some(base), Some((dir, _))) =
|
||||
(self.last_point, self.snapper.parallel_ref)
|
||||
{
|
||||
self.otrack_active = Some((base, dir));
|
||||
self.otrack_active = Some((base.as_vec3(), dir));
|
||||
}
|
||||
self.tabs[i].snap_result = Some(par);
|
||||
}
|
||||
@@ -1034,11 +1036,10 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
if let Some(base) = self.last_point {
|
||||
let ucs_xf = self.tabs[i].ucs_xform();
|
||||
if self.ortho_mode {
|
||||
pt = ortho_constrain(pt.as_vec3(), base, &ucs_xf)
|
||||
.as_dvec3();
|
||||
pt = ortho_constrain(pt, base, &ucs_xf);
|
||||
} else if self.polar_mode {
|
||||
pt = polar_constrain_near(
|
||||
pt.as_vec3(),
|
||||
pt,
|
||||
base,
|
||||
self.polar_increment_deg,
|
||||
view_rot,
|
||||
@@ -1046,8 +1047,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
bounds,
|
||||
self.snapper.osnap_radius_px,
|
||||
&ucs_xf,
|
||||
)
|
||||
.as_dvec3();
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1060,7 +1060,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
}
|
||||
pt
|
||||
};
|
||||
self.tabs[i].last_cursor_world = effective.as_vec3();
|
||||
self.tabs[i].last_cursor_world = effective;
|
||||
self.tabs[i].last_cursor_screen = p_full;
|
||||
// Project the step anchor (an explicit `dyn_anchor` or the
|
||||
// last point) so the dynamic-input overlay can place its
|
||||
@@ -1069,8 +1069,8 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
// origin (`tile_b`, the viewport rect inside a viewport) so
|
||||
// the DYN guide/labels share the canvas frame with
|
||||
// `last_cursor_screen` (= p_full, canvas space).
|
||||
let proj = |bp: glam::Vec3| {
|
||||
let ndc = view_rot.project_point3((bp.as_dvec3() - eye).as_vec3());
|
||||
let proj = |bp: glam::DVec3| {
|
||||
let ndc = view_rot.project_point3((bp - eye).as_vec3());
|
||||
iced::Point::new(
|
||||
(ndc.x + 1.0) * 0.5 * bounds.width + tile_b.x,
|
||||
(1.0 - ndc.y) * 0.5 * bounds.height + tile_b.y,
|
||||
@@ -1096,7 +1096,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
.and_then(|c| c.window_first_corner());
|
||||
self.tabs[i].scene.selection.borrow_mut().preview_box =
|
||||
if is_window_corner {
|
||||
window_first.map(|c1| (proj(c1.as_vec3()), p_full, true))
|
||||
window_first.map(|c1| (proj(c1), p_full, true))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -1217,6 +1217,8 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
// the snapped angle direction, extending across the drawing.
|
||||
if self.polar_mode && !needs_entity {
|
||||
if let Some(base) = self.last_point {
|
||||
// Screen-space dotted guide (render geometry is f32).
|
||||
let base = base.as_vec3();
|
||||
let effective = effective.as_vec3();
|
||||
let dx = effective.x - base.x;
|
||||
let dy = effective.y - base.y;
|
||||
@@ -1367,7 +1369,12 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
crate::ui::overlay::ucs_icon_hit(cam.view_proj_rte(bounds), bounds, (ux, uy, uz), os)
|
||||
crate::ui::overlay::ucs_icon_hit(
|
||||
cam.view_proj_rte(bounds),
|
||||
bounds,
|
||||
(ux.as_vec3(), uy.as_vec3(), uz.as_vec3()),
|
||||
os,
|
||||
)
|
||||
}
|
||||
|
||||
/// Apply one frame of a UCS-icon grip drag: map the cursor onto the active
|
||||
@@ -1896,7 +1903,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
.snap_tangent_only(snap_cursor.as_vec3(), p, &all_wires[..], view_rot, eye, bounds)
|
||||
} else {
|
||||
let (go, gr) = self.tabs[i].ucs_grid_basis();
|
||||
self.snapper.from_point = self.last_point;
|
||||
self.snapper.from_point = self.last_point.map(|p| p.as_vec3());
|
||||
self.snapper.snap(snap_cursor, p, &all_wires[..], view_rot, eye, bounds, go, gr)
|
||||
};
|
||||
// Snap runs in model space; the result is already model.
|
||||
@@ -1916,7 +1923,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
};
|
||||
let ucs = self.tabs[i].scene.viewcube_ucs_mat();
|
||||
self.snapper
|
||||
.otrack_snap(raw.as_vec3(), view_rot, eye, bounds, step, self.last_point, self.ortho_mode && !is_window_corner, ucs)
|
||||
.otrack_snap(raw.as_vec3(), view_rot, eye, bounds, step, self.last_point.map(|p| p.as_vec3()), self.ortho_mode && !is_window_corner, ucs)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -1934,10 +1941,10 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
if let Some(base) = self.last_point {
|
||||
let ucs_xf = self.tabs[i].ucs_xform();
|
||||
if self.ortho_mode {
|
||||
pt = ortho_constrain(pt.as_vec3(), base, &ucs_xf).as_dvec3();
|
||||
pt = ortho_constrain(pt, base, &ucs_xf);
|
||||
} else if self.polar_mode {
|
||||
pt = polar_constrain_near(
|
||||
pt.as_vec3(),
|
||||
pt,
|
||||
base,
|
||||
self.polar_increment_deg,
|
||||
view_rot,
|
||||
@@ -1945,8 +1952,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
bounds,
|
||||
self.snapper.osnap_radius_px,
|
||||
&ucs_xf,
|
||||
)
|
||||
.as_dvec3();
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2128,7 +2134,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
self.tabs[i].dyn_active = 0;
|
||||
}
|
||||
}
|
||||
self.last_point = Some(world_pt.as_vec3());
|
||||
self.last_point = Some(world_pt);
|
||||
self.dyn_user_reshaped = false;
|
||||
self.sync_dyn_fields();
|
||||
self.reset_tracking_after_point();
|
||||
@@ -2147,7 +2153,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
.as_ref()
|
||||
.and_then(|c| c.resolved_anchor())
|
||||
{
|
||||
self.last_point = Some(a.as_vec3());
|
||||
self.last_point = Some(a);
|
||||
}
|
||||
handled
|
||||
} else {
|
||||
|
||||
@@ -191,15 +191,15 @@ pub(super) fn viewport_controls<'a>(
|
||||
/// world position (and the base point for polar quantities).
|
||||
pub(super) fn dyn_component_value(
|
||||
f: &DynFieldEntry,
|
||||
w: glam::Vec3,
|
||||
base: Option<glam::Vec3>,
|
||||
w: glam::DVec3,
|
||||
base: Option<glam::DVec3>,
|
||||
xf: &super::super::helpers::UcsXform,
|
||||
comma_cartesian: bool,
|
||||
) -> String {
|
||||
if let Some(b) = &f.buffer {
|
||||
return b.clone();
|
||||
}
|
||||
let b = base.unwrap_or(glam::Vec3::ZERO);
|
||||
let b = base.unwrap_or(glam::DVec3::ZERO);
|
||||
// Relative deltas and the polar angle read in the active UCS plane. The
|
||||
// delta is offset-invariant, so only the axis rotation matters (identity
|
||||
// xf reproduces the world-frame deltas).
|
||||
|
||||
+7
-7
@@ -200,7 +200,7 @@ impl OpenCADStudio {
|
||||
let (vw, vh) = tab.scene.selection.borrow().vp_size;
|
||||
let model_basis = {
|
||||
let (o, ux, uy, uz) = tab.ucs_xform().axes();
|
||||
(o.as_dvec3(), (ux, uy, uz))
|
||||
(o, (ux.as_vec3(), uy.as_vec3(), uz.as_vec3()))
|
||||
};
|
||||
let grid: Vec<crate::ui::overlay::GridParams> = tab
|
||||
.scene
|
||||
@@ -212,7 +212,7 @@ impl OpenCADStudio {
|
||||
Some(u) => {
|
||||
let (o, ux, uy, uz) =
|
||||
super::helpers::UcsXform::from_ucs(&u).axes();
|
||||
(o.as_dvec3(), (ux, uy, uz))
|
||||
(o, (ux.as_vec3(), uy.as_vec3(), uz.as_vec3()))
|
||||
}
|
||||
None => (
|
||||
glam::DVec3::ZERO,
|
||||
@@ -345,7 +345,7 @@ impl OpenCADStudio {
|
||||
vec![crate::ui::overlay::UcsIconParams {
|
||||
view_proj: vp_cam.view_proj_rte(full),
|
||||
bounds: full,
|
||||
axes: (ux, uy, uz),
|
||||
axes: (ux.as_vec3(), uy.as_vec3(), uz.as_vec3()),
|
||||
origin_screen,
|
||||
hover: self.ucs_icon_hover,
|
||||
selected: self.ucs_icon_selected,
|
||||
@@ -378,7 +378,7 @@ impl OpenCADStudio {
|
||||
crate::ui::overlay::UcsIconParams {
|
||||
view_proj: cam.view_proj_rte(b),
|
||||
bounds: b,
|
||||
axes: (ux, uy, uz),
|
||||
axes: (ux.as_vec3(), uy.as_vec3(), uz.as_vec3()),
|
||||
origin_screen,
|
||||
hover: i == active && self.ucs_icon_hover,
|
||||
selected: i == active && self.ucs_icon_selected,
|
||||
@@ -440,7 +440,7 @@ impl OpenCADStudio {
|
||||
match (otrack_proj, self.otrack_active) {
|
||||
(Some((view_rot, eye, ob)), Some((base, _dir))) => {
|
||||
let b = ost_project(base, view_rot, eye, ob);
|
||||
let a = ost_project(tab.last_cursor_world, view_rot, eye, ob);
|
||||
let a = ost_project(tab.last_cursor_world.as_vec3(), view_rot, eye, ob);
|
||||
(b.x.is_finite() && a.x.is_finite()).then_some((b, a))
|
||||
}
|
||||
_ => None,
|
||||
@@ -573,7 +573,7 @@ impl OpenCADStudio {
|
||||
// A command may drive a typed scalar by mouse (e.g. a
|
||||
// perpendicular distance to a picked object); show that live
|
||||
// value in the box until the user types over it.
|
||||
let live = tab.active_cmd.as_ref().and_then(|c| c.dyn_live_value(w.as_dvec3()));
|
||||
let live = tab.active_cmd.as_ref().and_then(|c| c.dyn_live_value(w));
|
||||
let boxes: Vec<crate::ui::overlay::DynBox> = tab
|
||||
.dyn_fields
|
||||
.iter()
|
||||
@@ -1304,7 +1304,7 @@ impl OpenCADStudio {
|
||||
// drawing coordinates (paper space carries no offset), then
|
||||
// report it in the active UCS — the readout follows the
|
||||
// user's coordinate system, not raw WCS (no-op without UCS).
|
||||
let to_readout = |p: glam::Vec3| {
|
||||
let to_readout = |p: glam::DVec3| {
|
||||
// The readout follows the active pane's UCS — model space
|
||||
// or inside a floating viewport (no-op without a UCS).
|
||||
if tab.editing_model_space() {
|
||||
|
||||
Reference in new issue
Block a user