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 <[email protected]>
This commit is contained in:
1 parent
8ed10ceb2a
commit
57710eec2b
47 files changed
+699
-606
No files matched your search
@@ -3,7 +3,7 @@
|
||||
use acadrust::entities::{Dimension, DimensionAligned};
|
||||
use acadrust::types::Vector3;
|
||||
use acadrust::EntityType;
|
||||
use glam::{DVec3, Vec3};
|
||||
use glam::DVec3;
|
||||
|
||||
use crate::command::{CadCommand, CmdResult};
|
||||
use crate::modules::{IconKind, ModuleEvent, ToolDef};
|
||||
@@ -22,8 +22,8 @@ pub fn tool() -> ToolDef {
|
||||
|
||||
enum Step {
|
||||
First,
|
||||
Second(Vec3),
|
||||
DimLine { p1: Vec3, p2: Vec3 },
|
||||
Second(DVec3),
|
||||
DimLine { p1: DVec3, p2: DVec3 },
|
||||
}
|
||||
|
||||
pub struct AlignedDimensionCommand {
|
||||
@@ -73,7 +73,7 @@ impl CadCommand for AlignedDimensionCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
match self.step {
|
||||
Step::First => {
|
||||
self.step = Step::Second(pt);
|
||||
@@ -174,7 +174,7 @@ impl CadCommand for AlignedDimensionCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> { let pt = pt.as_vec3();
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
let (p1, p2) = match self.step {
|
||||
Step::First => return None,
|
||||
Step::Second(p1) => (p1, pt),
|
||||
@@ -182,6 +182,9 @@ impl CadCommand for AlignedDimensionCommand {
|
||||
return Some(preview_aligned(p1, p2, pt));
|
||||
}
|
||||
};
|
||||
// Preview WireModel points are screen/GPU-side: downcast to f32.
|
||||
let p1 = p1.as_vec3();
|
||||
let p2 = p2.as_vec3();
|
||||
Some(WireModel {
|
||||
text_verts: Vec::new(),
|
||||
name: "dimaligned_preview".into(),
|
||||
@@ -205,8 +208,8 @@ impl CadCommand for AlignedDimensionCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn v3(p: Vec3) -> Vector3 {
|
||||
Vector3::new(p.x as f64, p.y as f64, p.z as f64)
|
||||
fn v3(p: DVec3) -> Vector3 {
|
||||
Vector3::new(p.x, p.y, p.z)
|
||||
}
|
||||
|
||||
/// Dimension-line endpoints: the baseline `p1`–`p2` shifted to pass through
|
||||
@@ -214,16 +217,21 @@ fn v3(p: Vec3) -> Vector3 {
|
||||
/// it matches the committed entity's renderer (and DIMLINEAR). The old
|
||||
/// preview used an XZ-plane perpendicular, drawing the offset in the wrong
|
||||
/// spatial direction. (#150)
|
||||
fn dim_line_endpoints(p1: Vec3, p2: Vec3, dim_pt: Vec3) -> (Vec3, Vec3) {
|
||||
fn dim_line_endpoints(p1: DVec3, p2: DVec3, dim_pt: DVec3) -> (DVec3, DVec3) {
|
||||
let axis = (p2 - p1).normalize_or_zero();
|
||||
let perp = Vec3::new(-axis.y, axis.x, 0.0);
|
||||
let perp = DVec3::new(-axis.y, axis.x, 0.0);
|
||||
let offset = (dim_pt - p1).dot(perp);
|
||||
(p1 + perp * offset, p2 + perp * offset)
|
||||
}
|
||||
|
||||
fn preview_aligned(p1: Vec3, p2: Vec3, dim_pt: Vec3) -> WireModel {
|
||||
fn preview_aligned(p1: DVec3, p2: DVec3, dim_pt: DVec3) -> WireModel {
|
||||
// Show ext lines + dim line.
|
||||
let (d1, d2) = dim_line_endpoints(p1, p2, dim_pt);
|
||||
// Preview WireModel points are screen/GPU-side: downcast to f32.
|
||||
let p1 = p1.as_vec3();
|
||||
let p2 = p2.as_vec3();
|
||||
let d1 = d1.as_vec3();
|
||||
let d2 = d2.as_vec3();
|
||||
WireModel {
|
||||
text_verts: Vec::new(),
|
||||
name: "dimaligned_preview".into(),
|
||||
|
||||
@@ -20,15 +20,15 @@ pub fn tool() -> ToolDef {
|
||||
|
||||
enum Step {
|
||||
Vertex,
|
||||
FirstRay(Vec3),
|
||||
FirstRay(DVec3),
|
||||
SecondRay {
|
||||
vertex: Vec3,
|
||||
first: Vec3,
|
||||
vertex: DVec3,
|
||||
first: DVec3,
|
||||
},
|
||||
ArcPoint {
|
||||
vertex: Vec3,
|
||||
first: Vec3,
|
||||
second: Vec3,
|
||||
vertex: DVec3,
|
||||
first: DVec3,
|
||||
second: DVec3,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ impl CadCommand for AngularDimensionCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
match self.step {
|
||||
Step::Vertex => {
|
||||
self.step = Step::FirstRay(pt);
|
||||
@@ -98,28 +98,34 @@ impl CadCommand for AngularDimensionCommand {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> { let pt = pt.as_vec3();
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
let pt = pt.as_vec3();
|
||||
match self.step {
|
||||
Step::Vertex => None,
|
||||
Step::FirstRay(vertex) => Some(preview_wire(vec![vertex, pt])),
|
||||
Step::FirstRay(vertex) => Some(preview_wire(vec![vertex.as_vec3(), pt])),
|
||||
Step::SecondRay { vertex, first } => Some(preview_wire(vec![
|
||||
vertex,
|
||||
first,
|
||||
vertex.as_vec3(),
|
||||
first.as_vec3(),
|
||||
Vec3::new(f32::NAN, f32::NAN, f32::NAN),
|
||||
vertex,
|
||||
vertex.as_vec3(),
|
||||
pt,
|
||||
])),
|
||||
Step::ArcPoint {
|
||||
vertex,
|
||||
first,
|
||||
second,
|
||||
} => Some(preview_wire(angular_preview(vertex, first, second, pt))),
|
||||
} => Some(preview_wire(angular_preview(
|
||||
vertex.as_vec3(),
|
||||
first.as_vec3(),
|
||||
second.as_vec3(),
|
||||
pt,
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn v3(pt: Vec3) -> Vector3 {
|
||||
Vector3::new(pt.x as f64, pt.y as f64, pt.z as f64)
|
||||
fn v3(pt: DVec3) -> Vector3 {
|
||||
Vector3::new(pt.x, pt.y, pt.z)
|
||||
}
|
||||
|
||||
fn preview_wire(points: Vec<Vec3>) -> WireModel {
|
||||
|
||||
@@ -22,8 +22,8 @@ pub fn tool() -> ToolDef {
|
||||
|
||||
enum Step {
|
||||
CenterPoint,
|
||||
ArcPoint(Vec3),
|
||||
TextPoint { center: Vec3, arc_pt: Vec3 },
|
||||
ArcPoint(DVec3),
|
||||
TextPoint { center: DVec3, arc_pt: DVec3 },
|
||||
}
|
||||
|
||||
pub struct DiameterDimensionCommand {
|
||||
@@ -71,7 +71,7 @@ impl CadCommand for DiameterDimensionCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
match self.step {
|
||||
Step::CenterPoint => {
|
||||
self.step = Step::ArcPoint(pt);
|
||||
@@ -86,7 +86,7 @@ impl CadCommand for DiameterDimensionCommand {
|
||||
dim.base.definition_point = v3(arc_pt);
|
||||
dim.base.text_middle_point = v3(pt);
|
||||
dim.base.insertion_point = v3(pt);
|
||||
dim.leader_length = arc_pt.distance(pt) as f64;
|
||||
dim.leader_length = arc_pt.distance(pt);
|
||||
dim.base.actual_measurement = dim.measurement();
|
||||
dim.base.user_text = self.text_override.clone();
|
||||
// An explicit text angle overrides the default rotation.
|
||||
@@ -161,20 +161,21 @@ impl CadCommand for DiameterDimensionCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> { let pt = pt.as_vec3();
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
let pt = pt.as_vec3();
|
||||
match self.step {
|
||||
Step::CenterPoint => None,
|
||||
Step::ArcPoint(center) => Some(preview_line(center, pt)),
|
||||
Step::ArcPoint(center) => Some(preview_line(center.as_vec3(), pt)),
|
||||
Step::TextPoint { center, arc_pt } => {
|
||||
let far = center + (center - arc_pt); // opposite point on circle
|
||||
Some(preview_line(far, pt))
|
||||
Some(preview_line(far.as_vec3(), pt))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn v3(p: Vec3) -> Vector3 {
|
||||
Vector3::new(p.x as f64, p.y as f64, p.z as f64)
|
||||
fn v3(p: DVec3) -> Vector3 {
|
||||
Vector3::new(p.x, p.y, p.z)
|
||||
}
|
||||
|
||||
fn preview_line(a: Vec3, b: Vec3) -> WireModel {
|
||||
|
||||
@@ -27,22 +27,22 @@ pub fn tool() -> ToolDef {
|
||||
}
|
||||
|
||||
/// Fallback stacking increment (world units) when no DimStyle is available.
|
||||
const DEFAULT_DIMDLI: f32 = 1.5;
|
||||
const DEFAULT_DIMDLI: f64 = 1.5;
|
||||
|
||||
pub struct DimBaselineCommand {
|
||||
/// Fixed first-extension-line origin (never changes).
|
||||
base_p1: Vec3,
|
||||
base_p1: DVec3,
|
||||
/// Direction along the measurement direction (0.0 = horizontal, PI/2 = vertical).
|
||||
rotation: f64,
|
||||
/// Text reading rotation inherited from the base dim (keeps a UCS-aligned
|
||||
/// chain's text consistent with the originating DIMLINEAR). 0 = natural.
|
||||
text_rotation: f64,
|
||||
/// Unit vector perpendicular to the dimension axis, pointing toward the dim line side.
|
||||
perp: Vec3,
|
||||
perp: DVec3,
|
||||
/// Perpendicular distance of the NEXT dimension line from the extension-line axis.
|
||||
next_offset: f32,
|
||||
next_offset: f64,
|
||||
/// Stacking increment from the active DimStyle (DIMDLI).
|
||||
dimdli: f32,
|
||||
dimdli: f64,
|
||||
/// True once we have a base dimension loaded.
|
||||
ready: bool,
|
||||
}
|
||||
@@ -51,10 +51,10 @@ impl DimBaselineCommand {
|
||||
/// No base dim found — cancel immediately.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
base_p1: Vec3::ZERO,
|
||||
base_p1: DVec3::ZERO,
|
||||
rotation: 0.0,
|
||||
text_rotation: 0.0,
|
||||
perp: Vec3::Y,
|
||||
perp: DVec3::Y,
|
||||
next_offset: DEFAULT_DIMDLI,
|
||||
dimdli: DEFAULT_DIMDLI,
|
||||
ready: false,
|
||||
@@ -76,11 +76,16 @@ impl DimBaselineCommand {
|
||||
text_rotation: f64,
|
||||
dimdli: f32,
|
||||
) -> Self {
|
||||
// The base dim's points enter as f32 from the caller; promote to f64 so
|
||||
// all committed-coordinate math downstream stays exact.
|
||||
let p1 = p1.as_dvec3();
|
||||
let definition_point = definition_point.as_dvec3();
|
||||
// Measurement axis = the base dim's rotation angle (any angle, incl. a
|
||||
// UCS-aligned one), not a world horizontal/vertical.
|
||||
let axis = Vec3::new(rotation.cos() as f32, rotation.sin() as f32, 0.0);
|
||||
let perp = Vec3::new(-axis.y, axis.x, 0.0);
|
||||
let axis = DVec3::new(rotation.cos(), rotation.sin(), 0.0);
|
||||
let perp = DVec3::new(-axis.y, axis.x, 0.0);
|
||||
let base_offset = (definition_point - p1).dot(perp);
|
||||
let dimdli = dimdli as f64;
|
||||
let dimdli = if dimdli.abs() < 1e-6 {
|
||||
DEFAULT_DIMDLI
|
||||
} else {
|
||||
@@ -110,7 +115,7 @@ impl DimBaselineCommand {
|
||||
/// base origin and `p2` each projected INDEPENDENTLY onto the dim line (at
|
||||
/// the current stacking offset). Projecting both keeps the line straight
|
||||
/// even when the two origins aren't level — a shared offset tilts it. (#181)
|
||||
fn dim_line_pts(&self, p2: Vec3) -> (Vec3, Vec3) {
|
||||
fn dim_line_pts(&self, p2: DVec3) -> (DVec3, DVec3) {
|
||||
let target = self.base_p1.dot(self.perp) + self.next_offset;
|
||||
let d1 = self.base_p1 + self.perp * (target - self.base_p1.dot(self.perp));
|
||||
let d2 = p2 + self.perp * (target - p2.dot(self.perp));
|
||||
@@ -131,7 +136,7 @@ impl CadCommand for DimBaselineCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
if !self.ready {
|
||||
return CmdResult::Cancel;
|
||||
}
|
||||
@@ -162,12 +167,17 @@ impl CadCommand for DimBaselineCommand {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> { let pt = pt.as_vec3();
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
if !self.ready {
|
||||
return None;
|
||||
}
|
||||
let p1 = self.base_p1;
|
||||
// dim_line_pts stays in f64; downcast only here, into the preview's
|
||||
// [f32;3] GPU buffer — correct pixel-space, never touches committed geometry.
|
||||
let (dim_line_pt, dim_line_pt2) = self.dim_line_pts(pt);
|
||||
let p1 = self.base_p1.as_vec3();
|
||||
let dim_line_pt = dim_line_pt.as_vec3();
|
||||
let dim_line_pt2 = dim_line_pt2.as_vec3();
|
||||
let pt = pt.as_vec3();
|
||||
Some(WireModel {
|
||||
text_verts: Vec::new(),
|
||||
name: "dimbase_preview".into(),
|
||||
@@ -200,8 +210,8 @@ impl CadCommand for DimBaselineCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn v3(p: Vec3) -> Vector3 {
|
||||
Vector3::new(p.x as f64, p.y as f64, p.z as f64)
|
||||
fn v3(p: DVec3) -> Vector3 {
|
||||
Vector3::new(p.x, p.y, p.z)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ pub fn tool() -> ToolDef {
|
||||
|
||||
pub struct DimContinueCommand {
|
||||
/// Fixed first-extension-line origin for the current step (moves each iteration).
|
||||
chain_p1: Vec3,
|
||||
chain_p1: DVec3,
|
||||
/// Direction along the dimension axis (0.0 = horizontal, PI/2 = vertical).
|
||||
rotation: f64,
|
||||
/// Text reading rotation inherited from the base dim so a UCS-aligned chain
|
||||
@@ -39,9 +39,9 @@ pub struct DimContinueCommand {
|
||||
/// coordinate so the whole chain stays collinear — even when the extension
|
||||
/// origins sit at different perpendicular positions (extension lines of
|
||||
/// different lengths). (#151)
|
||||
dim_line_perp: f32,
|
||||
dim_line_perp: f64,
|
||||
/// Direction of "up" perpendicular to the dim axis (points toward the dim line).
|
||||
perp: Vec3,
|
||||
perp: DVec3,
|
||||
/// True once we have a base dimension loaded.
|
||||
ready: bool,
|
||||
}
|
||||
@@ -50,11 +50,11 @@ impl DimContinueCommand {
|
||||
/// No base dim found — will show an error prompt and cancel immediately.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
chain_p1: Vec3::ZERO,
|
||||
chain_p1: DVec3::ZERO,
|
||||
rotation: 0.0,
|
||||
text_rotation: 0.0,
|
||||
dim_line_perp: 0.0,
|
||||
perp: Vec3::Y,
|
||||
perp: DVec3::Y,
|
||||
ready: false,
|
||||
}
|
||||
}
|
||||
@@ -75,11 +75,16 @@ impl DimContinueCommand {
|
||||
rotation: f64,
|
||||
text_rotation: f64,
|
||||
) -> Self {
|
||||
// Widen the base-dim points to f64 so the committed chain math runs in
|
||||
// full precision. (The base points arrive as f32 from the entity, but
|
||||
// every derived committed coordinate must stay f64 from here on.)
|
||||
let p2 = p2.as_dvec3();
|
||||
let definition_point = definition_point.as_dvec3();
|
||||
// Axis unit vector along the measurement direction — the base dim's
|
||||
// rotation angle (any angle, incl. a UCS-aligned one), not a world H/V.
|
||||
let axis = Vec3::new(rotation.cos() as f32, rotation.sin() as f32, 0.0);
|
||||
let axis = DVec3::new(rotation.cos(), rotation.sin(), 0.0);
|
||||
// Perpendicular unit vector toward the dim line.
|
||||
let perp = Vec3::new(-axis.y, axis.x, 0.0);
|
||||
let perp = DVec3::new(-axis.y, axis.x, 0.0);
|
||||
let dim_line_perp = definition_point.dot(perp);
|
||||
Self {
|
||||
chain_p1: p2,
|
||||
@@ -105,7 +110,7 @@ impl CadCommand for DimContinueCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
if !self.ready {
|
||||
return CmdResult::Cancel;
|
||||
}
|
||||
@@ -141,13 +146,18 @@ impl CadCommand for DimContinueCommand {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> { let pt = pt.as_vec3();
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
if !self.ready {
|
||||
return None;
|
||||
}
|
||||
let p1 = self.chain_p1;
|
||||
let dim_line_pt = p1 + self.perp * (self.dim_line_perp - p1.dot(self.perp));
|
||||
let dim_line_pt2 = pt + self.perp * (self.dim_line_perp - pt.dot(self.perp));
|
||||
// Preview / rubber-band geometry feeds an f32 GPU buffer, so drop to f32
|
||||
// at this screen-only boundary.
|
||||
let pt = pt.as_vec3();
|
||||
let p1 = self.chain_p1.as_vec3();
|
||||
let perp = self.perp.as_vec3();
|
||||
let dim_line_perp = self.dim_line_perp as f32;
|
||||
let dim_line_pt = p1 + perp * (dim_line_perp - p1.dot(perp));
|
||||
let dim_line_pt2 = pt + perp * (dim_line_perp - pt.dot(perp));
|
||||
Some(WireModel {
|
||||
text_verts: Vec::new(),
|
||||
name: "dimcont_preview".into(),
|
||||
@@ -180,8 +190,8 @@ impl CadCommand for DimContinueCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn v3(p: Vec3) -> Vector3 {
|
||||
Vector3::new(p.x as f64, p.y as f64, p.z as f64)
|
||||
fn v3(p: DVec3) -> Vector3 {
|
||||
Vector3::new(p.x, p.y, p.z)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ impl CadCommand for DimJogLineCommand {
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
if let Step::PickJogPos { handle } = &self.step {
|
||||
let h = *handle;
|
||||
// Emit sentinel for commands.rs to store the jog position
|
||||
|
||||
@@ -69,13 +69,12 @@ impl CadCommand for DimTeditCommand {
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
if let Step::PickTextPos { handle, entity } = &mut self.step {
|
||||
let h = *handle;
|
||||
if let Some(mut ent) = entity.take() {
|
||||
if let EntityType::Dimension(ref mut d) = ent {
|
||||
let new_pt =
|
||||
acadrust::types::Vector3::new(pt.x as f64, pt.y as f64, pt.z as f64);
|
||||
let new_pt = acadrust::types::Vector3::new(pt.x, pt.y, pt.z);
|
||||
d.base_mut().text_middle_point = new_pt;
|
||||
d.base_mut().insertion_point = new_pt;
|
||||
// Pin the text to this location, else the renderer recomputes
|
||||
|
||||
@@ -31,7 +31,7 @@ pub fn tool() -> ToolDef {
|
||||
}
|
||||
|
||||
pub struct LeaderCommand {
|
||||
verts: Vec<Vec3>,
|
||||
verts: Vec<DVec3>,
|
||||
ucs: Mat4,
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ impl CadCommand for LeaderCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
self.verts.push(pt);
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
@@ -85,24 +85,24 @@ impl CadCommand for LeaderCommand {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> { let pt = pt.as_vec3();
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
if self.verts.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let mut pts = self.verts.clone();
|
||||
pts.push(pt);
|
||||
let mut pts: Vec<Vec3> = self.verts.iter().map(|p| p.as_vec3()).collect();
|
||||
pts.push(pt.as_vec3());
|
||||
Some(preview_wire(&pts))
|
||||
}
|
||||
}
|
||||
|
||||
// ── Helpers ─────────────────────────────────────────────────────────────────
|
||||
|
||||
fn v3(p: Vec3) -> Vector3 {
|
||||
Vector3::new(p.x as f64, p.y as f64, p.z as f64)
|
||||
fn dv3(p: DVec3) -> Vector3 {
|
||||
Vector3::new(p.x, p.y, p.z)
|
||||
}
|
||||
|
||||
fn build_leader(verts: &[Vec3], ucs: Mat4) -> Leader {
|
||||
let mut l = Leader::from_vertices(verts.iter().map(|p| v3(*p)).collect());
|
||||
fn build_leader(verts: &[DVec3], ucs: Mat4) -> Leader {
|
||||
let mut l = Leader::from_vertices(verts.iter().map(|p| dv3(*p)).collect());
|
||||
l.creation_type = LeaderCreationType::WithText;
|
||||
l.hookline_enabled = true;
|
||||
// The hookline / text read along the UCS X axis (DXF "horizontal direction
|
||||
@@ -116,17 +116,17 @@ fn build_leader(verts: &[Vec3], ucs: Mat4) -> Leader {
|
||||
/// keeps the text reading away from the leader (text to the right of a
|
||||
/// left-pointing landing, to the left of a right-pointing one).
|
||||
fn annotation_anchor(
|
||||
verts: &[Vec3],
|
||||
verts: &[DVec3],
|
||||
text_height: f64,
|
||||
ucs: Mat4,
|
||||
) -> (Vec3, AttachmentPoint) {
|
||||
) -> (DVec3, AttachmentPoint) {
|
||||
let last = *verts.last().unwrap();
|
||||
let prev = verts[verts.len() - 2];
|
||||
// Side + landing run along the UCS X axis (identity = world).
|
||||
let ux = ucs.transform_vector3(Vec3::X).normalize_or(Vec3::X);
|
||||
let ux = ucs.transform_vector3(Vec3::X).normalize_or(Vec3::X).as_dvec3();
|
||||
let to_right = (last - prev).dot(ux) >= 0.0;
|
||||
let sign = if to_right { 1.0_f32 } else { -1.0_f32 };
|
||||
let anchor = last + ux * (sign * text_height as f32 * 1.5);
|
||||
let sign = if to_right { 1.0_f64 } else { -1.0_f64 };
|
||||
let anchor = last + ux * (sign * text_height * 1.5);
|
||||
let attach = if to_right {
|
||||
AttachmentPoint::MiddleLeft
|
||||
} else {
|
||||
@@ -137,14 +137,14 @@ fn annotation_anchor(
|
||||
|
||||
fn build_mtext(
|
||||
text: &str,
|
||||
pos: Vec3,
|
||||
pos: DVec3,
|
||||
height: f64,
|
||||
attach: AttachmentPoint,
|
||||
ucs: Mat4,
|
||||
) -> MText {
|
||||
let mut m = MText::new();
|
||||
m.value = text.to_string();
|
||||
m.insertion_point = v3(pos);
|
||||
m.insertion_point = dv3(pos);
|
||||
m.height = height;
|
||||
m.attachment_point = attach;
|
||||
// Text reads along the UCS X axis.
|
||||
|
||||
@@ -10,10 +10,10 @@ use glam::{DVec3, Mat4, Vec3};
|
||||
/// World-space measurement axis for a linear dimension between `first` and
|
||||
/// `second`, chosen as the UCS X or Y axis (whichever the span is closer to).
|
||||
/// `ucs` is the UCS→wire affine; identity gives the world X/Y behaviour.
|
||||
fn measure_axis(first: Vec3, second: Vec3, ucs: Mat4) -> Vec3 {
|
||||
let ux = ucs.transform_vector3(Vec3::X).normalize_or(Vec3::X);
|
||||
let uy = ucs.transform_vector3(Vec3::Y).normalize_or(Vec3::Y);
|
||||
let du = ucs.inverse().transform_vector3(second - first);
|
||||
fn measure_axis(first: DVec3, second: DVec3, ucs: Mat4) -> DVec3 {
|
||||
let ux = ucs.transform_vector3(Vec3::X).normalize_or(Vec3::X).as_dvec3();
|
||||
let uy = ucs.transform_vector3(Vec3::Y).normalize_or(Vec3::Y).as_dvec3();
|
||||
let du = ucs.inverse().transform_vector3((second - first).as_vec3());
|
||||
if du.x.abs() >= du.y.abs() {
|
||||
ux
|
||||
} else {
|
||||
@@ -34,8 +34,8 @@ pub fn tool() -> ToolDef {
|
||||
|
||||
enum Step {
|
||||
FirstPoint,
|
||||
SecondPoint(Vec3),
|
||||
DimensionLine { first: Vec3, second: Vec3 },
|
||||
SecondPoint(DVec3),
|
||||
DimensionLine { first: DVec3, second: DVec3 },
|
||||
}
|
||||
|
||||
pub struct LinearDimensionCommand {
|
||||
@@ -91,7 +91,7 @@ impl CadCommand for LinearDimensionCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
match self.step {
|
||||
Step::FirstPoint => {
|
||||
self.step = Step::SecondPoint(pt);
|
||||
@@ -106,7 +106,7 @@ impl CadCommand for LinearDimensionCommand {
|
||||
// Measure along the UCS axis the span is closest to; the DXF
|
||||
// rotation is that axis's angle in world space.
|
||||
let axis = measure_axis(first, second, self.ucs);
|
||||
dim.rotation = (axis.y as f64).atan2(axis.x as f64);
|
||||
dim.rotation = axis.y.atan2(axis.x);
|
||||
// Bake the UCS X-axis angle as the text rotation so the
|
||||
// measurement text reads horizontally in the UCS (i.e. square on
|
||||
// screen) regardless of the style's force-horizontal flags,
|
||||
@@ -200,7 +200,7 @@ impl CadCommand for LinearDimensionCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> { let pt = pt.as_vec3();
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
match self.step {
|
||||
Step::FirstPoint => None,
|
||||
Step::SecondPoint(first) => Some(preview_wire(vec![first, pt])),
|
||||
@@ -212,15 +212,18 @@ impl CadCommand for LinearDimensionCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn v3(pt: Vec3) -> Vector3 {
|
||||
Vector3::new(pt.x as f64, pt.y as f64, pt.z as f64)
|
||||
fn v3(pt: DVec3) -> Vector3 {
|
||||
Vector3::new(pt.x, pt.y, pt.z)
|
||||
}
|
||||
|
||||
fn preview_wire(points: Vec<Vec3>) -> WireModel {
|
||||
fn preview_wire(points: Vec<DVec3>) -> WireModel {
|
||||
WireModel {
|
||||
text_verts: Vec::new(),
|
||||
name: "dimlinear_preview".to_string(),
|
||||
points: points.into_iter().map(|p| [p.x, p.y, p.z]).collect(),
|
||||
points: points
|
||||
.into_iter()
|
||||
.map(|p| [p.x as f32, p.y as f32, p.z as f32])
|
||||
.collect(),
|
||||
points_low: Vec::new(),
|
||||
color: WireModel::CYAN,
|
||||
selected: false,
|
||||
@@ -244,23 +247,23 @@ fn preview_wire(points: Vec<Vec3>) -> WireModel {
|
||||
/// single shared offset only lands both on the line when they are level, and
|
||||
/// tilts the dimension line when they are not (e.g. measuring across sloped
|
||||
/// points). See #181.
|
||||
fn dim_line_endpoints(first: Vec3, second: Vec3, def: Vec3, axis: Vec3) -> (Vec3, Vec3) {
|
||||
let perp = Vec3::new(-axis.y, axis.x, 0.0);
|
||||
fn dim_line_endpoints(first: DVec3, second: DVec3, def: DVec3, axis: DVec3) -> (DVec3, DVec3) {
|
||||
let perp = DVec3::new(-axis.y, axis.x, 0.0);
|
||||
let dperp = def.dot(perp);
|
||||
let d1 = first + perp * (dperp - first.dot(perp));
|
||||
let d2 = second + perp * (dperp - second.dot(perp));
|
||||
(d1, d2)
|
||||
}
|
||||
|
||||
fn linear_dimension_preview(first: Vec3, second: Vec3, def: Vec3, axis: Vec3) -> Vec<Vec3> {
|
||||
fn linear_dimension_preview(first: DVec3, second: DVec3, def: DVec3, axis: DVec3) -> Vec<DVec3> {
|
||||
let (d1, d2) = dim_line_endpoints(first, second, def, axis);
|
||||
let nan = Vec3::new(f32::NAN, f32::NAN, f32::NAN);
|
||||
let nan = DVec3::new(f64::NAN, f64::NAN, f64::NAN);
|
||||
vec![first, d1, nan, second, d2, nan, d1, d2]
|
||||
}
|
||||
|
||||
fn linear_text_pos(first: Vec3, second: Vec3, def: Vec3, axis: Vec3) -> Vec3 {
|
||||
fn linear_text_pos(first: DVec3, second: DVec3, def: DVec3, axis: DVec3) -> DVec3 {
|
||||
let (d1, d2) = dim_line_endpoints(first, second, def, axis);
|
||||
let perp = Vec3::new(-axis.y, axis.x, 0.0);
|
||||
let perp = DVec3::new(-axis.y, axis.x, 0.0);
|
||||
(d1 + d2) * 0.5 + perp * 0.15
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ pub fn tool() -> ToolDef {
|
||||
}
|
||||
|
||||
pub struct MLeaderCommand {
|
||||
verts: Vec<Vec3>,
|
||||
verts: Vec<DVec3>,
|
||||
ucs: Mat4,
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ impl CadCommand for MLeaderCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
self.verts.push(pt);
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
@@ -75,23 +75,24 @@ impl CadCommand for MLeaderCommand {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> { let pt = pt.as_vec3();
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
if self.verts.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let mut pts = self.verts.clone();
|
||||
pts.push(pt);
|
||||
// Preview / rubber-band is GPU screen-space: downcast to f32 here.
|
||||
let mut pts: Vec<Vec3> = self.verts.iter().map(|p| p.as_vec3()).collect();
|
||||
pts.push(pt.as_vec3());
|
||||
Some(preview_wire(&pts))
|
||||
}
|
||||
}
|
||||
|
||||
// ── Helpers ─────────────────────────────────────────────────────────────────
|
||||
|
||||
fn v3(p: Vec3) -> Vector3 {
|
||||
Vector3::new(p.x as f64, p.y as f64, p.z as f64)
|
||||
fn v3(p: DVec3) -> Vector3 {
|
||||
Vector3::new(p.x, p.y, p.z)
|
||||
}
|
||||
|
||||
fn build_mleader(text: &str, verts: &[Vec3], ucs: Mat4) -> MultiLeader {
|
||||
fn build_mleader(text: &str, verts: &[DVec3], ucs: Mat4) -> MultiLeader {
|
||||
// Last vertex = content/text location; remaining = leader line points
|
||||
let (leader_pts, content_pt) = verts.split_at(verts.len() - 1);
|
||||
let content_pt = content_pt[0];
|
||||
@@ -113,7 +114,7 @@ fn build_mleader(text: &str, verts: &[Vec3], ucs: Mat4) -> MultiLeader {
|
||||
let ux = ucs.transform_vector3(Vec3::X).normalize_or(Vec3::X);
|
||||
// Which side of the leader the text sits on, measured along the UCS X axis.
|
||||
let last_leader = leader_pts.last().copied().unwrap_or(content_pt);
|
||||
let to_right = (content_pt - last_leader).dot(ux) >= 0.0;
|
||||
let to_right = (content_pt - last_leader).dot(ux.as_dvec3()) >= 0.0;
|
||||
let sign = if to_right { 1.0 } else { -1.0 };
|
||||
let landing = ux * (sign as f32);
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ enum AddStep {
|
||||
CollectPoints {
|
||||
handle: Handle,
|
||||
entity: EntityType,
|
||||
pts: Vec<Vec3>,
|
||||
pts: Vec<DVec3>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ impl CadCommand for MLeaderAddCommand {
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
match &mut self.step {
|
||||
AddStep::PickArrowhead { handle, entity } => {
|
||||
if let Some(ent) = entity.take() {
|
||||
@@ -121,7 +121,7 @@ impl CadCommand for MLeaderAddCommand {
|
||||
// Add a new leader root with the collected points
|
||||
let points: Vec<Vector3> = pts
|
||||
.iter()
|
||||
.map(|p| Vector3::new(p.x as f64, p.y as f64, p.z as f64))
|
||||
.map(|p| Vector3::new(p.x, p.y, p.z))
|
||||
.collect();
|
||||
let root = ml.context.add_leader_root();
|
||||
root.create_line(points);
|
||||
@@ -132,14 +132,16 @@ impl CadCommand for MLeaderAddCommand {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> { let pt = pt.as_vec3();
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
let existing_pts = match &self.step {
|
||||
AddStep::CollectPoints { pts, .. } => pts.clone(),
|
||||
_ => return None,
|
||||
};
|
||||
let mut all_pts = existing_pts;
|
||||
all_pts.push(pt);
|
||||
Some(preview_wire(&all_pts))
|
||||
Some(preview_wire(
|
||||
&all_pts.iter().map(|p| p.as_vec3()).collect::<Vec<_>>(),
|
||||
))
|
||||
}
|
||||
|
||||
fn inject_picked_entity(&mut self, entity: EntityType) {
|
||||
@@ -275,7 +277,7 @@ pub fn tool_align() -> ToolDef {
|
||||
enum AlignStep {
|
||||
Gathering,
|
||||
PickDirection { handles: Vec<Handle> },
|
||||
PickEndDir { handles: Vec<Handle>, from: Vec3 },
|
||||
PickEndDir { handles: Vec<Handle>, from: DVec3 },
|
||||
}
|
||||
|
||||
pub struct MLeaderAlignCommand {
|
||||
@@ -321,7 +323,7 @@ impl CadCommand for MLeaderAlignCommand {
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
match &mut self.step {
|
||||
AlignStep::PickDirection { handles } => {
|
||||
let h = handles.clone();
|
||||
@@ -423,7 +425,7 @@ impl CadCommand for MLeaderCollectCommand {
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
if let CollectStep::PickLocation { handles } = &self.step {
|
||||
let h = handles.clone();
|
||||
// Build a new combined multileader at the collection point, delete originals.
|
||||
|
||||
@@ -28,7 +28,7 @@ pub fn tool() -> ToolDef {
|
||||
|
||||
enum Step {
|
||||
FeaturePoint,
|
||||
LeaderEndpoint { feature: Vec3 },
|
||||
LeaderEndpoint { feature: DVec3 },
|
||||
}
|
||||
|
||||
pub struct OrdinateDimCommand {
|
||||
@@ -55,7 +55,7 @@ impl CadCommand for OrdinateDimCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
match self.step {
|
||||
Step::FeaturePoint => {
|
||||
self.step = Step::LeaderEndpoint { feature: pt };
|
||||
@@ -80,25 +80,30 @@ impl CadCommand for OrdinateDimCommand {
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> { let pt = pt.as_vec3();
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
let feature = match self.step {
|
||||
Step::LeaderEndpoint { feature } => feature,
|
||||
_ => return None,
|
||||
};
|
||||
let is_x = is_x_type(feature, pt);
|
||||
let elbow = ordinate_elbow(feature, pt, is_x);
|
||||
Some(preview_wire(vec![feature, elbow, pt]))
|
||||
// Screen-only rubber band: downcast to f32 at the preview boundary.
|
||||
Some(preview_wire(vec![
|
||||
feature.as_vec3(),
|
||||
elbow.as_vec3(),
|
||||
pt.as_vec3(),
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
fn v3(p: Vec3) -> Vector3 {
|
||||
Vector3::new(p.x as f64, p.y as f64, p.z as f64)
|
||||
fn v3(p: DVec3) -> Vector3 {
|
||||
Vector3::new(p.x, p.y, p.z)
|
||||
}
|
||||
|
||||
/// X-datum (labels the feature's X coordinate) when the leader runs more
|
||||
/// vertically than horizontally; Y-datum otherwise. Mirrors the placement
|
||||
/// decision so the preview and the committed entity agree.
|
||||
fn is_x_type(feature: Vec3, leader: Vec3) -> bool {
|
||||
fn is_x_type(feature: DVec3, leader: DVec3) -> bool {
|
||||
let dx = (leader.x - feature.x).abs();
|
||||
let dy = (leader.y - feature.y).abs();
|
||||
dy >= dx
|
||||
@@ -106,11 +111,11 @@ fn is_x_type(feature: Vec3, leader: Vec3) -> bool {
|
||||
|
||||
/// Orthogonal elbow of the ordinate leader: an X-datum runs along Y from the
|
||||
/// feature then jogs across in X; a Y-datum runs along X then jogs in Y.
|
||||
fn ordinate_elbow(feature: Vec3, leader: Vec3, is_x: bool) -> Vec3 {
|
||||
fn ordinate_elbow(feature: DVec3, leader: DVec3, is_x: bool) -> DVec3 {
|
||||
if is_x {
|
||||
Vec3::new(feature.x, leader.y, feature.z)
|
||||
DVec3::new(feature.x, leader.y, feature.z)
|
||||
} else {
|
||||
Vec3::new(leader.x, feature.y, feature.z)
|
||||
DVec3::new(leader.x, feature.y, feature.z)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
use acadrust::entities::{Dimension, DimensionLinear};
|
||||
use acadrust::types::Vector3;
|
||||
use acadrust::{EntityType, Handle};
|
||||
use glam::{DVec3, Vec3};
|
||||
use glam::DVec3;
|
||||
|
||||
use crate::command::{CadCommand, CmdResult};
|
||||
use crate::modules::{IconKind, ModuleEvent, ToolDef};
|
||||
@@ -67,9 +67,11 @@ impl CadCommand for QdimCommand {
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
if let Step::PlaceLine { handles } = &self.step {
|
||||
CmdResult::Relaunch(
|
||||
// Keep the picked placement point in full f64 precision so the
|
||||
// committed dimension coordinates are not quantized to f32.
|
||||
format!("QDIM_PLACE {:.6} {:.6} {:.6}", pt.x, pt.y, pt.z),
|
||||
handles.clone(),
|
||||
)
|
||||
@@ -112,8 +114,8 @@ impl CadCommand for QdimCommand {
|
||||
|
||||
/// Build a linear dimension between two points at the given dimension-line position.
|
||||
#[allow(dead_code)]
|
||||
pub fn make_linear_dim(p1: Vec3, p2: Vec3, dim_pt: Vec3) -> EntityType {
|
||||
let v = |p: Vec3| Vector3::new(p.x as f64, p.y as f64, p.z as f64);
|
||||
pub fn make_linear_dim(p1: DVec3, p2: DVec3, dim_pt: DVec3) -> EntityType {
|
||||
let v = |p: DVec3| Vector3::new(p.x, p.y, p.z);
|
||||
let mut dim = DimensionLinear::new(v(p1), v(p2));
|
||||
// Determine axis: horizontal if Δx > Δz, else vertical
|
||||
let dx = (p2.x - p1.x).abs();
|
||||
|
||||
@@ -20,8 +20,8 @@ pub fn tool() -> ToolDef {
|
||||
|
||||
enum Step {
|
||||
CenterPoint,
|
||||
RadiusPoint(Vec3),
|
||||
TextPoint { center: Vec3, point: Vec3 },
|
||||
RadiusPoint(DVec3),
|
||||
TextPoint { center: DVec3, point: DVec3 },
|
||||
}
|
||||
|
||||
pub struct RadiusDimensionCommand {
|
||||
@@ -69,7 +69,7 @@ impl CadCommand for RadiusDimensionCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
match self.step {
|
||||
Step::CenterPoint => {
|
||||
self.step = Step::RadiusPoint(pt);
|
||||
@@ -84,7 +84,7 @@ impl CadCommand for RadiusDimensionCommand {
|
||||
dim.base.definition_point = v3(point);
|
||||
dim.base.text_middle_point = v3(pt);
|
||||
dim.base.insertion_point = v3(pt);
|
||||
dim.leader_length = point.distance(pt) as f64;
|
||||
dim.leader_length = point.distance(pt);
|
||||
dim.base.actual_measurement = dim.measurement();
|
||||
dim.base.user_text = self.text_override.clone();
|
||||
// An explicit text angle overrides the default rotation.
|
||||
@@ -163,23 +163,24 @@ impl CadCommand for RadiusDimensionCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> { let pt = pt.as_vec3();
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
let pt = pt.as_vec3();
|
||||
match self.step {
|
||||
Step::CenterPoint => None,
|
||||
Step::RadiusPoint(center) => Some(preview_wire(vec![center, pt])),
|
||||
Step::RadiusPoint(center) => Some(preview_wire(vec![center.as_vec3(), pt])),
|
||||
Step::TextPoint { center, point } => Some(preview_wire(vec![
|
||||
center,
|
||||
point,
|
||||
center.as_vec3(),
|
||||
point.as_vec3(),
|
||||
Vec3::new(f32::NAN, f32::NAN, f32::NAN),
|
||||
point,
|
||||
point.as_vec3(),
|
||||
pt,
|
||||
])),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn v3(pt: Vec3) -> Vector3 {
|
||||
Vector3::new(pt.x as f64, pt.y as f64, pt.z as f64)
|
||||
fn v3(pt: DVec3) -> Vector3 {
|
||||
Vector3::new(pt.x, pt.y, pt.z)
|
||||
}
|
||||
|
||||
fn preview_wire(points: Vec<Vec3>) -> WireModel {
|
||||
|
||||
@@ -116,9 +116,9 @@ impl CadCommand for TableCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
if let Step::Insertion { cols, rows } = self.step {
|
||||
let ins = Vector3::new(pt.x as f64, pt.y as f64, pt.z as f64);
|
||||
let ins = Vector3::new(pt.x, pt.y, pt.z);
|
||||
let table = TableBuilder::new(rows, cols)
|
||||
.at(ins)
|
||||
.row_height(ROW_HEIGHT)
|
||||
|
||||
@@ -64,9 +64,9 @@ impl CadCommand for ToleranceCommand {
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
if let Step::Insertion { text } = &self.step {
|
||||
let ins = Vector3::new(pt.x as f64, pt.y as f64, pt.z as f64);
|
||||
let ins = Vector3::new(pt.x, pt.y, pt.z);
|
||||
let tol = Tolerance::with_text(ins, text.clone());
|
||||
CmdResult::CommitAndExit(EntityType::Tolerance(tol))
|
||||
} else {
|
||||
|
||||
@@ -106,7 +106,7 @@ impl CadCommand for AttdefCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
if let Step::Insertion {
|
||||
tag,
|
||||
prompt,
|
||||
@@ -117,7 +117,7 @@ impl CadCommand for AttdefCommand {
|
||||
tag: tag.clone(),
|
||||
prompt: prompt.clone(),
|
||||
default_value: default.clone(),
|
||||
insertion_point: Vector3::new(pt.x as f64, pt.y as f64, pt.z as f64),
|
||||
insertion_point: Vector3::new(pt.x, pt.y, pt.z),
|
||||
height: self.height,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
use acadrust::entities::MLine;
|
||||
use acadrust::types::Vector3;
|
||||
use acadrust::EntityType;
|
||||
use glam::{DVec3, Vec3};
|
||||
use glam::DVec3;
|
||||
|
||||
use crate::command::{CadCommand, CmdResult};
|
||||
use crate::scene::model::wire_model::WireModel;
|
||||
|
||||
pub struct MlineCommand {
|
||||
points: Vec<Vec3>,
|
||||
points: Vec<DVec3>,
|
||||
scale: f64,
|
||||
waiting_scale: bool,
|
||||
style_name: String,
|
||||
@@ -111,7 +111,7 @@ impl CadCommand for MlineCommand {
|
||||
None
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
self.points.push(pt);
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
@@ -124,11 +124,16 @@ impl CadCommand for MlineCommand {
|
||||
CmdResult::CommitAndExit(entity)
|
||||
}
|
||||
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> { let pt = pt.as_vec3();
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
let pt = pt.as_vec3();
|
||||
if self.points.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let mut pts: Vec<[f32; 3]> = self.points.iter().map(|p| [p.x, p.y, p.z]).collect();
|
||||
let mut pts: Vec<[f32; 3]> = self
|
||||
.points
|
||||
.iter()
|
||||
.map(|p| [p.x as f32, p.y as f32, p.z as f32])
|
||||
.collect();
|
||||
pts.push([pt.x, pt.y, pt.z]);
|
||||
Some(WireModel {
|
||||
text_verts: Vec::new(),
|
||||
@@ -153,10 +158,10 @@ impl CadCommand for MlineCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_mline(pts: &[Vec3], scale: f64, closed: bool, style_name: &str) -> EntityType {
|
||||
fn build_mline(pts: &[DVec3], scale: f64, closed: bool, style_name: &str) -> EntityType {
|
||||
let verts: Vec<Vector3> = pts
|
||||
.iter()
|
||||
.map(|p| Vector3::new(p.x as f64, p.y as f64, p.z as f64))
|
||||
.map(|p| Vector3::new(p.x, p.y, p.z))
|
||||
.collect();
|
||||
let mut mline = if closed {
|
||||
MLine::closed_from_points(&verts)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
use acadrust::entities::LwVertex;
|
||||
use acadrust::types::Vector2;
|
||||
use acadrust::{EntityType, Handle, LwPolyline};
|
||||
use glam::{DVec3, Vec2, Vec3};
|
||||
use glam::{DVec2, DVec3, Vec2, Vec3};
|
||||
|
||||
use crate::command::{CadCommand, CmdResult};
|
||||
use crate::modules::{IconKind, ModuleEvent, ToolDef};
|
||||
@@ -40,7 +40,7 @@ enum SegMode {
|
||||
// ── Command implementation ─────────────────────────────────────────────────
|
||||
|
||||
pub struct PlineCommand {
|
||||
vertices: Vec<Vec3>,
|
||||
vertices: Vec<DVec3>,
|
||||
/// Bulge for segment i → i+1 (one entry per vertex; last entry unused on commit).
|
||||
bulges: Vec<f64>,
|
||||
mode: SegMode,
|
||||
@@ -90,7 +90,7 @@ impl PlineCommand {
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, v)| {
|
||||
let mut lv = LwVertex::new(Vector2::new(v.x as f64, v.y as f64));
|
||||
let mut lv = LwVertex::new(Vector2::new(v.x, v.y));
|
||||
lv.bulge = self.bulges.get(i).copied().unwrap_or(0.0);
|
||||
lv
|
||||
})
|
||||
@@ -108,14 +108,14 @@ impl PlineCommand {
|
||||
|
||||
/// Compute the bulge for the arc from `a` to `b` that is tangent to `tangent` at `a`.
|
||||
/// Returns 0.0 if the points are coincident or the tangent is parallel to the chord.
|
||||
fn compute_bulge(a: Vec2, tangent: Vec2, b: Vec2) -> f64 {
|
||||
fn compute_bulge(a: DVec2, tangent: DVec2, b: DVec2) -> f64 {
|
||||
let d = b - a;
|
||||
let len_sq = d.length_squared();
|
||||
if len_sq < 1e-10 {
|
||||
return 0.0;
|
||||
}
|
||||
// Perpendicular to tangent (CCW) — this is the direction to the arc center.
|
||||
let perp = Vec2::new(-tangent.y, tangent.x);
|
||||
let perp = DVec2::new(-tangent.y, tangent.x);
|
||||
let dot = d.dot(perp);
|
||||
if dot.abs() < 1e-10 {
|
||||
// Tangent is perpendicular to chord → straight line (bulge = 0).
|
||||
@@ -133,15 +133,15 @@ fn compute_bulge(a: Vec2, tangent: Vec2, b: Vec2) -> f64 {
|
||||
if t > 0.0 {
|
||||
// CCW arc: ensure arc_angle is in (0, 2π].
|
||||
if arc_angle <= 0.0 {
|
||||
arc_angle += std::f32::consts::TAU;
|
||||
arc_angle += std::f64::consts::TAU;
|
||||
}
|
||||
} else {
|
||||
// CW arc: ensure arc_angle is in [-2π, 0).
|
||||
if arc_angle >= 0.0 {
|
||||
arc_angle -= std::f32::consts::TAU;
|
||||
arc_angle -= std::f64::consts::TAU;
|
||||
}
|
||||
}
|
||||
(arc_angle as f64 / 4.0).tan()
|
||||
(arc_angle / 4.0).tan()
|
||||
}
|
||||
|
||||
/// Update `tangent` after an arc segment described by `bulge` from `a` to `b`.
|
||||
@@ -240,26 +240,28 @@ impl CadCommand for PlineCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
if !self.vertices.is_empty() {
|
||||
let last = *self.vertices.last().unwrap();
|
||||
let last_idx = self.vertices.len() - 1;
|
||||
|
||||
let bulge = match self.mode {
|
||||
SegMode::Line => {
|
||||
let d = Vec2::new(pt.x - last.x, pt.y - last.y);
|
||||
let d = DVec2::new(pt.x - last.x, pt.y - last.y);
|
||||
if d.length_squared() > 1e-10 {
|
||||
self.last_tangent = Some(d.normalize());
|
||||
// Direction only — f32 is sufficient for tangent continuity.
|
||||
self.last_tangent = Some(d.normalize().as_vec2());
|
||||
}
|
||||
0.0
|
||||
}
|
||||
SegMode::Arc => {
|
||||
let a = Vec2::new(last.x, last.y);
|
||||
let b = Vec2::new(pt.x, pt.y);
|
||||
let tangent = self.last_tangent.unwrap_or_else(|| {
|
||||
let a = DVec2::new(last.x, last.y);
|
||||
let b = DVec2::new(pt.x, pt.y);
|
||||
let tangent = self
|
||||
.last_tangent
|
||||
.map(|t| t.as_dvec2())
|
||||
// No previous tangent: default to pointing right (arbitrary).
|
||||
Vec2::new(1.0, 0.0)
|
||||
});
|
||||
.unwrap_or(DVec2::new(1.0, 0.0));
|
||||
let bulge = compute_bulge(a, tangent, b);
|
||||
update_tangent_after_arc(&mut self.last_tangent, bulge);
|
||||
bulge
|
||||
@@ -318,11 +320,12 @@ impl CadCommand for PlineCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> { let pt = pt.as_vec3();
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
let pt = pt.as_vec3();
|
||||
// The committed vertices already render as a real document entity, so
|
||||
// the preview is just the pending segment from the last vertex to the
|
||||
// cursor. (#119)
|
||||
let last = *self.vertices.last()?;
|
||||
// cursor. (#119) Downcast to f32 for the pixel-space rubber band.
|
||||
let last = self.vertices.last()?.as_vec3();
|
||||
|
||||
let mut pts: Vec<[f32; 3]> = Vec::new();
|
||||
match self.mode {
|
||||
@@ -331,9 +334,12 @@ impl CadCommand for PlineCommand {
|
||||
pts.push([pt.x, pt.y, pt.z]);
|
||||
}
|
||||
SegMode::Arc => {
|
||||
let a = Vec2::new(last.x, last.y);
|
||||
let b = Vec2::new(pt.x, pt.y);
|
||||
let tangent = self.last_tangent.unwrap_or(Vec2::new(1.0, 0.0));
|
||||
let a = DVec2::new(last.x as f64, last.y as f64);
|
||||
let b = DVec2::new(pt.x as f64, pt.y as f64);
|
||||
let tangent = self
|
||||
.last_tangent
|
||||
.map(|t| t.as_dvec2())
|
||||
.unwrap_or(DVec2::new(1.0, 0.0));
|
||||
let bulge = compute_bulge(a, tangent, b);
|
||||
let arc_pts = arc_sample_points(last, bulge, pt, 16);
|
||||
pts.extend_from_slice(&arc_pts);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
use acadrust::entities::RasterImage;
|
||||
use acadrust::types::Vector3;
|
||||
use acadrust::EntityType;
|
||||
use glam::{DVec3, Vec3};
|
||||
use glam::DVec3;
|
||||
|
||||
use crate::command::{CadCommand, CmdResult};
|
||||
use crate::scene::model::wire_model::WireModel;
|
||||
@@ -18,7 +18,7 @@ pub struct ImageCommand {
|
||||
file_path: String,
|
||||
pixel_width: u32,
|
||||
pixel_height: u32,
|
||||
origin: Option<Vec3>,
|
||||
origin: Option<DVec3>,
|
||||
}
|
||||
|
||||
impl ImageCommand {
|
||||
@@ -39,11 +39,11 @@ impl ImageCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn make_entity(&self, origin: Vec3, width_pt: Vec3) -> EntityType {
|
||||
let world_width = ((width_pt.x - origin.x) as f64).abs().max(0.001);
|
||||
fn make_entity(&self, origin: DVec3, width_pt: DVec3) -> EntityType {
|
||||
let world_width = (width_pt.x - origin.x).abs().max(0.001);
|
||||
let world_height = world_width / self.aspect();
|
||||
|
||||
let ins = Vector3::new(origin.x as f64, origin.y as f64, origin.z as f64);
|
||||
let ins = Vector3::new(origin.x, origin.y, origin.z);
|
||||
|
||||
let mut img = RasterImage::with_size(
|
||||
&self.file_path,
|
||||
@@ -75,7 +75,7 @@ impl CadCommand for ImageCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
if let Some(origin) = self.origin {
|
||||
let entity = self.make_entity(origin, pt);
|
||||
CmdResult::CommitAndExit(entity)
|
||||
@@ -89,7 +89,7 @@ impl CadCommand for ImageCommand {
|
||||
// If origin is set, place with a default width of 1 unit * pixel count / 100
|
||||
if let Some(origin) = self.origin {
|
||||
let default_w = (self.pixel_width as f64 / 100.0).max(1.0);
|
||||
let width_pt = Vec3::new(origin.x + default_w as f32, origin.y, origin.z);
|
||||
let width_pt = DVec3::new(origin.x + default_w, origin.y, origin.z);
|
||||
let entity = self.make_entity(origin, width_pt);
|
||||
CmdResult::CommitAndExit(entity)
|
||||
} else {
|
||||
@@ -98,7 +98,7 @@ impl CadCommand for ImageCommand {
|
||||
}
|
||||
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> { let pt = pt.as_vec3();
|
||||
let origin = self.origin?;
|
||||
let origin = self.origin?.as_vec3();
|
||||
let world_width = (pt.x - origin.x).abs().max(0.001);
|
||||
let world_height = world_width / self.aspect() as f32;
|
||||
|
||||
|
||||
@@ -10,14 +10,14 @@ use acadrust::EntityType;
|
||||
|
||||
use crate::command::{CadCommand, CmdResult};
|
||||
use crate::scene::model::wire_model::WireModel;
|
||||
use glam::{DVec3, Vec3};
|
||||
use glam::DVec3;
|
||||
|
||||
const DISPLAY_EXTENT: f32 = 1_000_000.0;
|
||||
|
||||
// ── RAY ───────────────────────────────────────────────────────────────────
|
||||
|
||||
pub struct RayCommand {
|
||||
base: Option<Vec3>,
|
||||
base: Option<DVec3>,
|
||||
}
|
||||
|
||||
impl RayCommand {
|
||||
@@ -48,7 +48,7 @@ impl CadCommand for RayCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
if let Some(base) = self.base {
|
||||
let dir = pt - base;
|
||||
let len = dir.length();
|
||||
@@ -57,8 +57,8 @@ impl CadCommand for RayCommand {
|
||||
}
|
||||
let dir_n = dir / len;
|
||||
let ray = RayEnt::new(
|
||||
Vector3::new(base.x as f64, base.y as f64, base.z as f64),
|
||||
Vector3::new(dir_n.x as f64, dir_n.y as f64, dir_n.z as f64),
|
||||
Vector3::new(base.x, base.y, base.z),
|
||||
Vector3::new(dir_n.x, dir_n.y, dir_n.z),
|
||||
);
|
||||
// Stay active: new base = same base (can keep clicking through points)
|
||||
// Actually AutoCAD prompts for new start after each ray — reset base.
|
||||
@@ -78,8 +78,9 @@ impl CadCommand for RayCommand {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> { let pt = pt.as_vec3();
|
||||
let base = self.base?;
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
let pt = pt.as_vec3();
|
||||
let base = self.base?.as_vec3();
|
||||
let dir = (pt - base).normalize_or_zero();
|
||||
let far = base + dir * DISPLAY_EXTENT;
|
||||
Some(WireModel {
|
||||
@@ -108,7 +109,7 @@ impl CadCommand for RayCommand {
|
||||
// ── XLINE ─────────────────────────────────────────────────────────────────
|
||||
|
||||
pub struct XLineCommand {
|
||||
base: Option<Vec3>,
|
||||
base: Option<DVec3>,
|
||||
}
|
||||
|
||||
impl XLineCommand {
|
||||
@@ -139,7 +140,7 @@ impl CadCommand for XLineCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
if let Some(base) = self.base {
|
||||
let dir = pt - base;
|
||||
let len = dir.length();
|
||||
@@ -148,8 +149,8 @@ impl CadCommand for XLineCommand {
|
||||
}
|
||||
let dir_n = dir / len;
|
||||
let xline = XLineEnt::new(
|
||||
Vector3::new(base.x as f64, base.y as f64, base.z as f64),
|
||||
Vector3::new(dir_n.x as f64, dir_n.y as f64, dir_n.z as f64),
|
||||
Vector3::new(base.x, base.y, base.z),
|
||||
Vector3::new(dir_n.x, dir_n.y, dir_n.z),
|
||||
);
|
||||
self.base = None;
|
||||
CmdResult::CommitEntity(EntityType::XLine(xline))
|
||||
@@ -167,8 +168,9 @@ impl CadCommand for XLineCommand {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> { let pt = pt.as_vec3();
|
||||
let base = self.base?;
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
let pt = pt.as_vec3();
|
||||
let base = self.base?.as_vec3();
|
||||
let dir = (pt - base).normalize_or_zero();
|
||||
let far_pos = base + dir * DISPLAY_EXTENT;
|
||||
let far_neg = base - dir * DISPLAY_EXTENT;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
use acadrust::entities::LwPolyline;
|
||||
use acadrust::types::Vector2;
|
||||
use acadrust::{entities::LwVertex, EntityType};
|
||||
use glam::{DVec3, Vec3};
|
||||
use glam::DVec3;
|
||||
|
||||
use crate::command::{CadCommand, CmdResult};
|
||||
use crate::modules::{IconKind, ModuleEvent, ToolDef};
|
||||
@@ -27,7 +27,7 @@ pub fn tool() -> ToolDef {
|
||||
const DEFAULT_ARC_LEN: f64 = 1.0; // default arc bump length
|
||||
|
||||
pub struct RevCloudCommand {
|
||||
points: Vec<Vec3>,
|
||||
points: Vec<DVec3>,
|
||||
arc_length: f64,
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ impl CadCommand for RevCloudCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
self.points.push(pt);
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
@@ -72,13 +72,22 @@ impl CadCommand for RevCloudCommand {
|
||||
CmdResult::CommitAndExit(entity)
|
||||
}
|
||||
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> { let pt = pt.as_vec3();
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
let pt = pt.as_vec3();
|
||||
if self.points.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let mut preview_pts: Vec<[f32; 3]> = self.points.iter().map(|p| [p.x, p.y, p.z]).collect();
|
||||
let mut preview_pts: Vec<[f32; 3]> = self
|
||||
.points
|
||||
.iter()
|
||||
.map(|p| [p.x as f32, p.y as f32, p.z as f32])
|
||||
.collect();
|
||||
preview_pts.push([pt.x, pt.y, pt.z]);
|
||||
preview_pts.push([self.points[0].x, self.points[0].y, self.points[0].z]);
|
||||
preview_pts.push([
|
||||
self.points[0].x as f32,
|
||||
self.points[0].y as f32,
|
||||
self.points[0].z as f32,
|
||||
]);
|
||||
Some(WireModel {
|
||||
text_verts: Vec::new(),
|
||||
name: "revcloud_preview".into(),
|
||||
@@ -102,7 +111,7 @@ impl CadCommand for RevCloudCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn make_revcloud(pts: &[Vec3], arc_len: f64) -> EntityType {
|
||||
fn make_revcloud(pts: &[DVec3], arc_len: f64) -> EntityType {
|
||||
let n = pts.len();
|
||||
let mut vertices: Vec<LwVertex> = Vec::new();
|
||||
|
||||
@@ -112,19 +121,19 @@ fn make_revcloud(pts: &[Vec3], arc_len: f64) -> EntityType {
|
||||
for i in 0..n {
|
||||
let p0 = pts[i];
|
||||
let p1 = pts[(i + 1) % n];
|
||||
let seg_len = ((p1.x - p0.x).powi(2) + (p1.y - p0.y).powi(2)).sqrt() as f64;
|
||||
let seg_len = ((p1.x - p0.x).powi(2) + (p1.y - p0.y).powi(2)).sqrt();
|
||||
if seg_len < 1e-6 {
|
||||
continue;
|
||||
}
|
||||
|
||||
// How many full arcs fit?
|
||||
let num_arcs = ((seg_len / arc_len).round() as usize).max(1);
|
||||
let step_x = (p1.x - p0.x) as f64 / num_arcs as f64;
|
||||
let step_y = (p1.y - p0.y) as f64 / num_arcs as f64;
|
||||
let step_x = (p1.x - p0.x) / num_arcs as f64;
|
||||
let step_y = (p1.y - p0.y) / num_arcs as f64;
|
||||
|
||||
for j in 0..num_arcs {
|
||||
let x = p0.x as f64 + step_x * j as f64;
|
||||
let y = p0.y as f64 + step_y * j as f64; // DXF Y
|
||||
let x = p0.x + step_x * j as f64;
|
||||
let y = p0.y + step_y * j as f64; // DXF Y
|
||||
let mut v = LwVertex::new(Vector2::new(x, y));
|
||||
v.bulge = bump_bulge;
|
||||
vertices.push(v);
|
||||
|
||||
@@ -9,7 +9,7 @@ use acadrust::{EntityType, Spline};
|
||||
use crate::command::{CadCommand, CmdResult};
|
||||
use crate::modules::{IconKind, ModuleEvent, ToolDef};
|
||||
use crate::scene::model::wire_model::WireModel;
|
||||
use glam::{DVec3, Vec3};
|
||||
use glam::DVec3;
|
||||
use truck_modeling::base::{BoundedCurve, ParametricCurve};
|
||||
use truck_modeling::{BSplineCurve, KnotVec, Point3};
|
||||
|
||||
@@ -24,7 +24,7 @@ pub fn tool() -> ToolDef {
|
||||
}
|
||||
|
||||
pub struct SplineCommand {
|
||||
pts: Vec<Vec3>,
|
||||
pts: Vec<DVec3>,
|
||||
}
|
||||
|
||||
impl SplineCommand {
|
||||
@@ -39,7 +39,7 @@ impl SplineCommand {
|
||||
let control_points = self
|
||||
.pts
|
||||
.iter()
|
||||
.map(|p| Vector3::new(p.x as f64, p.y as f64, p.z as f64))
|
||||
.map(|p| Vector3::new(p.x, p.y, p.z))
|
||||
.collect();
|
||||
let n = self.pts.len();
|
||||
// Uniform open knot vector for degree-3 clamped B-spline
|
||||
@@ -77,15 +77,18 @@ fn uniform_knots(n: usize, d: usize) -> Vec<f64> {
|
||||
/// built with the same control points + knot vector `build()` commits so the
|
||||
/// preview traces the exact final curve — not the straight control polygon.
|
||||
/// Fewer than 2 points has no curve; the points are returned as-is.
|
||||
fn sample_curve(pts: &[Vec3]) -> Vec<[f32; 3]> {
|
||||
fn sample_curve(pts: &[DVec3]) -> Vec<[f32; 3]> {
|
||||
let n = pts.len();
|
||||
if n < 2 {
|
||||
return pts.iter().map(|p| [p.x, p.y, p.z]).collect();
|
||||
return pts
|
||||
.iter()
|
||||
.map(|p| [p.x as f32, p.y as f32, p.z as f32])
|
||||
.collect();
|
||||
}
|
||||
let degree = 3_usize.min(n - 1);
|
||||
let ctrl: Vec<Point3> = pts
|
||||
.iter()
|
||||
.map(|p| Point3::new(p.x as f64, p.y as f64, p.z as f64))
|
||||
.map(|p| Point3::new(p.x, p.y, p.z))
|
||||
.collect();
|
||||
let curve = BSplineCurve::new(KnotVec::from(uniform_knots(n, degree)), ctrl);
|
||||
let (t0, t1) = curve.range_tuple();
|
||||
@@ -125,7 +128,7 @@ impl CadCommand for SplineCommand {
|
||||
opts
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
self.pts.push(pt);
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
@@ -170,7 +173,7 @@ impl CadCommand for SplineCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> { let pt = pt.as_vec3();
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
if self.pts.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
use acadrust::entities::Wipeout;
|
||||
use acadrust::types::Vector3;
|
||||
use acadrust::EntityType;
|
||||
use glam::{DVec3, Vec3};
|
||||
use glam::DVec3;
|
||||
|
||||
use crate::command::{CadCommand, CmdResult};
|
||||
use crate::modules::{IconKind, ModuleEvent, ToolDef};
|
||||
@@ -26,8 +26,8 @@ pub fn tool() -> ToolDef {
|
||||
|
||||
pub struct WipeoutCommand {
|
||||
mode: WipeoutMode,
|
||||
first: Option<Vec3>,
|
||||
poly_pts: Vec<Vec3>,
|
||||
first: Option<DVec3>,
|
||||
poly_pts: Vec<DVec3>,
|
||||
}
|
||||
|
||||
enum WipeoutMode {
|
||||
@@ -79,7 +79,7 @@ impl CadCommand for WipeoutCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
match &self.mode {
|
||||
WipeoutMode::Rectangular => {
|
||||
if let Some(p1) = self.first {
|
||||
@@ -110,7 +110,7 @@ impl CadCommand for WipeoutCommand {
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> { let pt = pt.as_vec3();
|
||||
match &self.mode {
|
||||
WipeoutMode::Rectangular => {
|
||||
let p1 = self.first?;
|
||||
let p1 = self.first?.as_vec3();
|
||||
let min = p1.min(pt);
|
||||
let max = p1.max(pt);
|
||||
Some(WireModel {
|
||||
@@ -144,10 +144,17 @@ impl CadCommand for WipeoutCommand {
|
||||
if self.poly_pts.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let mut pts: Vec<[f32; 3]> =
|
||||
self.poly_pts.iter().map(|p| [p.x, p.y, p.z]).collect();
|
||||
let mut pts: Vec<[f32; 3]> = self
|
||||
.poly_pts
|
||||
.iter()
|
||||
.map(|p| [p.x as f32, p.y as f32, p.z as f32])
|
||||
.collect();
|
||||
pts.push([pt.x, pt.y, pt.z]);
|
||||
pts.push([self.poly_pts[0].x, self.poly_pts[0].y, self.poly_pts[0].z]);
|
||||
pts.push([
|
||||
self.poly_pts[0].x as f32,
|
||||
self.poly_pts[0].y as f32,
|
||||
self.poly_pts[0].z as f32,
|
||||
]);
|
||||
Some(WireModel {
|
||||
text_verts: Vec::new(),
|
||||
name: "wipeout_poly_preview".into(),
|
||||
@@ -173,20 +180,17 @@ impl CadCommand for WipeoutCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn make_rect_wipeout(p1: Vec3, p2: Vec3) -> EntityType {
|
||||
fn make_rect_wipeout(p1: DVec3, p2: DVec3) -> EntityType {
|
||||
// Drawing plane is world XY (z = elevation).
|
||||
let c1 = Vector3::new(p1.x.min(p2.x) as f64, p1.y.min(p2.y) as f64, p1.z as f64);
|
||||
let c2 = Vector3::new(p1.x.max(p2.x) as f64, p1.y.max(p2.y) as f64, p1.z as f64);
|
||||
let c1 = Vector3::new(p1.x.min(p2.x), p1.y.min(p2.y), p1.z);
|
||||
let c2 = Vector3::new(p1.x.max(p2.x), p1.y.max(p2.y), p1.z);
|
||||
EntityType::Wipeout(Wipeout::from_corners(c1, c2))
|
||||
}
|
||||
|
||||
fn make_poly_wipeout(pts: &[Vec3]) -> EntityType {
|
||||
fn make_poly_wipeout(pts: &[DVec3]) -> EntityType {
|
||||
use acadrust::types::Vector2;
|
||||
let z = pts[0].z as f64;
|
||||
let verts: Vec<Vector2> = pts
|
||||
.iter()
|
||||
.map(|p| Vector2::new(p.x as f64, p.y as f64))
|
||||
.collect();
|
||||
let z = pts[0].z;
|
||||
let verts: Vec<Vector2> = pts.iter().map(|p| Vector2::new(p.x, p.y)).collect();
|
||||
EntityType::Wipeout(Wipeout::polygonal(&verts, z))
|
||||
}
|
||||
|
||||
|
||||
@@ -10,17 +10,17 @@
|
||||
// With 2 pairs: translate + rotate (+ optional uniform scale to fit)
|
||||
|
||||
use acadrust::Handle;
|
||||
use glam::{DVec3, Vec3};
|
||||
use glam::DVec3;
|
||||
|
||||
use crate::command::{CadCommand, CmdResult, EntityTransform};
|
||||
|
||||
pub struct AlignCommand {
|
||||
state: AlignState,
|
||||
handles: Vec<Handle>,
|
||||
src1: Option<Vec3>,
|
||||
dst1: Option<Vec3>,
|
||||
src2: Option<Vec3>,
|
||||
dst2: Option<Vec3>,
|
||||
src1: Option<DVec3>,
|
||||
dst1: Option<DVec3>,
|
||||
src2: Option<DVec3>,
|
||||
dst2: Option<DVec3>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
@@ -74,7 +74,7 @@ impl CadCommand for AlignCommand {
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
match self.state {
|
||||
AlignState::Gathering => CmdResult::NeedPoint,
|
||||
AlignState::Src1 => {
|
||||
@@ -117,7 +117,7 @@ impl CadCommand for AlignCommand {
|
||||
let delta = d - s;
|
||||
CmdResult::TransformSelected(
|
||||
self.handles.clone(),
|
||||
EntityTransform::Translate(delta.as_dvec3()),
|
||||
EntityTransform::Translate(delta),
|
||||
)
|
||||
}
|
||||
_ => CmdResult::Cancel,
|
||||
@@ -163,7 +163,7 @@ impl AlignCommand {
|
||||
let delta = d1 - s1;
|
||||
return CmdResult::TransformSelected(
|
||||
self.handles.clone(),
|
||||
EntityTransform::Translate(delta.as_dvec3()),
|
||||
EntityTransform::Translate(delta),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -182,10 +182,12 @@ impl AlignCommand {
|
||||
// Compose via AlignTransform CmdResult
|
||||
CmdResult::AlignSelected {
|
||||
handles: self.handles.clone(),
|
||||
src1: s1.as_dvec3(),
|
||||
dst1: d1.as_dvec3(),
|
||||
angle_rad: angle,
|
||||
scale: scale_factor,
|
||||
src1: s1,
|
||||
dst1: d1,
|
||||
// Reference points (src1/dst1) stay f64 exact; the derived
|
||||
// similarity scalars feed the f32 EntityTransform, so downcast here.
|
||||
angle_rad: angle as f32,
|
||||
scale: scale_factor as f32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// 1. Center point → 2. Item count (text) → 3. Total angle in degrees (text)
|
||||
|
||||
use acadrust::Handle;
|
||||
use glam::{DVec3, Mat4, Vec3};
|
||||
use glam::{DVec3, Mat4};
|
||||
|
||||
use crate::command::{CadCommand, CmdResult, EntityTransform};
|
||||
use crate::modules::draw::defaults;
|
||||
@@ -246,8 +246,8 @@ impl CadCommand for ArrayRectCommand {
|
||||
|
||||
enum PolarStep {
|
||||
Center,
|
||||
Count { center: Vec3 },
|
||||
Angle { center: Vec3, count: u32 },
|
||||
Count { center: DVec3 },
|
||||
Angle { center: DVec3, count: u32 },
|
||||
}
|
||||
|
||||
pub struct ArrayPolarCommand {
|
||||
@@ -331,7 +331,6 @@ impl CadCommand for ArrayPolarCommand {
|
||||
v
|
||||
};
|
||||
let step_rad = total_deg.to_radians() / count as f32;
|
||||
let center = center.as_dvec3();
|
||||
let transforms = (1..count)
|
||||
.map(|n| EntityTransform::Rotate {
|
||||
center,
|
||||
@@ -348,8 +347,12 @@ impl CadCommand for ArrayPolarCommand {
|
||||
let pt = pt.as_vec3();
|
||||
let (center, count, total_deg) = match &self.step {
|
||||
PolarStep::Center => (pt, self.default_count, self.default_angle),
|
||||
PolarStep::Count { center } => (*center, self.default_count, self.default_angle),
|
||||
PolarStep::Angle { center, count } => (*center, *count, self.default_angle),
|
||||
PolarStep::Count { center } => {
|
||||
(center.as_vec3(), self.default_count, self.default_angle)
|
||||
}
|
||||
PolarStep::Angle { center, count } => {
|
||||
(center.as_vec3(), *count, self.default_angle)
|
||||
}
|
||||
};
|
||||
let step_rad = total_deg.to_radians() / count as f32;
|
||||
let mut out: Vec<WireModel> = (1..count)
|
||||
@@ -373,7 +376,6 @@ impl CadCommand for ArrayPolarCommand {
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
let pt = pt.as_vec3();
|
||||
if let PolarStep::Center = self.step {
|
||||
self.step = PolarStep::Count { center: pt };
|
||||
}
|
||||
@@ -398,8 +400,8 @@ impl CadCommand for ArrayPolarCommand {
|
||||
// → Returns BatchCopy with Translate transforms derived from path samples.
|
||||
|
||||
use acadrust::EntityType;
|
||||
use std::f32::consts::PI as FPI;
|
||||
use std::f32::consts::TAU as FTAU;
|
||||
use std::f64::consts::PI as FPI;
|
||||
use std::f64::consts::TAU as FTAU;
|
||||
|
||||
// ── Path geometry helpers ──────────────────────────────────────────────────
|
||||
|
||||
@@ -408,7 +410,7 @@ use std::f32::consts::TAU as FTAU;
|
||||
/// sweep) and every point is mapped OCS→WCS via the polyline's normal. The
|
||||
/// previous ad-hoc bulge math placed the arc centre on the wrong side of the
|
||||
/// chord and ignored the extrusion, so arc-segment paths came out mirrored.
|
||||
fn lw_dense_pts(p: &acadrust::entities::LwPolyline) -> Vec<Vec3> {
|
||||
fn lw_dense_pts(p: &acadrust::entities::LwPolyline) -> Vec<DVec3> {
|
||||
use crate::entities::common::BulgeArc;
|
||||
use crate::scene::view::transform::ocs_point_to_wcs;
|
||||
|
||||
@@ -418,14 +420,14 @@ fn lw_dense_pts(p: &acadrust::entities::LwPolyline) -> Vec<Vec3> {
|
||||
}
|
||||
let normal = (p.normal.x, p.normal.y, p.normal.z);
|
||||
let elev = p.elevation;
|
||||
let to_wcs = |x: f64, y: f64| -> Vec3 {
|
||||
let to_wcs = |x: f64, y: f64| -> DVec3 {
|
||||
let (wx, wy, wz) = ocs_point_to_wcs((x, y, elev), normal);
|
||||
Vec3::new(wx as f32, wy as f32, wz as f32)
|
||||
DVec3::new(wx, wy, wz)
|
||||
};
|
||||
|
||||
let n = verts.len();
|
||||
let segs = if p.is_closed { n } else { n.saturating_sub(1) };
|
||||
let mut out: Vec<Vec3> = vec![];
|
||||
let mut out: Vec<DVec3> = vec![];
|
||||
|
||||
for i in 0..segs {
|
||||
let v0 = &verts[i];
|
||||
@@ -456,15 +458,15 @@ fn lw_dense_pts(p: &acadrust::entities::LwPolyline) -> Vec<Vec3> {
|
||||
}
|
||||
|
||||
/// Walk `pts` (ordered) and return `count` points at equal arc-length spacing.
|
||||
fn subsample_equidistant(pts: &[Vec3], count: usize) -> Vec<Vec3> {
|
||||
fn subsample_equidistant(pts: &[DVec3], count: usize) -> Vec<DVec3> {
|
||||
if count == 0 {
|
||||
return vec![];
|
||||
}
|
||||
if pts.len() < 2 {
|
||||
return vec![pts.first().copied().unwrap_or(Vec3::ZERO); count];
|
||||
return vec![pts.first().copied().unwrap_or(DVec3::ZERO); count];
|
||||
}
|
||||
|
||||
let mut cum = vec![0.0f32; pts.len()];
|
||||
let mut cum = vec![0.0f64; pts.len()];
|
||||
for i in 1..pts.len() {
|
||||
cum[i] = cum[i - 1] + pts[i].distance(pts[i - 1]);
|
||||
}
|
||||
@@ -477,7 +479,7 @@ fn subsample_equidistant(pts: &[Vec3], count: usize) -> Vec<Vec3> {
|
||||
let mut seg = 0usize;
|
||||
for i in 0..count {
|
||||
let target = if count > 1 {
|
||||
total * i as f32 / (count - 1) as f32
|
||||
total * i as f64 / (count - 1) as f64
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
@@ -512,7 +514,7 @@ pub struct ArrayPathCommand {
|
||||
/// path end nearest this point, so clicking near either end picks the
|
||||
/// travel direction (a stored arc is always CCW regardless of how it was
|
||||
/// drawn, so without this the array could run opposite to expectation).
|
||||
pick_pt: Vec3,
|
||||
pick_pt: DVec3,
|
||||
}
|
||||
|
||||
impl ArrayPathCommand {
|
||||
@@ -527,13 +529,13 @@ impl ArrayPathCommand {
|
||||
all_entities,
|
||||
step: PathStep::SelectPath,
|
||||
default_count: defaults::get_array_path_count() as u32,
|
||||
pick_pt: Vec3::ZERO,
|
||||
pick_pt: DVec3::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sample the path and orient the points so index 0 is the end nearest the
|
||||
/// pick point, giving the user control over the array's travel direction.
|
||||
fn oriented_samples(&self, entity: &EntityType, count: usize) -> Vec<Vec3> {
|
||||
fn oriented_samples(&self, entity: &EntityType, count: usize) -> Vec<DVec3> {
|
||||
let mut pts = Self::sample_path(entity, count);
|
||||
if pts.len() >= 2 {
|
||||
let d_first = self.pick_pt.distance_squared(pts[0]);
|
||||
@@ -546,16 +548,16 @@ impl ArrayPathCommand {
|
||||
}
|
||||
|
||||
/// Sample `count` evenly-spaced points along `entity`.
|
||||
fn sample_path(entity: &EntityType, count: usize) -> Vec<Vec3> {
|
||||
fn sample_path(entity: &EntityType, count: usize) -> Vec<DVec3> {
|
||||
if count == 0 {
|
||||
return vec![];
|
||||
}
|
||||
match entity {
|
||||
EntityType::Line(l) => {
|
||||
let p0 = Vec3::new(l.start.x as f32, l.start.y as f32, 0.0);
|
||||
let p1 = Vec3::new(l.end.x as f32, l.end.y as f32, 0.0);
|
||||
let d = (count - 1).max(1) as f32;
|
||||
(0..count).map(|i| p0.lerp(p1, i as f32 / d)).collect()
|
||||
let p0 = DVec3::new(l.start.x, l.start.y, 0.0);
|
||||
let p1 = DVec3::new(l.end.x, l.end.y, 0.0);
|
||||
let d = (count - 1).max(1) as f64;
|
||||
(0..count).map(|i| p0.lerp(p1, i as f64 / d)).collect()
|
||||
}
|
||||
EntityType::Arc(a) => {
|
||||
// Sample in WCS via the arc's OCS basis so flipped-normal arcs
|
||||
@@ -576,10 +578,10 @@ impl ArrayPathCommand {
|
||||
.map(|i| {
|
||||
let ang = sa + span * (i as f64 / d);
|
||||
let (c, s) = (ang.cos(), ang.sin());
|
||||
Vec3::new(
|
||||
(cx + r * c * ax.0 + r * s * ay.0) as f32,
|
||||
(cy + r * c * ax.1 + r * s * ay.1) as f32,
|
||||
(cz + r * c * ax.2 + r * s * ay.2) as f32,
|
||||
DVec3::new(
|
||||
cx + r * c * ax.0 + r * s * ay.0,
|
||||
cy + r * c * ax.1 + r * s * ay.1,
|
||||
cz + r * c * ax.2 + r * s * ay.2,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
@@ -595,25 +597,25 @@ impl ArrayPathCommand {
|
||||
.map(|i| {
|
||||
let ang = i as f64 / count as f64 * std::f64::consts::TAU;
|
||||
let (cs, sn) = (ang.cos(), ang.sin());
|
||||
Vec3::new(
|
||||
(cx + r * cs * ax.0 + r * sn * ay.0) as f32,
|
||||
(cy + r * cs * ax.1 + r * sn * ay.1) as f32,
|
||||
(cz + r * cs * ax.2 + r * sn * ay.2) as f32,
|
||||
DVec3::new(
|
||||
cx + r * cs * ax.0 + r * sn * ay.0,
|
||||
cy + r * cs * ax.1 + r * sn * ay.1,
|
||||
cz + r * cs * ax.2 + r * sn * ay.2,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
EntityType::LwPolyline(p) => subsample_equidistant(&lw_dense_pts(p), count),
|
||||
_ => vec![Vec3::ZERO; count],
|
||||
_ => vec![DVec3::ZERO; count],
|
||||
}
|
||||
}
|
||||
|
||||
/// Continuous path-tangent angle (XY) at each sample, via central
|
||||
/// differences and unwrapped so the sequence stays smooth across an arc
|
||||
/// that turns more than ±π in total.
|
||||
fn tangents(pts: &[Vec3]) -> Vec<f32> {
|
||||
fn tangents(pts: &[DVec3]) -> Vec<f64> {
|
||||
let n = pts.len();
|
||||
let mut a = vec![0.0f32; n];
|
||||
let mut a = vec![0.0f64; n];
|
||||
for i in 0..n {
|
||||
let prev = if i > 0 { pts[i - 1] } else { pts[i] };
|
||||
let next = if i + 1 < n { pts[i + 1] } else { pts[i] };
|
||||
@@ -645,7 +647,7 @@ impl ArrayPathCommand {
|
||||
/// orientation; each copy rotates by the tangent change at its sample).
|
||||
/// On a straight path the tangent never changes, so every copy is a pure
|
||||
/// `Translate` — identical to a non-aligned array.
|
||||
fn build_transforms(pts: &[Vec3]) -> Vec<EntityTransform> {
|
||||
fn build_transforms(pts: &[DVec3]) -> Vec<EntityTransform> {
|
||||
if pts.len() < 2 {
|
||||
return vec![];
|
||||
}
|
||||
@@ -658,14 +660,14 @@ impl ArrayPathCommand {
|
||||
.map(|(i, &p)| {
|
||||
let dth = tans[i] - t0;
|
||||
if dth.abs() < 1e-5 {
|
||||
EntityTransform::Translate((p - p0).as_dvec3())
|
||||
EntityTransform::Translate(p - p0)
|
||||
} else {
|
||||
// The aligned copy is the rigid motion x' = p + R(x - p0),
|
||||
// which is a pure rotation by `dth` about its fixed point.
|
||||
let center = Self::rigid_center(p0, p, dth);
|
||||
EntityTransform::Rotate {
|
||||
center: center.as_dvec3(),
|
||||
angle_rad: dth,
|
||||
center,
|
||||
angle_rad: dth as f32,
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -674,7 +676,7 @@ impl ArrayPathCommand {
|
||||
|
||||
/// Fixed point of the rigid motion `x' = pi + R(dth)·(x - p0)`; rotating an
|
||||
/// entity by `dth` about this point reproduces that motion exactly.
|
||||
fn rigid_center(p0: Vec3, pi: Vec3, dth: f32) -> Vec3 {
|
||||
fn rigid_center(p0: DVec3, pi: DVec3, dth: f64) -> DVec3 {
|
||||
let (s, c) = dth.sin_cos();
|
||||
// b = pi - R·p0
|
||||
let bx = pi.x - (c * p0.x - s * p0.y);
|
||||
@@ -682,7 +684,7 @@ impl ArrayPathCommand {
|
||||
// c = (I - R)^{-1} · b, with (I-R)^{-1} = 1/(2(1-c)) [[1-c, -s],[s, 1-c]]
|
||||
let one_c = 1.0 - c;
|
||||
let det = 2.0 * one_c;
|
||||
Vec3::new(
|
||||
DVec3::new(
|
||||
(one_c * bx - s * by) / det,
|
||||
(s * bx + one_c * by) / det,
|
||||
p0.z,
|
||||
@@ -724,7 +726,6 @@ impl CadCommand for ArrayPathCommand {
|
||||
}
|
||||
|
||||
fn on_entity_pick(&mut self, handle: Handle, pt: DVec3) -> CmdResult {
|
||||
let pt = pt.as_vec3();
|
||||
if handle.is_null() || self.handles.contains(&handle) {
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
@@ -758,7 +759,9 @@ impl CadCommand for ArrayPathCommand {
|
||||
if pts.len() >= 2 {
|
||||
return vec![WireModel::solid(
|
||||
"arraypath_hover".into(),
|
||||
pts.iter().map(|p| [p.x, p.y, p.z]).collect(),
|
||||
pts.iter()
|
||||
.map(|p| [p.x as f32, p.y as f32, p.z as f32])
|
||||
.collect(),
|
||||
WireModel::CYAN,
|
||||
false,
|
||||
)];
|
||||
|
||||
@@ -15,7 +15,7 @@ use acadrust::entities::{
|
||||
};
|
||||
use acadrust::types::Vector3;
|
||||
use acadrust::{EntityType, Handle};
|
||||
use glam::{DVec3, Vec3};
|
||||
use glam::DVec3;
|
||||
use truck_modeling::base::{BoundedCurve, Cut};
|
||||
|
||||
use crate::command::{CadCommand, CmdResult};
|
||||
@@ -38,7 +38,7 @@ pub fn tool() -> ToolDef {
|
||||
|
||||
/// Break `entity` between world-space points `p1` and `p2`.
|
||||
/// Returns the replacement entities (empty vec means "erase, no replacement").
|
||||
pub fn break_entity(entity: &EntityType, p1: Vec3, p2: Vec3) -> Option<Vec<EntityType>> {
|
||||
pub fn break_entity(entity: &EntityType, p1: DVec3, p2: DVec3) -> Option<Vec<EntityType>> {
|
||||
match entity {
|
||||
EntityType::Line(line) => Some(break_line(line, p1, p2)),
|
||||
EntityType::Arc(arc) => Some(break_arc(arc, p1, p2)),
|
||||
@@ -50,13 +50,9 @@ pub fn break_entity(entity: &EntityType, p1: Vec3, p2: Vec3) -> Option<Vec<Entit
|
||||
}
|
||||
}
|
||||
|
||||
fn break_line(line: &LineEnt, p1: Vec3, p2: Vec3) -> Vec<EntityType> {
|
||||
let s = Vec3::new(
|
||||
line.start.x as f32,
|
||||
line.start.y as f32,
|
||||
line.start.z as f32,
|
||||
);
|
||||
let e = Vec3::new(line.end.x as f32, line.end.y as f32, line.end.z as f32);
|
||||
fn break_line(line: &LineEnt, p1: DVec3, p2: DVec3) -> Vec<EntityType> {
|
||||
let s = DVec3::new(line.start.x, line.start.y, line.start.z);
|
||||
let e = DVec3::new(line.end.x, line.end.y, line.end.z);
|
||||
let dir = e - s;
|
||||
let len2 = dir.length_squared();
|
||||
if len2 < 1e-12 {
|
||||
@@ -94,17 +90,17 @@ fn break_line(line: &LineEnt, p1: Vec3, p2: Vec3) -> Vec<EntityType> {
|
||||
result
|
||||
}
|
||||
|
||||
fn break_arc(arc: &ArcEnt, p1: Vec3, p2: Vec3) -> Vec<EntityType> {
|
||||
let cx = arc.center.x as f32;
|
||||
let cy = arc.center.y as f32;
|
||||
let r = arc.radius as f32;
|
||||
fn break_arc(arc: &ArcEnt, p1: DVec3, p2: DVec3) -> Vec<EntityType> {
|
||||
let cx = arc.center.x;
|
||||
let cy = arc.center.y;
|
||||
let r = arc.radius;
|
||||
|
||||
// Project p1 and p2 onto the arc (world XY plane)
|
||||
let a1 = angle_on_arc(cx, cy, p1);
|
||||
let a2 = angle_on_arc(cx, cy, p2);
|
||||
|
||||
let start = arc.start_angle as f32;
|
||||
let end = arc.end_angle as f32;
|
||||
let start = arc.start_angle;
|
||||
let end = arc.end_angle;
|
||||
|
||||
// Normalize: clamp a1 to arc range, then remove CCW from a1 to a2
|
||||
let a1_on = clamp_to_arc(a1, start, end);
|
||||
@@ -120,14 +116,14 @@ fn break_arc(arc: &ArcEnt, p1: Vec3, p2: Vec3) -> Vec<EntityType> {
|
||||
let _ = r; // radius unchanged
|
||||
let mut result = arc.clone();
|
||||
result.common.handle = Handle::NULL;
|
||||
result.start_angle = a2_on as f64;
|
||||
result.end_angle = a1_on as f64;
|
||||
result.start_angle = a2_on;
|
||||
result.end_angle = a1_on;
|
||||
vec![EntityType::Arc(result)]
|
||||
}
|
||||
|
||||
fn break_circle(circle: &acadrust::entities::Circle, p1: Vec3, p2: Vec3) -> Vec<EntityType> {
|
||||
let cx = circle.center.x as f32;
|
||||
let cy = circle.center.y as f32;
|
||||
fn break_circle(circle: &acadrust::entities::Circle, p1: DVec3, p2: DVec3) -> Vec<EntityType> {
|
||||
let cx = circle.center.x;
|
||||
let cy = circle.center.y;
|
||||
|
||||
let a1 = angle_on_arc(cx, cy, p1);
|
||||
let a2 = angle_on_arc(cx, cy, p2);
|
||||
@@ -143,12 +139,12 @@ fn break_circle(circle: &acadrust::entities::Circle, p1: Vec3, p2: Vec3) -> Vec<
|
||||
arc.center = circle.center.clone();
|
||||
arc.radius = circle.radius;
|
||||
arc.normal = circle.normal.clone();
|
||||
arc.start_angle = a2 as f64;
|
||||
arc.end_angle = a1 as f64;
|
||||
arc.start_angle = a2;
|
||||
arc.end_angle = a1;
|
||||
vec![EntityType::Arc(arc)]
|
||||
}
|
||||
|
||||
fn break_lwpolyline(p: &LwPolyline, p1: Vec3, p2: Vec3) -> Vec<EntityType> {
|
||||
fn break_lwpolyline(p: &LwPolyline, p1: DVec3, p2: DVec3) -> Vec<EntityType> {
|
||||
// For LwPolyline, find the nearest vertex indices for p1 and p2,
|
||||
// then split into two polylines at those vertices.
|
||||
let n = p.vertices.len();
|
||||
@@ -191,7 +187,7 @@ fn break_lwpolyline(p: &LwPolyline, p1: Vec3, p2: Vec3) -> Vec<EntityType> {
|
||||
}
|
||||
}
|
||||
|
||||
fn break_ellipse(ell: &EllipseEnt, p1: Vec3, p2: Vec3) -> Vec<EntityType> {
|
||||
fn break_ellipse(ell: &EllipseEnt, p1: DVec3, p2: DVec3) -> Vec<EntityType> {
|
||||
// Compute the eccentric-anomaly parameter of a world point relative to ellipse.
|
||||
// World = DXF (XY drawing plane).
|
||||
let cx = ell.center.x;
|
||||
@@ -205,9 +201,9 @@ fn break_ellipse(ell: &EllipseEnt, p1: Vec3, p2: Vec3) -> Vec<EntityType> {
|
||||
let ny = ell.major_axis.y / a;
|
||||
|
||||
// Project a point onto the ellipse parameter (eccentric anomaly)
|
||||
let param_of = |pt: Vec3| -> f64 {
|
||||
let rx = pt.x as f64 - cx;
|
||||
let ry = pt.y as f64 - cy;
|
||||
let param_of = |pt: DVec3| -> f64 {
|
||||
let rx = pt.x - cx;
|
||||
let ry = pt.y - cy;
|
||||
let xl = rx * nx + ry * ny;
|
||||
let yl = -rx * ny + ry * nx;
|
||||
yl.atan2(xl) // atan2(yl/b*b, xl/a*a) simplifies to atan2(yl,xl) for ordering
|
||||
@@ -257,19 +253,19 @@ fn break_ellipse(ell: &EllipseEnt, p1: Vec3, p2: Vec3) -> Vec<EntityType> {
|
||||
// ── Small utilities ────────────────────────────────────────────────────────
|
||||
|
||||
/// Returns the angle (radians, 0-2π) of `pt` viewed from (cx, cy) in the world XY plane.
|
||||
fn angle_on_arc(cx: f32, cy: f32, pt: Vec3) -> f32 {
|
||||
fn angle_on_arc(cx: f64, cy: f64, pt: DVec3) -> f64 {
|
||||
let dx = pt.x - cx;
|
||||
let dy = pt.y - cy;
|
||||
dy.atan2(dx).rem_euclid(std::f32::consts::TAU)
|
||||
dy.atan2(dx).rem_euclid(std::f64::consts::TAU)
|
||||
}
|
||||
|
||||
/// Clamp angle `a` to within the arc's angular range (CCW from `start` to `end`).
|
||||
fn clamp_to_arc(a: f32, start: f32, end: f32) -> f32 {
|
||||
let span = (end - start).rem_euclid(std::f32::consts::TAU);
|
||||
let rel = (a - start).rem_euclid(std::f32::consts::TAU);
|
||||
fn clamp_to_arc(a: f64, start: f64, end: f64) -> f64 {
|
||||
let span = (end - start).rem_euclid(std::f64::consts::TAU);
|
||||
let rel = (a - start).rem_euclid(std::f64::consts::TAU);
|
||||
if rel <= span {
|
||||
a
|
||||
} else if rel < span + (std::f32::consts::TAU - span) / 2.0 {
|
||||
} else if rel < span + (std::f64::consts::TAU - span) / 2.0 {
|
||||
end
|
||||
} else {
|
||||
start
|
||||
@@ -277,35 +273,35 @@ fn clamp_to_arc(a: f32, start: f32, end: f32) -> f32 {
|
||||
}
|
||||
|
||||
/// Find the index of the polyline vertex closest to `pt`.
|
||||
fn nearest_pline_param(p: &LwPolyline, pt: Vec3) -> usize {
|
||||
fn nearest_pline_param(p: &LwPolyline, pt: DVec3) -> usize {
|
||||
p.vertices
|
||||
.iter()
|
||||
.enumerate()
|
||||
.min_by_key(|(_, v)| {
|
||||
let dx = v.location.x as f32 - pt.x;
|
||||
let dy = v.location.y as f32 - pt.y;
|
||||
let dx = v.location.x - pt.x;
|
||||
let dy = v.location.y - pt.y;
|
||||
((dx * dx + dy * dy) * 1e6) as i64
|
||||
})
|
||||
.map(|(i, _)| i)
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
fn world_to_dxf(v: Vec3) -> Vec3 {
|
||||
fn world_to_dxf(v: DVec3) -> DVec3 {
|
||||
// World = DXF (identity).
|
||||
Vec3::new(v.x, v.y, v.z)
|
||||
DVec3::new(v.x, v.y, v.z)
|
||||
}
|
||||
|
||||
fn vec3_to_v3(v: Vec3) -> Vector3 {
|
||||
Vector3::new(v.x as f64, v.y as f64, v.z as f64)
|
||||
fn vec3_to_v3(v: DVec3) -> Vector3 {
|
||||
Vector3::new(v.x, v.y, v.z)
|
||||
}
|
||||
|
||||
fn break_spline(spl: &SplineEnt, p1: Vec3, p2: Vec3) -> Vec<EntityType> {
|
||||
fn break_spline(spl: &SplineEnt, p1: DVec3, p2: DVec3) -> Vec<EntityType> {
|
||||
// Find the two nearest parameters to p1 and p2 (DXF XY: world x, z).
|
||||
let t1 = match spline_nearest_t(spl, p1.x as f64, p1.y as f64) {
|
||||
let t1 = match spline_nearest_t(spl, p1.x, p1.y) {
|
||||
Some(t) => t,
|
||||
None => return vec![EntityType::Spline(spl.clone())],
|
||||
};
|
||||
let t2 = match spline_nearest_t(spl, p2.x as f64, p2.y as f64) {
|
||||
let t2 = match spline_nearest_t(spl, p2.x, p2.y) {
|
||||
Some(t) => t,
|
||||
None => return vec![EntityType::Spline(spl.clone())],
|
||||
};
|
||||
@@ -351,7 +347,7 @@ fn break_spline(spl: &SplineEnt, p1: Vec3, p2: Vec3) -> Vec<EntityType> {
|
||||
/// BreakEntity CmdResult variant added below.
|
||||
pub struct BreakInteractiveCommand {
|
||||
target: Option<Handle>,
|
||||
p1: Option<Vec3>,
|
||||
p1: Option<DVec3>,
|
||||
}
|
||||
|
||||
impl BreakInteractiveCommand {
|
||||
@@ -382,7 +378,7 @@ impl CadCommand for BreakInteractiveCommand {
|
||||
self.target.is_none()
|
||||
}
|
||||
|
||||
fn on_entity_pick(&mut self, handle: Handle, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_entity_pick(&mut self, handle: Handle, pt: DVec3) -> CmdResult {
|
||||
if handle.is_null() {
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
@@ -391,7 +387,7 @@ impl CadCommand for BreakInteractiveCommand {
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
let handle = match self.target {
|
||||
Some(h) => h,
|
||||
None => return CmdResult::Cancel,
|
||||
@@ -403,7 +399,7 @@ impl CadCommand for BreakInteractiveCommand {
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
};
|
||||
CmdResult::BreakEntity { handle, p1: p1.as_dvec3(), p2: pt.as_dvec3() }
|
||||
CmdResult::BreakEntity { handle, p1, p2: pt }
|
||||
}
|
||||
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
@@ -452,12 +448,12 @@ impl CadCommand for BreakAtPointCommand {
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
match self.target {
|
||||
Some(handle) => CmdResult::BreakEntity {
|
||||
handle,
|
||||
p1: pt.as_dvec3(),
|
||||
p2: pt.as_dvec3(),
|
||||
p1: pt,
|
||||
p2: pt,
|
||||
},
|
||||
None => CmdResult::Cancel,
|
||||
}
|
||||
|
||||
@@ -159,11 +159,16 @@ pub fn lengthen_entity(entity: &EntityType, pick_pt: Vec3, mode: &LenMode) -> Op
|
||||
}
|
||||
|
||||
fn lengthen_line(line: &LineEnt, pick_pt: Vec3, mode: &LenMode) -> Option<EntityType> {
|
||||
let s = Vec3::new(line.start.x as f32, line.start.y as f32, 0.0);
|
||||
let e = Vec3::new(line.end.x as f32, line.end.y as f32, 0.0);
|
||||
let p = Vec3::new(pick_pt.x, pick_pt.y, 0.0);
|
||||
// Keep the line's own coordinates on the f64 grid; only pick_pt (screen-only,
|
||||
// nearest-end selection) widens for the distance compare.
|
||||
let sx = line.start.x;
|
||||
let sy = line.start.y;
|
||||
let ex = line.end.x;
|
||||
let ey = line.end.y;
|
||||
|
||||
let current_len = (e - s).length() as f64;
|
||||
let dx = ex - sx;
|
||||
let dy = ey - sy;
|
||||
let current_len = (dx * dx + dy * dy).sqrt();
|
||||
if current_len < 1e-10 {
|
||||
return None;
|
||||
}
|
||||
@@ -173,23 +178,28 @@ fn lengthen_line(line: &LineEnt, pick_pt: Vec3, mode: &LenMode) -> Option<Entity
|
||||
return None;
|
||||
}
|
||||
|
||||
let dir = (e - s) / current_len as f32;
|
||||
let ux = dx / current_len;
|
||||
let uy = dy / current_len;
|
||||
|
||||
// Which end is closer to pick?
|
||||
let dist_to_start = (p - s).length();
|
||||
let dist_to_end = (p - e).length();
|
||||
let px = pick_pt.x as f64;
|
||||
let py = pick_pt.y as f64;
|
||||
let dist_to_start = (px - sx).hypot(py - sy);
|
||||
let dist_to_end = (px - ex).hypot(py - ey);
|
||||
|
||||
let mut result = line.clone();
|
||||
result.common.handle = Handle::NULL;
|
||||
|
||||
if dist_to_end <= dist_to_start {
|
||||
// Extend/trim the end
|
||||
let new_end = s + dir * new_len as f32;
|
||||
result.end = xz_to_v3(new_end, line.end.z);
|
||||
let new_x = sx + ux * new_len;
|
||||
let new_y = sy + uy * new_len;
|
||||
result.end = Vector3::new(new_x, new_y, line.end.z);
|
||||
} else {
|
||||
// Extend/trim the start (move start backward along dir)
|
||||
let new_start = e - dir * new_len as f32;
|
||||
result.start = xz_to_v3(new_start, line.start.z);
|
||||
let new_x = ex - ux * new_len;
|
||||
let new_y = ey - uy * new_len;
|
||||
result.start = Vector3::new(new_x, new_y, line.start.z);
|
||||
}
|
||||
Some(EntityType::Line(result))
|
||||
}
|
||||
@@ -335,11 +345,6 @@ fn arc_span_rad(start: f64, end: f64) -> f64 {
|
||||
}
|
||||
}
|
||||
|
||||
fn xz_to_v3(v: Vec3, z: f64) -> Vector3 {
|
||||
// v is (world_x, world_z, 0) → DXF (x, world_z, z)
|
||||
Vector3::new(v.x as f64, v.y as f64, z)
|
||||
}
|
||||
|
||||
fn lengthen_lwpoly(poly: &LwPolyline, pick_pt: Vec3, mode: &LenMode) -> Option<EntityType> {
|
||||
let n = poly.vertices.len();
|
||||
if n < 2 {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
// All others : move the whole entity if any point is inside.
|
||||
|
||||
use acadrust::Handle;
|
||||
use glam::{DVec3, Vec3};
|
||||
use glam::DVec3;
|
||||
|
||||
use crate::command::{CadCommand, CmdResult};
|
||||
use crate::modules::{IconKind, ModuleEvent, ToolDef};
|
||||
@@ -39,14 +39,14 @@ enum Step {
|
||||
/// Waiting for the first crossing-window corner.
|
||||
WindowCorner1,
|
||||
/// Waiting for the second corner; `c1` is the first corner.
|
||||
WindowCorner2(Vec3),
|
||||
WindowCorner2(DVec3),
|
||||
/// Crossing window defined; waiting for base point.
|
||||
Base { win_min: Vec3, win_max: Vec3 },
|
||||
Base { win_min: DVec3, win_max: DVec3 },
|
||||
/// Waiting for target point.
|
||||
Target {
|
||||
win_min: Vec3,
|
||||
win_max: Vec3,
|
||||
base: Vec3,
|
||||
win_min: DVec3,
|
||||
win_max: DVec3,
|
||||
base: DVec3,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ impl CadCommand for StretchCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
match &self.step {
|
||||
Step::WindowCorner1 => {
|
||||
self.step = Step::WindowCorner2(pt);
|
||||
@@ -115,9 +115,9 @@ impl CadCommand for StretchCommand {
|
||||
let delta = pt - *base;
|
||||
CmdResult::StretchEntities {
|
||||
handles: self.handles.clone(),
|
||||
win_min: win_min.as_dvec3(),
|
||||
win_max: win_max.as_dvec3(),
|
||||
delta: delta.as_dvec3(),
|
||||
win_min: *win_min,
|
||||
win_max: *win_max,
|
||||
delta,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,12 +140,12 @@ impl CadCommand for StretchCommand {
|
||||
// Expose the first corner so the host draws a filled crossing marquee to
|
||||
// the cursor, matching a normal box selection instead of a bare outline.
|
||||
match &self.step {
|
||||
Step::WindowCorner2(c1) => Some(c1.as_dvec3()),
|
||||
Step::WindowCorner2(c1) => Some(*c1),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn on_preview_wires(&mut self, pt: DVec3) -> Vec<WireModel> { let pt = pt.as_vec3();
|
||||
fn on_preview_wires(&mut self, pt: DVec3) -> Vec<WireModel> {
|
||||
match &self.step {
|
||||
// The crossing-window rectangle is drawn as a filled selection
|
||||
// marquee by the host (via window_first_corner) so it matches a
|
||||
@@ -158,15 +158,19 @@ impl CadCommand for StretchCommand {
|
||||
} => {
|
||||
let delta = pt - *base;
|
||||
// Live ghost: vertices inside the crossing window follow the
|
||||
// cursor, the rest stay anchored.
|
||||
// cursor, the rest stay anchored. This is the preview/GPU path,
|
||||
// so downcast to f32 only at the WireModel boundary.
|
||||
let mut out: Vec<WireModel> = self
|
||||
.wire_models
|
||||
.iter()
|
||||
.map(|w| w.stretched(*win_min, *win_max, delta))
|
||||
.map(|w| w.stretched((*win_min).as_vec3(), (*win_max).as_vec3(), delta.as_vec3()))
|
||||
.collect();
|
||||
out.push(WireModel::solid(
|
||||
"rubber_band".into(),
|
||||
vec![[base.x, base.y, base.z], [pt.x, pt.y, pt.z]],
|
||||
vec![
|
||||
[base.x as f32, base.y as f32, base.z as f32],
|
||||
[pt.x as f32, pt.y as f32, pt.z as f32],
|
||||
],
|
||||
WireModel::CYAN,
|
||||
false,
|
||||
));
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
// Step 2: If first point picked, pick second point to define angle vector.
|
||||
|
||||
use acadrust::Handle;
|
||||
use glam::{DVec3, Vec3};
|
||||
use glam::DVec3;
|
||||
|
||||
use crate::command::{CadCommand, CmdResult, DynField};
|
||||
use crate::scene::model::wire_model::WireModel;
|
||||
@@ -16,7 +16,7 @@ use acadrust::EntityType;
|
||||
|
||||
enum Step {
|
||||
AngleOrFirstPoint,
|
||||
SecondPoint { first_point: Vec3 },
|
||||
SecondPoint { first_point: DVec3 },
|
||||
}
|
||||
|
||||
pub struct TorientCommand {
|
||||
@@ -122,7 +122,7 @@ impl CadCommand for TorientCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
match self.step {
|
||||
Step::AngleOrFirstPoint => {
|
||||
self.step = Step::SecondPoint { first_point: pt };
|
||||
@@ -131,7 +131,7 @@ impl CadCommand for TorientCommand {
|
||||
Step::SecondPoint { first_point } => {
|
||||
let dx = pt.x - first_point.x;
|
||||
let dy = pt.y - first_point.y;
|
||||
let angle = dy.atan2(dx) as f64;
|
||||
let angle = dy.atan2(dx);
|
||||
self.commit_angle(Some(angle))
|
||||
}
|
||||
}
|
||||
@@ -157,8 +157,10 @@ impl CadCommand for TorientCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_preview_wires(&mut self, pt: DVec3) -> Vec<WireModel> { let pt = pt.as_vec3();
|
||||
fn on_preview_wires(&mut self, pt: DVec3) -> Vec<WireModel> {
|
||||
let pt = pt.as_vec3();
|
||||
if let Step::SecondPoint { first_point } = self.step {
|
||||
let first_point = first_point.as_vec3();
|
||||
vec![WireModel::solid(
|
||||
"rubber_band".into(),
|
||||
vec![[first_point.x, first_point.y, first_point.z], [pt.x, pt.y, pt.z]],
|
||||
|
||||
@@ -135,14 +135,11 @@ impl CadCommand for InsertBlockCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
match &self.step {
|
||||
Step::Name => CmdResult::NeedPoint,
|
||||
Step::Point { name } => {
|
||||
let mut ins = Insert::new(
|
||||
name.clone(),
|
||||
Vector3::new(pt.x as f64, pt.y as f64, pt.z as f64),
|
||||
);
|
||||
let mut ins = Insert::new(name.clone(), Vector3::new(pt.x, pt.y, pt.z));
|
||||
ins.set_x_scale(self.x_scale);
|
||||
ins.set_y_scale(self.y_scale);
|
||||
ins.rotation = self.rotation_rad;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
// live in the Model tab (`modules::model::primitive_cmd`).
|
||||
|
||||
use acadrust::{entities::Solid3D, EntityType};
|
||||
use glam::{DVec3, Vec3};
|
||||
use glam::DVec3;
|
||||
|
||||
use crate::command::{CadCommand, CmdResult};
|
||||
|
||||
@@ -95,8 +95,8 @@ impl CadCommand for ExtrudeCommand {
|
||||
pub struct RevolveCommand {
|
||||
step: RevolveStep,
|
||||
target_handle: acadrust::Handle,
|
||||
axis_start: Vec3,
|
||||
axis_end: Vec3,
|
||||
axis_start: DVec3,
|
||||
axis_end: DVec3,
|
||||
color: [f32; 4],
|
||||
}
|
||||
|
||||
@@ -113,8 +113,8 @@ impl RevolveCommand {
|
||||
Self {
|
||||
step: RevolveStep::Pick,
|
||||
target_handle: acadrust::Handle::NULL,
|
||||
axis_start: Vec3::ZERO,
|
||||
axis_end: Vec3::new(0.0, 0.0, 1.0),
|
||||
axis_start: DVec3::ZERO,
|
||||
axis_end: DVec3::new(0.0, 0.0, 1.0),
|
||||
color,
|
||||
}
|
||||
}
|
||||
@@ -143,7 +143,7 @@ impl CadCommand for RevolveCommand {
|
||||
self.step = RevolveStep::AxisStart;
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
match self.step {
|
||||
RevolveStep::AxisStart => {
|
||||
self.axis_start = pt;
|
||||
@@ -186,8 +186,8 @@ impl RevolveCommand {
|
||||
fn make_revolve(&self, angle_deg: f32) -> CmdResult {
|
||||
CmdResult::RevolveEntity {
|
||||
handle: self.target_handle,
|
||||
axis_start: self.axis_start.as_dvec3(),
|
||||
axis_end: self.axis_end.as_dvec3(),
|
||||
axis_start: self.axis_start,
|
||||
axis_end: self.axis_end,
|
||||
angle_deg,
|
||||
color: self.color,
|
||||
}
|
||||
|
||||
+15
-14
@@ -11,7 +11,7 @@ use acadrust::EntityType;
|
||||
use crate::command::{CadCommand, CmdResult};
|
||||
use crate::modules::{IconKind, ModuleEvent, ToolDef};
|
||||
use crate::scene::model::wire_model::WireModel;
|
||||
use glam::{DVec3, Vec3};
|
||||
use glam::DVec3;
|
||||
|
||||
// ── Ribbon definition ─────────────────────────────────────────────────────
|
||||
|
||||
@@ -27,7 +27,7 @@ pub fn tool() -> ToolDef {
|
||||
// ── Command ───────────────────────────────────────────────────────────────
|
||||
|
||||
pub struct MviewCommand {
|
||||
corner1: Option<Vec3>,
|
||||
corner1: Option<DVec3>,
|
||||
}
|
||||
|
||||
impl MviewCommand {
|
||||
@@ -49,16 +49,16 @@ impl CadCommand for MviewCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
if let Some(c1) = self.corner1 {
|
||||
let w = (pt.x - c1.x).abs() as f64;
|
||||
let h = (pt.y - c1.y).abs() as f64;
|
||||
let w = (pt.x - c1.x).abs();
|
||||
let h = (pt.y - c1.y).abs();
|
||||
if w < 1.0 || h < 1.0 {
|
||||
return CmdResult::Cancel;
|
||||
}
|
||||
let cx = ((c1.x + pt.x) / 2.0) as f64;
|
||||
let cy = ((c1.y + pt.y) / 2.0) as f64;
|
||||
let cz = c1.z as f64;
|
||||
let cx = (c1.x + pt.x) / 2.0;
|
||||
let cy = (c1.y + pt.y) / 2.0;
|
||||
let cz = c1.z;
|
||||
|
||||
let mut vp = Viewport::new();
|
||||
vp.center = Vector3::new(cx, cy, cz);
|
||||
@@ -77,17 +77,18 @@ impl CadCommand for MviewCommand {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> { let pt = pt.as_vec3();
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
let pt = pt.as_vec3();
|
||||
let c1 = self.corner1?;
|
||||
Some(WireModel {
|
||||
text_verts: Vec::new(),
|
||||
name: "mview_preview".to_string(),
|
||||
points: vec![
|
||||
[c1.x, c1.y, c1.z],
|
||||
[pt.x, c1.y, c1.z],
|
||||
[pt.x, pt.y, c1.z],
|
||||
[c1.x, pt.y, c1.z],
|
||||
[c1.x, c1.y, c1.z],
|
||||
[c1.x as f32, c1.y as f32, c1.z as f32],
|
||||
[pt.x, c1.y as f32, c1.z as f32],
|
||||
[pt.x, pt.y, c1.z as f32],
|
||||
[c1.x as f32, pt.y, c1.z as f32],
|
||||
[c1.x as f32, c1.y as f32, c1.z as f32],
|
||||
],
|
||||
points_low: Vec::new(),
|
||||
color: WireModel::CYAN,
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
use acadrust::entities::Solid3D;
|
||||
use acadrust::{primitives, EntityType};
|
||||
use glam::{DVec3, Vec3};
|
||||
use glam::DVec3;
|
||||
use truck_modeling::Solid;
|
||||
|
||||
use crate::command::{CadCommand, CmdResult};
|
||||
@@ -63,7 +63,7 @@ impl Shape {
|
||||
pub struct PrimitiveCommand {
|
||||
shape: Shape,
|
||||
/// Footprint points collected so far (local/world XY, z = 0).
|
||||
pts: Vec<Vec3>,
|
||||
pts: Vec<DVec3>,
|
||||
/// True once the footprint is set and we are collecting the height.
|
||||
height_step: bool,
|
||||
}
|
||||
@@ -90,9 +90,9 @@ impl PrimitiveCommand {
|
||||
match self.shape {
|
||||
Shape::Box | Shape::Wedge => {
|
||||
let d = self.pts[1] - self.pts[0];
|
||||
(d.x.abs().max(d.y.abs())) as f64
|
||||
d.x.abs().max(d.y.abs())
|
||||
}
|
||||
_ => (self.pts[1] - self.pts[0]).length() as f64,
|
||||
_ => (self.pts[1] - self.pts[0]).length(),
|
||||
}
|
||||
.max(1.0)
|
||||
}
|
||||
@@ -103,23 +103,19 @@ impl PrimitiveCommand {
|
||||
let (doc, solid) = match self.shape {
|
||||
Shape::Box | Shape::Wedge => {
|
||||
let (a, b) = (self.pts[0], self.pts[1]);
|
||||
let length = (b.x - a.x).abs() as f64;
|
||||
let width = (b.y - a.y).abs() as f64;
|
||||
let length = (b.x - a.x).abs();
|
||||
let width = (b.y - a.y).abs();
|
||||
if length < 1e-6 || width < 1e-6 || height < 1e-6 {
|
||||
return None;
|
||||
}
|
||||
if self.shape == Shape::Box {
|
||||
let center = [
|
||||
(a.x + b.x) as f64 / 2.0,
|
||||
(a.y + b.y) as f64 / 2.0,
|
||||
height / 2.0,
|
||||
];
|
||||
let center = [(a.x + b.x) / 2.0, (a.y + b.y) / 2.0, height / 2.0];
|
||||
(
|
||||
primitives::build_box(center, length, width, height),
|
||||
solid_model::box_solid(center, length, width, height),
|
||||
)
|
||||
} else {
|
||||
let origin = [a.x.min(b.x) as f64, a.y.min(b.y) as f64, 0.0];
|
||||
let origin = [a.x.min(b.x), a.y.min(b.y), 0.0];
|
||||
(
|
||||
primitives::build_wedge(origin, length, width, height),
|
||||
solid_model::wedge_solid(origin, length, width, height),
|
||||
@@ -128,11 +124,11 @@ impl PrimitiveCommand {
|
||||
}
|
||||
Shape::Cylinder | Shape::Cone => {
|
||||
let c = self.pts[0];
|
||||
let r = (self.pts[1] - c).length() as f64;
|
||||
let r = (self.pts[1] - c).length();
|
||||
if r < 1e-6 || height < 1e-6 {
|
||||
return None;
|
||||
}
|
||||
let center = [c.x as f64, c.y as f64, 0.0];
|
||||
let center = [c.x, c.y, 0.0];
|
||||
if self.shape == Shape::Cylinder {
|
||||
(
|
||||
primitives::build_cylinder(center, r, height),
|
||||
@@ -147,11 +143,11 @@ impl PrimitiveCommand {
|
||||
}
|
||||
Shape::Sphere => {
|
||||
let c = self.pts[0];
|
||||
let r = (self.pts[1] - c).length() as f64;
|
||||
let r = (self.pts[1] - c).length();
|
||||
if r < 1e-6 {
|
||||
return None;
|
||||
}
|
||||
let center = [c.x as f64, c.y as f64, 0.0];
|
||||
let center = [c.x, c.y, 0.0];
|
||||
(
|
||||
primitives::build_sphere(center, r),
|
||||
solid_model::sphere_solid(center, r),
|
||||
@@ -159,12 +155,12 @@ impl PrimitiveCommand {
|
||||
}
|
||||
Shape::Torus => {
|
||||
let c = self.pts[0];
|
||||
let major = (self.pts[1] - c).length() as f64;
|
||||
let minor = (self.pts[2] - self.pts[1]).length() as f64;
|
||||
let major = (self.pts[1] - c).length();
|
||||
let minor = (self.pts[2] - self.pts[1]).length();
|
||||
if major < 1e-6 || minor < 1e-6 {
|
||||
return None;
|
||||
}
|
||||
let center = [c.x as f64, c.y as f64, 0.0];
|
||||
let center = [c.x, c.y, 0.0];
|
||||
(
|
||||
primitives::build_torus(center, major, minor),
|
||||
solid_model::torus_solid(center, major, minor),
|
||||
@@ -209,11 +205,11 @@ impl CadCommand for PrimitiveCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
if self.height_step {
|
||||
// A click in the ground plane has no Z; use its distance from the
|
||||
// footprint centre as the height magnitude.
|
||||
let h = (pt - self.pts[0]).length() as f64;
|
||||
let h = (pt - self.pts[0]).length();
|
||||
return self.commit(h.max(1e-6));
|
||||
}
|
||||
self.pts.push(pt);
|
||||
@@ -253,7 +249,7 @@ impl CadCommand for PrimitiveCommand {
|
||||
Some(self.commit(h))
|
||||
}
|
||||
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> { let pt = pt.as_vec3();
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
if self.height_step || self.pts.is_empty() {
|
||||
return None;
|
||||
}
|
||||
@@ -265,7 +261,7 @@ impl CadCommand for PrimitiveCommand {
|
||||
|
||||
// ── Footprint preview ───────────────────────────────────────────────────────
|
||||
|
||||
fn footprint_wire(shape: Shape, pts: &[Vec3]) -> WireModel {
|
||||
fn footprint_wire(shape: Shape, pts: &[DVec3]) -> WireModel {
|
||||
let mut points: Vec<[f32; 3]> = Vec::new();
|
||||
if shape.radial() {
|
||||
let c = pts[0];
|
||||
@@ -280,21 +276,25 @@ fn footprint_wire(shape: Shape, pts: &[Vec3]) -> WireModel {
|
||||
} else {
|
||||
let (a, b) = (pts[0], pts[1]);
|
||||
points.extend_from_slice(&[
|
||||
[a.x, a.y, 0.0],
|
||||
[b.x, a.y, 0.0],
|
||||
[b.x, b.y, 0.0],
|
||||
[a.x, b.y, 0.0],
|
||||
[a.x, a.y, 0.0],
|
||||
[a.x as f32, a.y as f32, 0.0],
|
||||
[b.x as f32, a.y as f32, 0.0],
|
||||
[b.x as f32, b.y as f32, 0.0],
|
||||
[a.x as f32, b.y as f32, 0.0],
|
||||
[a.x as f32, a.y as f32, 0.0],
|
||||
]);
|
||||
}
|
||||
wire("primitive_preview", points)
|
||||
}
|
||||
|
||||
fn circle_points(out: &mut Vec<[f32; 3]>, c: Vec3, r: f32) {
|
||||
fn circle_points(out: &mut Vec<[f32; 3]>, c: DVec3, r: f64) {
|
||||
const SEG: usize = 48;
|
||||
for i in 0..=SEG {
|
||||
let t = i as f32 / SEG as f32 * std::f32::consts::TAU;
|
||||
out.push([c.x + r * t.cos(), c.y + r * t.sin(), 0.0]);
|
||||
let t = i as f64 / SEG as f64 * std::f64::consts::TAU;
|
||||
out.push([
|
||||
(c.x + r * t.cos()) as f32,
|
||||
(c.y + r * t.sin()) as f32,
|
||||
0.0,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
|
||||
use crate::command::{CadCommand, CmdResult};
|
||||
use crate::scene::model::wire_model::WireModel;
|
||||
use glam::{DVec3, Vec3};
|
||||
use glam::DVec3;
|
||||
|
||||
pub struct PlotWindowCommand {
|
||||
p1: Option<Vec3>,
|
||||
p1: Option<DVec3>,
|
||||
}
|
||||
|
||||
impl PlotWindowCommand {
|
||||
@@ -30,13 +30,13 @@ impl CadCommand for PlotWindowCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
match self.p1 {
|
||||
None => {
|
||||
self.p1 = Some(pt);
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
Some(p1) => CmdResult::SetPlotWindow { p1: p1.as_dvec3(), p2: pt.as_dvec3() },
|
||||
Some(p1) => CmdResult::SetPlotWindow { p1, p2: pt },
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ impl CadCommand for PlotWindowCommand {
|
||||
}
|
||||
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> { let pt = pt.as_vec3();
|
||||
let p1 = self.p1?;
|
||||
let p1 = self.p1?.as_vec3();
|
||||
// Draw the selection rectangle.
|
||||
Some(WireModel {
|
||||
text_verts: Vec::new(),
|
||||
|
||||
Reference in new issue
Block a user