Section Analysis takes a world coordinate: inspect.section {plane, at} places the cut at that coordinate on the named plane's axis (XY: Z, XZ: Y, YZ: X), so XZ's -Y normal no longer flips the sign; the params doc gives each named plane's normal; the result reports axis and at for named planes. at with offset, or with an {origin, normal} plane, is an error
This commit is contained in:
1 parent
b8bd43f797
commit
9a22d9df92
2 files changed
+52
-12
No files matched your search
@@ -8,11 +8,12 @@ use super::CommandSpec;
|
|||||||
use crate::params::{bad, bool_, expr, vec3};
|
use crate::params::{bad, bool_, expr, vec3};
|
||||||
use crate::{Result, Session};
|
use crate::{Result, Session};
|
||||||
|
|
||||||
pub static COMMANDS: &[CommandSpec] = &[CommandSpec::new("inspect.section", "Section Analysis", section)
|
pub static COMMANDS: &[CommandSpec] =
|
||||||
.at("SOLID", "INSPECT")
|
&[CommandSpec::new("inspect.section", "Section Analysis", section).at("SOLID", "INSPECT").icon("section").noundo().params(
|
||||||
.icon("section")
|
"plane: XY|XZ|YZ | {origin, normal}; offset?: expr along the plane's normal (XY: +Z, XZ: -Y, YZ: +X); \
|
||||||
.noundo()
|
at?: expr, the cut's world coordinate on the named plane's axis (XY: Z, XZ: Y, YZ: X), instead of offset; \
|
||||||
.params("plane: XY|XZ|YZ | {origin, normal}; offset?: expr along the normal; flip?: bool; or clear: true")];
|
flip?: bool; or clear: true. Returns the cut's origin and normal, and for a named plane its axis and at",
|
||||||
|
)];
|
||||||
|
|
||||||
fn section(s: &mut Session, p: &Value) -> Result<Value> {
|
fn section(s: &mut Session, p: &Value) -> Result<Value> {
|
||||||
let cmd = "inspect.section";
|
let cmd = "inspect.section";
|
||||||
@@ -21,21 +22,34 @@ fn section(s: &mut Session, p: &Value) -> Result<Value> {
|
|||||||
s.revision += 1;
|
s.revision += 1;
|
||||||
return Ok(json!({"section": null}));
|
return Ok(json!({"section": null}));
|
||||||
}
|
}
|
||||||
let (origin, normal) = match p.get("plane") {
|
// A named plane's world axis: which component of a point lies along its normal.
|
||||||
|
let (origin, normal, axis) = match p.get("plane") {
|
||||||
Some(Value::String(n)) => {
|
Some(Value::String(n)) => {
|
||||||
let pl = Plane::named(n).ok_or_else(|| bad(cmd, format!("unknown plane `{n}` (XY, XZ, YZ)")))?;
|
let pl = Plane::named(n).ok_or_else(|| bad(cmd, format!("unknown plane `{n}` (XY, XZ, YZ)")))?;
|
||||||
(pl.origin, pl.normal())
|
let normal = pl.normal();
|
||||||
|
let axis = [normal.x, normal.y, normal.z].iter().position(|c| c.abs() > 0.5);
|
||||||
|
(pl.origin, normal, axis)
|
||||||
}
|
}
|
||||||
Some(o @ Value::Object(_)) => {
|
Some(o @ Value::Object(_)) => {
|
||||||
let origin = o.get("origin").and_then(vec3).unwrap_or(Vec3::ZERO);
|
let origin = o.get("origin").and_then(vec3).unwrap_or(Vec3::ZERO);
|
||||||
let n = o.get("normal").and_then(vec3).and_then(|n| n.normalized()).ok_or_else(|| bad(cmd, "the plane needs a non-zero `normal`"))?;
|
let n = o.get("normal").and_then(vec3).and_then(|n| n.normalized()).ok_or_else(|| bad(cmd, "the plane needs a non-zero `normal`"))?;
|
||||||
(origin, n)
|
(origin, n, None)
|
||||||
}
|
}
|
||||||
_ => return Err(bad(cmd, "`plane` must be XY, XZ, YZ or {origin, normal}")),
|
_ => return Err(bad(cmd, "`plane` must be XY, XZ, YZ or {origin, normal}")),
|
||||||
};
|
};
|
||||||
let offset = match expr(p, "offset") {
|
let length = |key: &str| -> Result<Option<f64>> {
|
||||||
Some(e) => s.doc.eval(&e, solvecraft_doc::expr::Kind::Length).map_err(|e| bad(cmd, format!("offset: {e}")))?,
|
match expr(p, key) {
|
||||||
None => 0.0,
|
Some(e) => s.doc.eval(&e, solvecraft_doc::expr::Kind::Length).map(Some).map_err(|e| bad(cmd, format!("{key}: {e}"))),
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let (offset, at) = (length("offset")?, length("at")?);
|
||||||
|
let offset = match (offset, at, axis) {
|
||||||
|
(Some(_), Some(_), _) => return Err(bad(cmd, "give either `offset` (along the normal) or `at` (a world coordinate), not both")),
|
||||||
|
(_, Some(_), None) => return Err(bad(cmd, "`at` needs a named plane (XY, XZ, YZ); move an {origin, normal} plane by its origin")),
|
||||||
|
// The named planes pass through the world origin, so the offset is `at` signed by the normal.
|
||||||
|
(_, Some(at), Some(i)) => at * axis_component(normal, i).signum(),
|
||||||
|
(offset, None, _) => offset.unwrap_or(0.0),
|
||||||
};
|
};
|
||||||
if !origin.is_finite() || !offset.is_finite() {
|
if !origin.is_finite() || !offset.is_finite() {
|
||||||
return Err(bad(cmd, "the plane must be finite"));
|
return Err(bad(cmd, "the plane must be finite"));
|
||||||
@@ -44,5 +58,18 @@ fn section(s: &mut Session, p: &Value) -> Result<Value> {
|
|||||||
let origin = origin + normal * offset * if bool_(p, "flip").unwrap_or(false) { -1.0 } else { 1.0 };
|
let origin = origin + normal * offset * if bool_(p, "flip").unwrap_or(false) { -1.0 } else { 1.0 };
|
||||||
s.section = Some((origin, normal));
|
s.section = Some((origin, normal));
|
||||||
s.revision += 1;
|
s.revision += 1;
|
||||||
Ok(json!({"section": {"origin": origin, "normal": normal}}))
|
let mut section = json!({"origin": origin, "normal": normal});
|
||||||
|
if let Some(i) = axis {
|
||||||
|
section["axis"] = json!(["X", "Y", "Z"].get(i).copied().unwrap_or("?"));
|
||||||
|
section["at"] = json!(axis_component(origin, i));
|
||||||
|
}
|
||||||
|
Ok(json!({"section": section}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn axis_component(v: Vec3, i: usize) -> f64 {
|
||||||
|
match i {
|
||||||
|
0 => v.x,
|
||||||
|
1 => v.y,
|
||||||
|
_ => v.z,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -637,6 +637,19 @@ fn section_analysis() {
|
|||||||
assert!(s.execute("inspect.section", &json!({"plane": {"normal": [0, 0, 0]}})).is_err());
|
assert!(s.execute("inspect.section", &json!({"plane": {"normal": [0, 0, 0]}})).is_err());
|
||||||
run(&mut s, "inspect.section", json!({"clear": true}));
|
run(&mut s, "inspect.section", json!({"clear": true}));
|
||||||
assert!(s.section.is_none());
|
assert!(s.section.is_none());
|
||||||
|
// XZ's normal is -Y: an offset moves the cut to -Y, `at` places it at a world Y, and the
|
||||||
|
// result says where the cut landed either way.
|
||||||
|
let r = run(&mut s, "inspect.section", json!({"plane": "XZ", "offset": 10}));
|
||||||
|
assert_eq!((r["section"]["axis"].as_str(), r["section"]["at"].as_f64()), (Some("Y"), Some(-10.0)), "{r}");
|
||||||
|
for (plane, at, flip) in [("XZ", -282.5, false), ("XZ", 7.0, true), ("XY", 3.0, false), ("YZ", -4.0, true)] {
|
||||||
|
let r = run(&mut s, "inspect.section", json!({"plane": plane, "at": at, "flip": flip}));
|
||||||
|
assert_eq!(r["section"]["at"].as_f64(), Some(at), "{plane} {r}");
|
||||||
|
let (o, _) = s.section.unwrap();
|
||||||
|
assert!(((o.x + o.y + o.z) - at).abs() < 1e-12, "{plane} {o:?}");
|
||||||
|
}
|
||||||
|
assert!(s.execute("inspect.section", &json!({"plane": "XZ", "at": 1, "offset": 1})).is_err());
|
||||||
|
assert!(s.execute("inspect.section", &json!({"plane": {"normal": [0, 1, 0]}, "at": 1})).is_err());
|
||||||
|
run(&mut s, "inspect.section", json!({"clear": true}));
|
||||||
assert!(Arc::ptr_eq(&doc, &s.doc));
|
assert!(Arc::ptr_eq(&doc, &s.doc));
|
||||||
assert_eq!(s.undo.len(), undo);
|
assert_eq!(s.undo.len(), undo);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user