Persistent naming: joint origins on faces and circular ends, and face appearances, store the face's name and find it by name first (a joint whose face is gone reports it instead of attaching elsewhere)
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
1 parent
e7aff4fef0
commit
e7a85b30f7
4 files changed
+62
-9
No files matched your search
@@ -42,6 +42,9 @@ pub struct FaceLook {
|
||||
pub body: String,
|
||||
pub point: Vec3,
|
||||
pub look: Look,
|
||||
/// The face's persistent name (found first).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
/// The design's appearances.
|
||||
@@ -123,7 +126,21 @@ impl Document {
|
||||
/// assignment to the same face wins.
|
||||
pub fn face_colors(&self, b: &crate::ModelBody) -> Vec<(usize, Look)> {
|
||||
let mut out: Vec<(usize, Look)> = Vec::new();
|
||||
for (p, look) in self.face_looks(&b.name) {
|
||||
let names = crate::naming::face_names(b);
|
||||
for f in self.appearances.faces.iter().filter(|f| f.body == b.name) {
|
||||
let (p, look) = (f.point, f.look.clone());
|
||||
// By name (every piece of a split face), else by the point.
|
||||
if let Some(n) = &f.name {
|
||||
let stem = crate::naming::strip_piece(n);
|
||||
let hits: Vec<usize> = (0..names.len()).filter(|i| names.get(*i).is_some_and(|x| crate::naming::strip_piece(x) == stem)).collect();
|
||||
if !hits.is_empty() {
|
||||
for i in hits {
|
||||
out.retain(|(j, _)| *j != i);
|
||||
out.push((i, look.clone()));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if let Some(i) = face_index_at(b, p) {
|
||||
out.retain(|(j, _)| *j != i);
|
||||
out.push((i, look));
|
||||
|
||||
@@ -30,11 +30,20 @@ pub enum Snap {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
x: Option<Vec3>,
|
||||
},
|
||||
/// The centre of the planar face nearest `pick`, z along its outward normal.
|
||||
FaceCenter { pick: Vec3 },
|
||||
/// The centre of the planar face nearest `pick`, z along its outward normal. `face` is the
|
||||
/// face's persistent name (found first).
|
||||
FaceCenter {
|
||||
pick: Vec3,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
face: Option<String>,
|
||||
},
|
||||
/// The centre of the circular end nearest `pick` of the cylindrical face there, z along its
|
||||
/// axis (pointing out of the face's end).
|
||||
CircleCenter { pick: Vec3 },
|
||||
CircleCenter {
|
||||
pick: Vec3,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
face: Option<String>,
|
||||
},
|
||||
/// A point, z along `z` (default +Z).
|
||||
Point {
|
||||
point: Vec3,
|
||||
@@ -225,12 +234,24 @@ fn component_of(doc: &Document, occ: u64) -> Result<u64> {
|
||||
}
|
||||
|
||||
/// Resolve a joint origin to a frame in its component's coordinates.
|
||||
/// A snap's pick point moved onto its named face (an error when the face is gone: a joint must
|
||||
/// not quietly attach elsewhere).
|
||||
fn named_pick(st: &ModelState, pick: Vec3, face: Option<&str>) -> Result<Vec3> {
|
||||
match face {
|
||||
Some(n) => crate::naming::point_on_face(st, n, pick)
|
||||
.map(|(q, _)| q)
|
||||
.ok_or_else(|| DocError::Invalid(format!("joint origin: the face `{n}` no longer exists (edit the joint to pick again)"))),
|
||||
None => Ok(pick),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_origin(doc: &Document, st: &ModelState, o: &JointOrigin) -> Result<Mat> {
|
||||
let bad = || DocError::Invalid("joint origin: degenerate frame".into());
|
||||
match &o.snap {
|
||||
Snap::Frame { origin, z, x } => frame(*origin, *z, *x).ok_or_else(bad),
|
||||
Snap::Point { point, z } => frame(*point, z.unwrap_or(Vec3::Z), None).ok_or_else(bad),
|
||||
Snap::FaceCenter { pick } => {
|
||||
Snap::FaceCenter { pick, face } => {
|
||||
let pick = &named_pick(st, *pick, face.as_deref())?;
|
||||
let comp = component_of(doc, o.occurrence)?;
|
||||
let mut best: Option<(f64, Vec3, Vec3)> = None;
|
||||
for b in component_bodies(doc, st, comp) {
|
||||
@@ -246,7 +267,8 @@ pub fn resolve_origin(doc: &Document, st: &ModelState, o: &JointOrigin) -> Resul
|
||||
let (_, c, n) = best.ok_or_else(|| DocError::Invalid("joint origin: no planar face there".into()))?;
|
||||
frame(c, n, None).ok_or_else(bad)
|
||||
}
|
||||
Snap::CircleCenter { pick } => {
|
||||
Snap::CircleCenter { pick, face } => {
|
||||
let pick = &named_pick(st, *pick, face.as_deref())?;
|
||||
let comp = component_of(doc, o.occurrence)?;
|
||||
let cyl = component_bodies(doc, st, comp)
|
||||
.into_iter()
|
||||
|
||||
@@ -354,6 +354,16 @@ fn appearance(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
if bodies.is_empty() && faces.is_empty() && comps.is_empty() {
|
||||
return Err(bad(cmd, "give `bodies`, `faces` or `components`"));
|
||||
}
|
||||
// Persistent names of the picked faces.
|
||||
let st_names: Vec<(String, solvecraft_geom::Vec3, Option<String>)> = faces
|
||||
.iter()
|
||||
.map(|(b, pt)| {
|
||||
let n = st.body(b).and_then(|mb| {
|
||||
solvecraft_doc::appearance::face_index_at(mb, *pt).and_then(|i| solvecraft_doc::naming::face_names(mb).get(i).cloned())
|
||||
});
|
||||
(b.clone(), *pt, n)
|
||||
})
|
||||
.collect();
|
||||
let a = &mut s.doc_mut().appearances;
|
||||
for b in &bodies {
|
||||
match &look {
|
||||
@@ -368,7 +378,8 @@ fn appearance(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
if a.faces.len() >= 100_000 {
|
||||
return Err(bad(cmd, "too many face appearances"));
|
||||
}
|
||||
a.faces.push(FaceLook { body: body.clone(), point: *pt, look: l.clone() });
|
||||
let name = st_names.iter().find(|(b, p, _)| b == body && p.dist(*pt) < 1e-12).and_then(|(_, _, n)| n.clone());
|
||||
a.faces.push(FaceLook { body: body.clone(), point: *pt, look: l.clone(), name });
|
||||
}
|
||||
}
|
||||
for c in &comps {
|
||||
|
||||
@@ -131,8 +131,11 @@ fn origin_param(s: &Session, v: Option<&Value>, cmd: &str) -> Result<JointOrigin
|
||||
let local = |p: Vec3| solvecraft_doc::apply_point(&inv, p);
|
||||
let local_dir = |d: Vec3| solvecraft_doc::apply_vector(&inv, d);
|
||||
let snap = match what {
|
||||
"face" => Snap::FaceCenter { pick: local(world) },
|
||||
"circle" => Snap::CircleCenter { pick: local(world) },
|
||||
"face" | "circle" => {
|
||||
let pick = local(world);
|
||||
let face = solvecraft_doc::naming::face_names_at(&s.model.state(), &[pick]).into_iter().next().filter(|n| !n.is_empty());
|
||||
if what == "face" { Snap::FaceCenter { pick, face } } else { Snap::CircleCenter { pick, face } }
|
||||
}
|
||||
"point" => Snap::Point { point: local(world), z: v.get("z").and_then(vec3).map(local_dir) },
|
||||
_ => {
|
||||
let f = v.get("frame").cloned().unwrap_or_default();
|
||||
|
||||
Reference in new issue
Block a user