Merge remote-tracking branch 'origin/main' into feat/sketch-tools-return-ids
# Conflicts: # crates/engine/src/cmd/mod.rs # crates/engine/src/cmd/sketch.rs # crates/engine/src/cmd/sketch_create.rs # crates/engine/src/cmd/sketch_modify.rs # crates/engine/src/lib.rs # docs/book/src/guide/sketching.md
This commit is contained in:
commit
b93d4905ac
181 files changed
+29350
-1572
No files matched your search
@@ -4,8 +4,9 @@
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_geom::Vec3;
|
||||
use solvecraft_sketch::WireSpace;
|
||||
|
||||
use super::sketch::edit;
|
||||
use super::sketch::{edit, target_sketch};
|
||||
use super::{CommandSpec, in_sketch};
|
||||
use crate::params::{bad, req_vec3, vec3};
|
||||
use crate::{Result, Session};
|
||||
@@ -38,32 +39,71 @@ fn needs_3d(sk: &solvecraft_sketch::Sketch, cmd: &str) -> Result<()> {
|
||||
if sk.view.three_d { Ok(()) } else { Err(bad(cmd, "turn on 3D Sketch (sketch.options sketch_3d: true) to draw off the sketch plane")) }
|
||||
}
|
||||
|
||||
/// World command inputs belong to the target sketch's owner, which may differ from the
|
||||
/// active component when a script supplies an explicit sketch.
|
||||
fn inverse_owner_frame(s: &Session, p: &Value, cmd: &str) -> Result<solvecraft_doc::Mat> {
|
||||
let id = target_sketch(s, p, cmd)?;
|
||||
let owner = s.doc.feature(id).map(|f| f.component).ok_or_else(|| bad(cmd, "target sketch is missing"))?;
|
||||
if owner != 0 && owner != s.active_component {
|
||||
if s.doc.occurrences.len() > 100_000 {
|
||||
return Err(bad(cmd, "sketch owner placement expansion exceeds supported bounds"));
|
||||
}
|
||||
let mut children: std::collections::BTreeMap<u64, Vec<&solvecraft_doc::Occurrence>> = std::collections::BTreeMap::new();
|
||||
for occurrence in &s.doc.occurrences {
|
||||
children.entry(occurrence.parent).or_default().push(occurrence);
|
||||
}
|
||||
let mut stack = vec![(0, 0usize)];
|
||||
let mut count = 0;
|
||||
let mut visited = 0;
|
||||
while let Some((component, depth)) = stack.pop() {
|
||||
for o in children.get(&component).into_iter().flatten() {
|
||||
visited += 1;
|
||||
if depth >= 64 || visited > 100_000 {
|
||||
return Err(bad(cmd, "sketch owner placement expansion exceeds supported bounds"));
|
||||
}
|
||||
if o.component == owner {
|
||||
count += 1;
|
||||
if count > 1 {
|
||||
return Err(bad(cmd, "activate the desired sketch owner occurrence before editing world 3D points"));
|
||||
}
|
||||
}
|
||||
stack.push((o.component, depth + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
let frame = if owner == s.active_component { s.active_component_frame() } else { s.placed_document().component_transform(owner) };
|
||||
solvecraft_doc::mat_inverse(&frame).ok_or_else(|| bad(cmd, "the sketch owner placement is not invertible"))
|
||||
}
|
||||
|
||||
fn line3d(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let cmd = "sketch.line3d";
|
||||
let pts = points(cmd, p, 2)?;
|
||||
let inverse = inverse_owner_frame(s, p, cmd)?;
|
||||
let pts: Vec<Vec3> = points(cmd, p, 2)?.into_iter().map(|at| solvecraft_doc::apply_point(&inverse, at)).collect();
|
||||
let (ids, info) = edit(s, p, cmd, false, |sk, _| {
|
||||
needs_3d(sk, cmd)?;
|
||||
pts.windows(2).map(|w| sk.add_drawn_wire(w).map_err(Into::into)).collect::<Result<Vec<String>>>()
|
||||
pts.windows(2).map(|w| sk.add_drawn_wire_in(w, WireSpace::Component).map_err(Into::into)).collect::<Result<Vec<String>>>()
|
||||
})?;
|
||||
Ok(json!({"curves": ids, "sketch": info}))
|
||||
}
|
||||
|
||||
fn spline3d(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let cmd = "sketch.spline3d";
|
||||
let pts = points(cmd, p, 2)?;
|
||||
let inverse = inverse_owner_frame(s, p, cmd)?;
|
||||
let pts: Vec<Vec3> = points(cmd, p, 2)?.into_iter().map(|at| solvecraft_doc::apply_point(&inverse, at)).collect();
|
||||
let (id, info) = edit(s, p, cmd, false, |sk, _| {
|
||||
needs_3d(sk, cmd)?;
|
||||
Ok(sk.add_drawn_wire(&pts)?)
|
||||
Ok(sk.add_drawn_wire_in(&pts, WireSpace::Component)?)
|
||||
})?;
|
||||
Ok(json!({"curves": [id], "sketch": info}))
|
||||
}
|
||||
|
||||
fn point3d(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let cmd = "sketch.point3d";
|
||||
let at = req_vec3(cmd, p, "point")?;
|
||||
let inverse = inverse_owner_frame(s, p, cmd)?;
|
||||
let at = solvecraft_doc::apply_point(&inverse, req_vec3(cmd, p, "point")?);
|
||||
let (id, info) = edit(s, p, cmd, false, |sk, _| {
|
||||
needs_3d(sk, cmd)?;
|
||||
Ok(sk.add_drawn_wire(&[at])?)
|
||||
Ok(sk.add_drawn_wire_in(&[at], WireSpace::Component)?)
|
||||
})?;
|
||||
Ok(json!({"point": id, "sketch": info}))
|
||||
}
|
||||
@@ -71,23 +111,41 @@ fn point3d(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
fn move3d(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let cmd = "sketch.move3d";
|
||||
let wire = p.get("wire").and_then(Value::as_str).ok_or_else(|| bad(cmd, "`wire` must be a 3D curve or point id"))?.to_string();
|
||||
let index = p.get("index").and_then(Value::as_u64).unwrap_or(0) as usize;
|
||||
let to = p.get("to").and_then(vec3);
|
||||
let by = p.get("by").and_then(vec3);
|
||||
let index = match p.get("index") {
|
||||
None => 0,
|
||||
Some(v) => v
|
||||
.as_u64()
|
||||
.and_then(|v| usize::try_from(v).ok())
|
||||
.filter(|v| *v < solvecraft_sketch::MAX_FIT_POINTS)
|
||||
.ok_or_else(|| bad(cmd, "`index` must be a fit point index below 500"))?,
|
||||
};
|
||||
let parse = |name| p.get(name).map(|v| vec3(v).ok_or_else(|| bad(cmd, format!("`{name}` must be [x,y,z]")))).transpose();
|
||||
let to = parse("to")?;
|
||||
let by = parse("by")?;
|
||||
let inverse = inverse_owner_frame(s, p, cmd)?;
|
||||
let (n, info) = edit(s, p, cmd, false, |sk, _| {
|
||||
let from = sk
|
||||
let drawn = sk
|
||||
.wire_index(&wire)
|
||||
.and_then(|w| sk.wires.get(w))
|
||||
.filter(|w| !w.fit.is_empty())
|
||||
.and_then(|w| w.fit.get(index))
|
||||
.copied()
|
||||
.ok_or_else(|| bad(cmd, format!("`{wire}` has no drawn point {index}")))?;
|
||||
.filter(|w| !w.fit.is_empty() && w.link.is_none())
|
||||
.ok_or_else(|| bad(cmd, format!("`{wire}` is not an editable drawn 3D curve")))?;
|
||||
let from = drawn.fit.get(index).copied().ok_or_else(|| bad(cmd, format!("`{wire}` has no drawn point {index}")))?;
|
||||
let to = match (to, by) {
|
||||
(Some(t), _) => t,
|
||||
(None, Some(d)) => from + d,
|
||||
(Some(t), _) => {
|
||||
if drawn.space == WireSpace::Component {
|
||||
solvecraft_doc::apply_point(&inverse, t)
|
||||
} else {
|
||||
t
|
||||
}
|
||||
}
|
||||
(None, Some(d)) => from + if drawn.space == WireSpace::Component { solvecraft_doc::apply_vector(&inverse, d) } else { d },
|
||||
_ => return Err(bad(cmd, "give `to` or `by`")),
|
||||
};
|
||||
Ok(sk.move_drawn_point(&wire, index, to)?)
|
||||
})?;
|
||||
Ok(json!({"moved": n, "sketch": info}))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "sketch_3d_frame_tests.rs"]
|
||||
mod frame_tests;
|
||||
Reference in new issue
Block a user