mirror of
https://github.com/storytold/cadcraft.git
synced 2026-11-02 07:16:53 -06:00
Scale heights with SCALE and keep them with OFFSET (#2558)
AutoCAD 2024's SCALE is a uniform 3D scale about the base point: every Z and elevation scales about the base point's Z (the current ELEVATION for a point given without one) and every thickness by the factor. cadcraft scaled X and Y only. The JSON form also takes a Z with the base point. OFFSET's copy now keeps the source's height: a line's end Zs, a circle's or arc's centre Z, a polyline's elevation. A circle's or arc's copy has no thickness and a polyline's keeps it, as AutoCAD makes them. Grip scaling, ALIGN's scale option and MOCORO stay 2D: their base point is often a grip or snapped point whose Z cadcraft doesn't keep. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
f18cdcbcac
commit
81e93f2caf
@@ -465,6 +465,91 @@ impl EntityKind {
|
||||
}
|
||||
}
|
||||
|
||||
/// Scale every height of the object by `f` about the height `base_z`: the Z of its points,
|
||||
/// its elevation. With [`EntityKind::transform`] scaling about the base point's XY this is a
|
||||
/// uniform 3D scale about the base point, as AutoCAD 2024's SCALE makes it. Sizes `transform`
|
||||
/// already scales (text and helix heights, an insert's Z scale) are left alone; splines,
|
||||
/// regions and wipeouts lie in the XY plane.
|
||||
pub fn scale_heights(&mut self, base_z: f64, f: f64) {
|
||||
let scale = |h: &mut f64| *h = base_z + (*h - base_z) * f;
|
||||
let z = |p: &mut Vec3| scale(&mut p.z);
|
||||
let text_z = |t: &mut Text| {
|
||||
z(&mut t.insert);
|
||||
if let Some(a) = &mut t.align_pt {
|
||||
z(a);
|
||||
}
|
||||
};
|
||||
let attribs_z = |attribs: &mut Vec<Attrib>| {
|
||||
for a in attribs {
|
||||
text_z(&mut a.text);
|
||||
if let Some(ml) = &mut a.multiline {
|
||||
z(&mut ml.mtext.insert);
|
||||
}
|
||||
}
|
||||
};
|
||||
match self {
|
||||
EntityKind::Line(l) => {
|
||||
z(&mut l.a);
|
||||
z(&mut l.b);
|
||||
}
|
||||
EntityKind::Point(p) => z(&mut p.p),
|
||||
EntityKind::Circle(c) => z(&mut c.center),
|
||||
EntityKind::Arc(a) => z(&mut a.center),
|
||||
EntityKind::Ellipse(e) => z(&mut e.center),
|
||||
EntityKind::LwPolyline(p) => scale(&mut p.elevation),
|
||||
EntityKind::Polyline3d(p) => p.points.iter_mut().for_each(z),
|
||||
EntityKind::Helix(h) => z(&mut h.center),
|
||||
EntityKind::Ray(r) | EntityKind::XLine(r) => z(&mut r.base),
|
||||
EntityKind::Text(t) => text_z(t),
|
||||
EntityKind::AttDef(a) => {
|
||||
text_z(&mut a.text);
|
||||
if let Some(ml) = &mut a.multiline {
|
||||
z(&mut ml.mtext.insert);
|
||||
}
|
||||
}
|
||||
EntityKind::MText(t) => z(&mut t.insert),
|
||||
EntityKind::Insert(i) => {
|
||||
z(&mut i.insert);
|
||||
attribs_z(&mut i.attribs);
|
||||
}
|
||||
EntityKind::Dimension(d) => {
|
||||
for p in [&mut d.defpt, &mut d.text_mid, &mut d.p13, &mut d.p14, &mut d.p15, &mut d.p16] {
|
||||
z(p);
|
||||
}
|
||||
if let Some(j) = &mut d.marks.jog_line {
|
||||
z(j);
|
||||
}
|
||||
if let Some(j) = &mut d.marks.jogged {
|
||||
z(&mut j.center);
|
||||
z(&mut j.jog);
|
||||
}
|
||||
d.marks.breaks.iter_mut().for_each(|b| z(&mut b.at));
|
||||
d.block = None;
|
||||
}
|
||||
EntityKind::MLine(ml) => ml.vertices.iter_mut().for_each(|v| z(&mut v.p)),
|
||||
EntityKind::Leader(l) => l.vertices.iter_mut().for_each(z),
|
||||
EntityKind::MLeader(ml) => {
|
||||
ml.leaders.iter_mut().flatten().for_each(z);
|
||||
z(&mut ml.landing);
|
||||
if let Some(t) = &mut ml.text {
|
||||
z(&mut t.insert);
|
||||
}
|
||||
if let Some(b) = &mut ml.block {
|
||||
z(&mut b.insert);
|
||||
attribs_z(&mut b.attribs);
|
||||
}
|
||||
}
|
||||
EntityKind::Hatch(h) => scale(&mut h.elevation),
|
||||
EntityKind::Solid(so) | EntityKind::Trace(so) => so.corners.iter_mut().for_each(z),
|
||||
EntityKind::Face3d(fc) => fc.corners.iter_mut().for_each(z),
|
||||
EntityKind::Viewport(v) => z(&mut v.center),
|
||||
EntityKind::Image(i) => z(&mut i.insert),
|
||||
EntityKind::Table(t) => z(&mut t.insert),
|
||||
EntityKind::Tolerance(t) => z(&mut t.insert),
|
||||
EntityKind::Spline(_) | EntityKind::Region(_) | EntityKind::Wipeout(_) | EntityKind::Unknown(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Grip points (editable defining points), in display order.
|
||||
pub fn grips(&self) -> Vec<Vec2> {
|
||||
// A broken table has a grip for each part.
|
||||
|
||||
@@ -61,7 +61,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
CommandSpec::new("scale", "Scale", run_scale)
|
||||
.menu(&["Modify", "Scale"])
|
||||
.alias(&["sc"])
|
||||
.params("{handles?, base: [x,y], factor, copy?: bool}")
|
||||
.params("{handles?, base: [x,y] or [x,y,z] (default Z: ELEVATION), factor, copy?: bool}")
|
||||
.interactive(|_| Ok(Box::new(SelectThen::new(Op::Scale)))),
|
||||
CommandSpec::new("mirror", "Mirror", run_mirror)
|
||||
.menu(&["Modify", "Mirror"])
|
||||
|
||||
@@ -212,12 +212,35 @@ pub(super) fn run_offset(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
Ok(json!({ "handle": nh.hex() }))
|
||||
}
|
||||
|
||||
/// The copy at the source's height, as AutoCAD 2024's OFFSET makes it: a line's end Zs, an arc's
|
||||
/// or circle's centre Z, a polyline's elevation (an ellipse's offset, a polyline, at its centre Z).
|
||||
fn keep_z(src: &EntityKind, k: &mut EntityKind) {
|
||||
match (src, k) {
|
||||
(EntityKind::Line(s), EntityKind::Line(l)) => {
|
||||
l.a.z = s.a.z;
|
||||
l.b.z = s.b.z;
|
||||
}
|
||||
(EntityKind::Circle(s), EntityKind::Circle(c)) => c.center.z = s.center.z,
|
||||
(EntityKind::Arc(s), EntityKind::Arc(a)) => a.center.z = s.center.z,
|
||||
(EntityKind::LwPolyline(s), EntityKind::LwPolyline(p)) => p.elevation = s.elevation,
|
||||
(EntityKind::Ellipse(s), EntityKind::LwPolyline(p)) => p.elevation = s.center.z,
|
||||
(EntityKind::Ray(s) | EntityKind::XLine(s), EntityKind::Ray(r) | EntityKind::XLine(r)) => r.base.z = s.base.z,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Add the offset copy `k` of `src` (on the current layer instead of the source's when
|
||||
/// `current_layer`), then erase the source when `erase` and its layer is not locked.
|
||||
fn add_offset(s: &mut Session, src: &Entity, k: EntityKind, current_layer: bool, erase: bool) -> Result<Handle> {
|
||||
/// `current_layer`), then erase the source when `erase` and its layer is not locked. The copy is
|
||||
/// at the source's height ([`keep_z`]); a circle's or arc's copy has no thickness, a polyline's
|
||||
/// keeps it, as AutoCAD 2024 makes them.
|
||||
fn add_offset(s: &mut Session, src: &Entity, mut k: EntityKind, current_layer: bool, erase: bool) -> Result<Handle> {
|
||||
keep_z(&src.kind, &mut k);
|
||||
let space = s.space();
|
||||
let d = s.doc_mut()?;
|
||||
let mut common = src.common.clone();
|
||||
if matches!(k, EntityKind::Circle(_) | EntityKind::Arc(_)) {
|
||||
common.thickness = 0.0;
|
||||
}
|
||||
if current_layer {
|
||||
common.layer = d.header.str("CLAYER", "0");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::break_join::join;
|
||||
use super::explode::explode;
|
||||
use super::transform::{erase, transform_entities};
|
||||
use super::transform::{at_elevation, erase, scale_entities, transform_entities};
|
||||
use super::{
|
||||
Accept, EngineError, EntityKind, Handle, Input, Interactive, Mat3, Prompt, Result, SelOutcome, SelectPhase, Session, Step, Vec2, line, number,
|
||||
};
|
||||
@@ -242,7 +242,7 @@ impl SelectThen {
|
||||
let f = require_length(Some(f))?;
|
||||
// The next SCALE offers this factor.
|
||||
s.last_used.scale_factor = f;
|
||||
transform_entities(s, &self.objs, &Mat3::scale_about(base, f), self.copy_mode)?;
|
||||
scale_entities(s, &self.objs, at_elevation(s, base), f, self.copy_mode)?;
|
||||
s.set_selection(Vec::new());
|
||||
Ok(Step::Done)
|
||||
}
|
||||
@@ -439,7 +439,7 @@ impl Interactive for SelectThen {
|
||||
};
|
||||
if f > 1e-12 && f.is_finite() {
|
||||
s.last_used.scale_factor = f;
|
||||
transform_entities(s, &self.objs, &Mat3::scale_about(self.pts[0], f), self.copy_mode)?;
|
||||
scale_entities(s, &self.objs, at_elevation(s, self.pts[0]), f, self.copy_mode)?;
|
||||
}
|
||||
s.set_selection(Vec::new());
|
||||
Ok(Step::Done)
|
||||
|
||||
@@ -1,8 +1,33 @@
|
||||
use cadcraft_geom::Vec3;
|
||||
|
||||
use super::{Entity, Handle, Mat3, Result, Session, Value, Vec2, bad, bool_or, f64_req, json, point_param, point_req, targets};
|
||||
|
||||
// ---------------- core operations ----------------
|
||||
|
||||
pub(crate) fn transform_entities(s: &mut Session, hs: &[Handle], m: &Mat3, copy: bool) -> Result<Vec<Handle>> {
|
||||
edit_entities(s, hs, copy, |e| e.kind.transform(m))
|
||||
}
|
||||
|
||||
/// Scale `hs` by `f` about `base` in 3D, as AutoCAD 2024's SCALE does: about the base point's XY,
|
||||
/// and every height of the objects about its Z, their thickness too.
|
||||
pub(crate) fn scale_entities(s: &mut Session, hs: &[Handle], base: Vec3, f: f64, copy: bool) -> Result<Vec<Handle>> {
|
||||
let m = Mat3::scale_about(base.xy(), f);
|
||||
edit_entities(s, hs, copy, |e| {
|
||||
e.kind.transform(&m);
|
||||
e.kind.scale_heights(base.z, f);
|
||||
e.common.thickness *= f;
|
||||
})
|
||||
}
|
||||
|
||||
/// `p` at the current elevation (ELEVATION), the Z AutoCAD gives a point entered without one.
|
||||
pub(crate) fn at_elevation(s: &Session, p: Vec2) -> Vec3 {
|
||||
let z = s.doc().map(|d| d.header.f64("ELEVATION", 0.0)).ok().filter(|z| z.is_finite());
|
||||
Vec3::new(p.x, p.y, z.unwrap_or(0.0))
|
||||
}
|
||||
|
||||
/// Apply `edit` to each of `hs` still in the drawing, or to a copy of each when `copy`; an object
|
||||
/// on a locked layer is left alone unless it is copied. The handles edited or made.
|
||||
fn edit_entities(s: &mut Session, hs: &[Handle], copy: bool, edit: impl Fn(&mut Entity)) -> Result<Vec<Handle>> {
|
||||
let d = s.doc_mut()?;
|
||||
let mut out = Vec::new();
|
||||
for h in hs {
|
||||
@@ -11,7 +36,7 @@ pub(crate) fn transform_entities(s: &mut Session, hs: &[Handle], m: &Mat3, copy:
|
||||
let Some(e) = d.entity(*h).map(|e| (**e).clone()) else { continue };
|
||||
let nh = d.new_handle();
|
||||
let mut ne = Entity { handle: nh, ..e };
|
||||
ne.kind.transform(m);
|
||||
edit(&mut ne);
|
||||
if let Some(st) = d.space_mut(&sp) {
|
||||
st.push(ne);
|
||||
out.push(nh);
|
||||
@@ -21,7 +46,7 @@ pub(crate) fn transform_entities(s: &mut Session, hs: &[Handle], m: &Mat3, copy:
|
||||
if locked {
|
||||
continue;
|
||||
}
|
||||
d.modify_entity(*h, |e| e.kind.transform(m))?;
|
||||
d.modify_entity(*h, &edit)?;
|
||||
out.push(*h);
|
||||
}
|
||||
}
|
||||
@@ -92,7 +117,12 @@ pub(super) fn run_scale(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
if f <= 0.0 {
|
||||
return Err(bad("scale", "factor must be positive"));
|
||||
}
|
||||
let r = transform_entities(s, &hs, &Mat3::scale_about(base, f), bool_or(p, "copy", false))?;
|
||||
// A Z given with the base point, as AutoCAD takes one; without one, the current elevation.
|
||||
let base = match p.get("base").and_then(|b| b.get(2)) {
|
||||
Some(z) => z.as_f64().map(|z| Vec3::new(base.x, base.y, z)).ok_or_else(|| bad("scale", "`base` Z must be a number"))?,
|
||||
None => at_elevation(s, base),
|
||||
};
|
||||
let r = scale_entities(s, &hs, base, f, bool_or(p, "copy", false))?;
|
||||
Ok(json!({ "handles": r.iter().map(|h| h.hex()).collect::<Vec<_>>() }))
|
||||
}
|
||||
|
||||
|
||||
@@ -113,6 +113,7 @@ mod refedit;
|
||||
mod region;
|
||||
mod rename_cmdline;
|
||||
mod rotate_reference;
|
||||
mod scale_offset_z;
|
||||
mod select_keywords;
|
||||
mod select_similar;
|
||||
mod select_underscore_keywords;
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
//! SCALE scales heights too and OFFSET keeps them, as AutoCAD 2024 does (measured with
|
||||
//! accoreconsole, running these scripts on these drawings): SCALE scales every Z and elevation
|
||||
//! about the base point's Z (the current elevation for a point given without one) and every
|
||||
//! thickness by the factor; OFFSET's copy is at the source's height, and a circle's or arc's copy
|
||||
//! has no thickness while a polyline's keeps it.
|
||||
|
||||
use cadcraft_engine::Session;
|
||||
use cadcraft_engine::doc::{Arc, Circle, Common, EntityKind, Handle, Line, LwPolyline, Point};
|
||||
use cadcraft_engine::geom::{PolyVertex, Vec2, Vec3};
|
||||
use serde_json::json;
|
||||
|
||||
fn add(s: &mut Session, kind: EntityKind, thickness: f64) -> Handle {
|
||||
let space = s.space();
|
||||
s.doc_mut().unwrap().add(&space, Common { thickness, ..Common::default() }, kind).unwrap()
|
||||
}
|
||||
|
||||
fn lisp(s: &mut Session, expr: &str) {
|
||||
s.cmdline(expr).unwrap();
|
||||
assert!(s.running.is_none(), "{expr}: {}", s.prompt_text());
|
||||
}
|
||||
|
||||
fn get(s: &Session, h: Handle) -> (EntityKind, f64) {
|
||||
let e = s.doc().unwrap().entity(h).unwrap().clone();
|
||||
(e.kind.clone(), e.common.thickness)
|
||||
}
|
||||
|
||||
fn last(s: &Session) -> (EntityKind, f64) {
|
||||
let e = s.doc().unwrap().model.iter().last().unwrap().clone();
|
||||
(e.kind.clone(), e.common.thickness)
|
||||
}
|
||||
|
||||
fn close(a: f64, b: f64) -> bool {
|
||||
(a - b).abs() <= 1e-9 * (1.0 + b.abs())
|
||||
}
|
||||
|
||||
fn near3(a: Vec3, b: Vec3) -> bool {
|
||||
close(a.x, b.x) && close(a.y, b.y) && close(a.z, b.z)
|
||||
}
|
||||
|
||||
fn scale(s: &mut Session, h: Handle, base: &str) {
|
||||
lisp(s, &format!("(command \"_.SCALE\" (handent \"{}\") \"\" '({base}) 1.7)", h.hex()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scale_scales_a_lines_z() {
|
||||
let mut s = Session::new();
|
||||
let h = add(&mut s, EntityKind::Line(Line { a: Vec3::new(0.0, 0.0, 7.0), b: Vec3::new(40.0, 0.0, 7.0) }), 0.0);
|
||||
scale(&mut s, h, "2.0 3.0");
|
||||
let EntityKind::Line(l) = get(&s, h).0 else { panic!() };
|
||||
assert!(near3(l.a, Vec3::new(-1.4, -2.1, 11.9)) && near3(l.b, Vec3::new(66.6, -2.1, 11.9)), "{l:?}");
|
||||
// A sloped line at survey coordinates.
|
||||
let mut s = Session::new();
|
||||
let h = add(&mut s, EntityKind::Line(Line { a: Vec3::new(612345.678, 2398765.432, 3.0), b: Vec3::new(612375.678, 2398775.432, 8.0) }), 0.0);
|
||||
scale(&mut s, h, "612347.678 2398768.432");
|
||||
let EntityKind::Line(l) = get(&s, h).0 else { panic!() };
|
||||
assert!(near3(l.a, Vec3::new(612344.278, 2398763.332, 5.1)) && near3(l.b, Vec3::new(612395.278, 2398780.332, 13.6)), "{l:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scale_scales_the_centre_z_and_thickness_of_circles_and_arcs() {
|
||||
let mut s = Session::new();
|
||||
let c = add(&mut s, EntityKind::Circle(Circle { center: Vec3::new(0.5, 0.25, 0.005), radius: 0.01 }), 0.002);
|
||||
scale(&mut s, c, "0.502 0.253");
|
||||
let (EntityKind::Circle(k), t) = get(&s, c) else { panic!() };
|
||||
assert!(near3(k.center, Vec3::new(0.4986, 0.2479, 0.0085)) && close(k.radius, 0.017) && close(t, 0.0034), "{k:?} {t}");
|
||||
let mut s = Session::new();
|
||||
let arc = Arc { center: Vec3::new(0.0, 0.0, 4.5), radius: 10.0, start: 30f64.to_radians(), end: 200f64.to_radians() };
|
||||
let a = add(&mut s, EntityKind::Arc(arc), 1.5);
|
||||
scale(&mut s, a, "2.0 3.0");
|
||||
let (EntityKind::Arc(k), t) = get(&s, a) else { panic!() };
|
||||
assert!(near3(k.center, Vec3::new(-1.4, -2.1, 7.65)) && close(k.radius, 17.0) && close(t, 2.55), "{k:?} {t}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scale_scales_a_polylines_elevation_and_thickness_and_a_points_z() {
|
||||
let mut s = Session::new();
|
||||
let vs = [(612345.678, 2398765.432, 0.0), (612365.678, 2398765.432, 0.4), (612375.678, 2398780.432, 0.0), (612350.678, 2398785.432, 0.0)];
|
||||
let vertices = vs.iter().map(|&(x, y, b)| PolyVertex { bulge: b, ..PolyVertex::new(Vec2::new(x, y)) }).collect();
|
||||
let p = add(&mut s, EntityKind::LwPolyline(LwPolyline { vertices, closed: false, const_width: 0.0, elevation: 12.5, plinegen: false }), 1.0);
|
||||
scale(&mut s, p, "612347.678 2398768.432");
|
||||
let (EntityKind::LwPolyline(k), t) = get(&s, p) else { panic!() };
|
||||
assert!(close(k.elevation, 21.25) && close(t, 1.7), "{} {t}", k.elevation);
|
||||
let first = k.vertices.first().unwrap().p;
|
||||
assert!(close(first.x, 612344.278) && close(first.y, 2398763.332), "{first:?}");
|
||||
let pt = add(&mut s, EntityKind::Point(Point { p: Vec3::new(5.0, 5.0, 6.0), angle: 0.0 }), 0.0);
|
||||
scale(&mut s, pt, "2.0 3.0");
|
||||
let EntityKind::Point(k) = get(&s, pt).0 else { panic!() };
|
||||
assert!(near3(k.p, Vec3::new(7.1, 6.4, 10.2)), "{k:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scale_scales_heights_about_the_current_elevation() {
|
||||
// ELEVATION 5: the base point, given without a Z, is at Z = 5.
|
||||
let mut s = Session::new();
|
||||
s.doc_mut().unwrap().header.set_f64("ELEVATION", 5.0);
|
||||
let h = add(&mut s, EntityKind::Line(Line { a: Vec3::new(0.0, 0.0, 7.0), b: Vec3::new(40.0, 0.0, 7.0) }), 0.0);
|
||||
scale(&mut s, h, "2.0 3.0");
|
||||
let EntityKind::Line(l) = get(&s, h).0 else { panic!() };
|
||||
assert!(near3(l.a, Vec3::new(-1.4, -2.1, 8.4)) && near3(l.b, Vec3::new(66.6, -2.1, 8.4)), "{l:?}");
|
||||
let c = add(&mut s, EntityKind::Circle(Circle { center: Vec3::new(0.0, 0.0, 4.5), radius: 10.0 }), 1.5);
|
||||
scale(&mut s, c, "2.0 3.0");
|
||||
let (EntityKind::Circle(k), t) = get(&s, c) else { panic!() };
|
||||
assert!(near3(k.center, Vec3::new(-1.4, -2.1, 4.15)) && close(k.radius, 17.0) && close(t, 2.55), "{k:?} {t}");
|
||||
let vs = [(0.0, 0.0, 0.0), (20.0, 0.0, 0.4), (30.0, 15.0, 0.0)];
|
||||
let vertices = vs.iter().map(|&(x, y, b)| PolyVertex { bulge: b, ..PolyVertex::new(Vec2::new(x, y)) }).collect();
|
||||
let p = add(&mut s, EntityKind::LwPolyline(LwPolyline { vertices, closed: false, const_width: 0.0, elevation: 12.5, plinegen: false }), 1.0);
|
||||
scale(&mut s, p, "2.0 3.0");
|
||||
let (EntityKind::LwPolyline(k), t) = get(&s, p) else { panic!() };
|
||||
assert!(close(k.elevation, 17.75) && close(t, 1.7), "{} {t}", k.elevation);
|
||||
let last = k.vertices.last().unwrap().p;
|
||||
assert!(close(last.x, 49.6) && close(last.y, 23.4), "{last:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_scale_takes_the_base_points_z() {
|
||||
let line = || EntityKind::Line(Line { a: Vec3::new(0.0, 0.0, 7.0), b: Vec3::new(40.0, 0.0, 7.0) });
|
||||
let z = |s: &Session, h: Handle| {
|
||||
let EntityKind::Line(l) = get(s, h).0 else { panic!() };
|
||||
(l.a.z, l.b.z)
|
||||
};
|
||||
let mut s = Session::new();
|
||||
let h = add(&mut s, line(), 0.0);
|
||||
s.execute("scale", &json!({ "handles": [h.hex()], "base": [2.0, 3.0, 1.0], "factor": 1.7 })).unwrap();
|
||||
let (a, b) = z(&s, h);
|
||||
assert!(close(a, 11.2) && close(b, 11.2), "{a} {b}");
|
||||
// Without one, the current elevation (not measured through JSON: the same base point as the
|
||||
// command line's).
|
||||
let mut s = Session::new();
|
||||
s.doc_mut().unwrap().header.set_f64("ELEVATION", 5.0);
|
||||
let h = add(&mut s, line(), 0.0);
|
||||
s.execute("scale", &json!({ "handles": [h.hex()], "base": [2.0, 3.0], "factor": 1.7 })).unwrap();
|
||||
let (a, b) = z(&s, h);
|
||||
assert!(close(a, 8.4) && close(b, 8.4), "{a} {b}");
|
||||
// A Z that isn't a number is refused, the drawing left as it was.
|
||||
assert!(s.execute("scale", &json!({ "handles": [h.hex()], "base": [2.0, 3.0, "1"], "factor": 1.7 })).is_err());
|
||||
assert_eq!(z(&s, h), (a, b));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_and_rotate_leave_heights_alone() {
|
||||
let mut s = Session::new();
|
||||
let h = add(&mut s, EntityKind::Line(Line { a: Vec3::new(0.0, 0.0, 7.0), b: Vec3::new(40.0, 0.0, 7.0) }), 2.0);
|
||||
lisp(&mut s, &format!("(command \"_.ROTATE\" (handent \"{}\") \"\" '(0.0 0.0) 90)", h.hex()));
|
||||
lisp(&mut s, &format!("(command \"_.MOVE\" (handent \"{}\") \"\" '(0.0 0.0) '(5.0 5.0))", h.hex()));
|
||||
let (EntityKind::Line(l), t) = get(&s, h) else { panic!() };
|
||||
assert!(close(l.a.z, 7.0) && close(l.b.z, 7.0) && close(t, 2.0), "{l:?} {t}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn offset_keeps_a_lines_z() {
|
||||
let mut s = Session::new();
|
||||
let h = add(&mut s, EntityKind::Line(Line { a: Vec3::new(0.0, 0.0, 7.0), b: Vec3::new(40.0, 0.0, 7.0) }), 0.0);
|
||||
lisp(&mut s, &format!("(command \"_.OFFSET\" 3.0 (list (handent \"{}\") '(20.0 0.0)) '(10.0 5.0) \"\")", h.hex()));
|
||||
let EntityKind::Line(l) = last(&s).0 else { panic!() };
|
||||
assert!(near3(l.a, Vec3::new(0.0, 3.0, 7.0)) && near3(l.b, Vec3::new(40.0, 3.0, 7.0)), "{l:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn offset_keeps_the_centre_z_of_circles_and_arcs_but_not_their_thickness() {
|
||||
let mut s = Session::new();
|
||||
let c = add(&mut s, EntityKind::Circle(Circle { center: Vec3::new(612345.678, 2398765.432, 5.0), radius: 10.0 }), 2.0);
|
||||
lisp(&mut s, &format!("(command \"_.OFFSET\" 3.0 (list (handent \"{}\") '(612355.678 2398765.432)) '(612345.678 2398780.432) \"\")", c.hex()));
|
||||
let (EntityKind::Circle(k), t) = last(&s) else { panic!() };
|
||||
assert!(close(k.center.z, 5.0) && close(k.radius, 13.0) && t == 0.0, "{k:?} {t}");
|
||||
let mut s = Session::new();
|
||||
let arc = Arc { center: Vec3::new(-35000.0, 81000.0, 225.0), radius: 500.0, start: 30f64.to_radians(), end: 200f64.to_radians() };
|
||||
let a = add(&mut s, EntityKind::Arc(arc), 75.0);
|
||||
lisp(&mut s, &format!("(command \"_.OFFSET\" 150.0 (list (handent \"{}\") '(-35000.0 81500.0)) '(-35000.0 81750.0) \"\")", a.hex()));
|
||||
let (EntityKind::Arc(k), t) = last(&s) else { panic!() };
|
||||
assert!(close(k.center.z, 225.0) && close(k.radius, 650.0) && t == 0.0, "{k:?} {t}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn offset_keeps_a_polylines_elevation_and_thickness() {
|
||||
let mut s = Session::new();
|
||||
let vs = [(0.0, 0.0, 0.0), (20.0, 0.0, 0.4), (30.0, 15.0, 0.0), (5.0, 20.0, 0.0)];
|
||||
let vertices = vs.iter().map(|&(x, y, b)| PolyVertex { bulge: b, ..PolyVertex::new(Vec2::new(x, y)) }).collect();
|
||||
let p = add(&mut s, EntityKind::LwPolyline(LwPolyline { vertices, closed: false, const_width: 0.0, elevation: 12.5, plinegen: false }), 1.0);
|
||||
lisp(&mut s, &format!("(command \"_.OFFSET\" 3.0 (list (handent \"{}\") '(10.0 0.0)) '(12.0 6.0) \"\")", p.hex()));
|
||||
let (EntityKind::LwPolyline(k), t) = last(&s) else { panic!() };
|
||||
assert!(close(k.elevation, 12.5) && close(t, 1.0), "{} {t}", k.elevation);
|
||||
let first = k.vertices.first().unwrap().p;
|
||||
assert!(close(first.x, 0.0) && close(first.y, 3.0), "{first:?}");
|
||||
}
|
||||
Reference in New Issue
Block a user