mirror of
https://github.com/storytold/cadcraft.git
synced 2026-10-31 06:16:51 -06:00
* ELLIPSE from axis endpoints matches what AutoCAD stores Typed ELLIPSE input was checked against AutoCAD 2024 black-box (synthetic input in accoreconsole, read back with entget); three rules differed: - The major axis pointed at the second axis endpoint; AutoCAD points it at the first. A full ellipse looks the same, but an elliptical arc's angles are measured from that axis, so the arc was drawn on the opposite side of the center, and the stored major vector, start point and quadrant order all differed. - A point at "Specify distance to other axis" gave its distance from the first axis; AutoCAD takes its distance from the center (the rubber band), then swaps the axes when it is the longer one. - The end-angle prompt had no Included angle option, so `_I` was rejected and the next number became an absolute end angle. AutoCAD adds the included angle (default 180) to the start angle. The JSON form (ellipse.axis / ellipse.arc with p1, p2, p3) follows the same rules. The regression test pins ten AutoCAD-measured cases, among them survey (VN-2000) coordinates, a near-millimetre ellipse and both Included angle cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: geometry parity says which part was measured against AutoCAD The "How this was measured" section still said no numeric comparison against AutoCAD had been run, next to the new ELLIPSE revision row that reports one. It now names the 11 measured ELLIPSE cases and the test that pins them, and keeps the rest of the document marked as read from the source. Pointed out in review. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ellipse: drop the import the axis-endpoint change left unused --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com>
177 lines
9.3 KiB
Rust
177 lines
9.3 KiB
Rust
//! Draw menu depth (M1/M2): every ARC and CIRCLE variant, ELLIPSE axis/arc entries, RECTANG
|
|
//! options, DIVIDE/MEASURE, REVCLOUD, WIPEOUT, XLINE options, 3DPOLY, MLINE, HELIX, spline by
|
|
//! control vertices, CENTERMARK and CENTERLINE. Each has a JSON form and a prompt sequence.
|
|
|
|
use cadcraft_doc::{EntityKind, Handle, Helix, HelixConstrain, Point, Polyline3d, RayLine, Wipeout};
|
|
use cadcraft_geom::{Arc, Circle, Ellipse, Line, PolyVertex, Polyline, Spline, TAU, Vec2, Vec3, norm_angle, shoelace};
|
|
use serde_json::{Value, json};
|
|
|
|
use super::curves::{self, Chain, MAX_GEN, TanObj};
|
|
use super::helpers::{arc, circle, line, lwpoly, rect_vertices, rect_with_corners, v3};
|
|
|
|
use super::machines::number;
|
|
use super::{CommandSpec, bad, bool_or, f64_or, f64_req, point_param, point_req, point_value, points_param, size_param, str_param, targets};
|
|
|
|
use crate::{Accept, EngineError, Input, Interactive, Prompt, Result, Session, Step};
|
|
|
|
mod arc;
|
|
mod centermark;
|
|
mod curves3d;
|
|
mod divide;
|
|
mod ellipse;
|
|
mod rect;
|
|
mod revcloud;
|
|
mod tancircle;
|
|
mod wipeout;
|
|
mod xline;
|
|
|
|
pub(crate) use arc::arc_machine;
|
|
use arc::{ArcContinueM, ArcV, run_arc_continue, run_arc_variant, seq};
|
|
use centermark::{CenterM, run_centerline, run_centermark};
|
|
use curves3d::{HelixM, Poly3dM, SplineCvM, run_3dpoly, run_helix, run_spline_cv};
|
|
use divide::{DivideM, run_divide, run_measure};
|
|
use ellipse::{run_ellipse_arc, run_ellipse_axis};
|
|
pub(crate) use rect::{RectM2, run_rectang2};
|
|
use revcloud::{CloudKind, RevcloudM, run_revcloud};
|
|
pub(crate) use tancircle::TanCircleM;
|
|
use tancircle::{run_circle_ttr, run_circle_ttt, run_circle_variant};
|
|
use wipeout::{WipeoutM, run_wipeout};
|
|
pub(crate) use xline::{XlineM2, run_xline2};
|
|
|
|
pub(crate) mod breakline;
|
|
pub(crate) mod mline;
|
|
|
|
pub fn specs() -> Vec<CommandSpec> {
|
|
let mut v = vec![
|
|
CommandSpec::new("arc.sce", "Start, Center, End", run_arc_variant)
|
|
.menu(&["Draw", "Arc", "Start, Center, End"])
|
|
.params("{start, center, end}")
|
|
.interactive(|_| Ok(seq(ArcV::Sce))),
|
|
CommandSpec::new("arc.sca", "Start, Center, Angle", run_arc_variant)
|
|
.menu(&["Draw", "Arc", "Start, Center, Angle"])
|
|
.params("{start, center, angle (degrees, + = CCW)}")
|
|
.interactive(|_| Ok(seq(ArcV::Sca))),
|
|
CommandSpec::new("arc.scl", "Start, Center, Length", run_arc_variant)
|
|
.menu(&["Draw", "Arc", "Start, Center, Length"])
|
|
.params("{start, center, length (chord; negative = major arc)}")
|
|
.interactive(|_| Ok(seq(ArcV::Scl))),
|
|
CommandSpec::new("arc.sea", "Start, End, Angle", run_arc_variant)
|
|
.menu(&["Draw", "Arc", "Start, End, Angle"])
|
|
.params("{start, end, angle (degrees, + = CCW)}")
|
|
.interactive(|_| Ok(seq(ArcV::Sea))),
|
|
CommandSpec::new("arc.sed", "Start, End, Direction", run_arc_variant)
|
|
.menu(&["Draw", "Arc", "Start, End, Direction"])
|
|
.params("{start, end, direction (degrees | [dx,dy])}")
|
|
.interactive(|_| Ok(seq(ArcV::Sed))),
|
|
CommandSpec::new("arc.ser", "Start, End, Radius", run_arc_variant)
|
|
.menu(&["Draw", "Arc", "Start, End, Radius"])
|
|
.params("{start, end, radius (negative = major arc)}")
|
|
.interactive(|_| Ok(seq(ArcV::Ser))),
|
|
CommandSpec::new("arc.cse", "Center, Start, End", run_arc_variant)
|
|
.menu(&["Draw", "Arc", "Center, Start, End"])
|
|
.params("{center, start, end}")
|
|
.interactive(|_| Ok(seq(ArcV::Cse))),
|
|
CommandSpec::new("arc.csa", "Center, Start, Angle", run_arc_variant)
|
|
.menu(&["Draw", "Arc", "Center, Start, Angle"])
|
|
.params("{center, start, angle (degrees)}")
|
|
.interactive(|_| Ok(seq(ArcV::Csa))),
|
|
CommandSpec::new("arc.csl", "Center, Start, Length", run_arc_variant)
|
|
.menu(&["Draw", "Arc", "Center, Start, Length"])
|
|
.params("{center, start, length (chord)}")
|
|
.interactive(|_| Ok(seq(ArcV::Csl))),
|
|
CommandSpec::new("arc.continue", "Continue", run_arc_continue)
|
|
.menu(&["Draw", "Arc", "Continue"])
|
|
.params("{end} (tangent from the last line/arc) | {start, direction, end}")
|
|
.interactive(|_| Ok(Box::new(ArcContinueM::default()))),
|
|
CommandSpec::new("circle.cd", "Center, Diameter", run_circle_variant)
|
|
.menu(&["Draw", "Circle", "Center, Diameter"])
|
|
.params("{center, diameter}")
|
|
.interactive(|_| Ok(seq(ArcV::CircleCd))),
|
|
CommandSpec::new("circle.2p", "2 Points", run_circle_variant)
|
|
.menu(&["Draw", "Circle", "2 Points"])
|
|
.params("{p1, p2}")
|
|
.interactive(|_| Ok(seq(ArcV::Circle2p))),
|
|
CommandSpec::new("circle.3p", "3 Points", run_circle_variant)
|
|
.menu(&["Draw", "Circle", "3 Points"])
|
|
.params("{p1, p2, p3}")
|
|
.interactive(|_| Ok(seq(ArcV::Circle3p))),
|
|
CommandSpec::new("circle.ttr", "Tan, Tan, Radius", run_circle_ttr)
|
|
.menu(&["Draw", "Circle", "Tan, Tan, Radius"])
|
|
.params("{h1, p1, h2, p2, radius} (lines, circles, arcs, polyline segments; nearest solution to the picks)")
|
|
.interactive(|_| Ok(Box::new(TanCircleM::new(true)))),
|
|
CommandSpec::new("circle.ttt", "Tan, Tan, Tan", run_circle_ttt)
|
|
.menu(&["Draw", "Circle", "Tan, Tan, Tan"])
|
|
.params("{h1, p1, h2, p2, h3, p3}")
|
|
.interactive(|_| Ok(Box::new(TanCircleM::new(false)))),
|
|
CommandSpec::new("ellipse.axis", "Axis, End", run_ellipse_axis)
|
|
.menu(&["Draw", "Ellipse", "Axis, End"])
|
|
.params("{p1, p2, distance} | {p1, p2, p3} (axis endpoints, then half the other axis: a distance, or a point that far from the center)")
|
|
.interactive(|_| Ok(super::draw::ellipse_machine(false, false))),
|
|
CommandSpec::new("ellipse.arc", "Elliptical Arc", run_ellipse_arc)
|
|
.menu(&["Draw", "Ellipse", "Arc"])
|
|
.params("{center, major: [dx,dy], ratio, start, end (degrees)} | {p1, p2, distance, start, end}")
|
|
.interactive(|_| Ok(super::draw::ellipse_machine(false, true))),
|
|
CommandSpec::new("divide", "Divide", run_divide)
|
|
.menu(&["Draw", "Point", "Divide"])
|
|
.alias(&["div"])
|
|
.params("{handle, segments, block?, align?: bool}")
|
|
.interactive(|_| Ok(Box::new(DivideM::new(false)))),
|
|
CommandSpec::new("measure", "Measure", run_measure)
|
|
.menu(&["Draw", "Point", "Measure"])
|
|
.alias(&["me"])
|
|
.params("{handle, length, block?, align?: bool, from?: [x,y] (end to start at)}")
|
|
.interactive(|_| Ok(Box::new(DivideM::new(true)))),
|
|
CommandSpec::new("revcloud", "Rectangular", run_revcloud)
|
|
.menu(&["Draw", "Revision Cloud", "Rectangular"])
|
|
.params("{p1, p2} | {points: [...]} | {freehand: [...]} | {handle}, arcLength?, style?: normal|calligraphy, reverse?")
|
|
.interactive(|s| Ok(Box::new(RevcloudM::new(s, CloudKind::Rect)))),
|
|
CommandSpec::new("revcloud.polygonal", "Polygonal", run_revcloud)
|
|
.menu(&["Draw", "Revision Cloud", "Polygonal"])
|
|
.params("{points: [[x,y],...], arcLength?}")
|
|
.interactive(|s| Ok(Box::new(RevcloudM::new(s, CloudKind::Poly)))),
|
|
CommandSpec::new("revcloud.freehand", "Freehand", run_revcloud)
|
|
.menu(&["Draw", "Revision Cloud", "Freehand"])
|
|
.params("{freehand: [[x,y],...], arcLength?}")
|
|
.interactive(|s| Ok(Box::new(RevcloudM::new(s, CloudKind::Free)))),
|
|
CommandSpec::new("wipeout", "Wipeout", run_wipeout)
|
|
.menu(&["Draw", "Wipeout"])
|
|
.params("{points: [[x,y],...]} | {handle (closed polyline), erase?: bool} | {frames: on|off|display}")
|
|
.interactive(|_| Ok(Box::new(WipeoutM::default()))),
|
|
CommandSpec::new("3dpoly", "3D Polyline", run_3dpoly)
|
|
.menu(&["Draw", "3D Polyline"])
|
|
.alias(&["3p"])
|
|
.params("{points: [[x,y,z?],...], closed?}")
|
|
.interactive(|_| Ok(Box::new(Poly3dM::default()))),
|
|
CommandSpec::new("helix", "Helix", run_helix)
|
|
.menu(&["Draw", "Helix"])
|
|
.params("{center, baseRadius, topRadius?, height?, turns?, ccw?, startAngle? (degrees)} → {handle}")
|
|
.interactive(|_| Ok(Box::new(HelixM::default()))),
|
|
CommandSpec::new("spline.cv", "Control Vertices", run_spline_cv)
|
|
.menu(&["Draw", "Spline", "Control Vertices"])
|
|
.params("{control: [[x,y],...], degree?: 1..10 (default 3), closed?}")
|
|
.interactive(|_| Ok(Box::new(SplineCvM::default()))),
|
|
CommandSpec::new("centermark", "Center Mark", run_centermark)
|
|
.menu(&["Draw", "Center Mark"])
|
|
.params("{handle | handles} (circles/arcs; lines on the CENTER linetype)")
|
|
.interactive(|_| Ok(Box::new(CenterM { first: None, mark: true }))),
|
|
CommandSpec::new("centerline", "Center Line", run_centerline)
|
|
.menu(&["Draw", "Center Line"])
|
|
.params("{h1, h2} (two lines)")
|
|
.interactive(|_| Ok(Box::new(CenterM { first: None, mark: false }))),
|
|
];
|
|
v.extend(mline::specs());
|
|
v.extend(breakline::specs());
|
|
v
|
|
}
|
|
|
|
fn added(h: Handle) -> Result<Value> {
|
|
Ok(json!({ "handle": h.hex() }))
|
|
}
|
|
|
|
fn other(m: impl Into<String>) -> EngineError {
|
|
EngineError::Other(m.into())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests;
|