A pattern or mirror of a suppressed feature has nothing to copy: its copies go with it and the pattern warns (the acceptance enclosure loses all four standoffs when Standoff is suppressed); scenario acceptance_pattern_of_suppressed_feature enabled
Fixes #42 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT
This commit is contained in:
1 parent
d1428ef27c
commit
72bc6fb010
4 files changed
+59
-7
No files matched your search
+22
-4
@@ -1948,11 +1948,29 @@ fn path_at(segs: &[kernel::PathSeg], d: f64) -> (Vec3, Vec3) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Apply transformed copies of the tools of `features` (patterns, mirrors).
|
/// Apply transformed copies of the tools of `features` (patterns, mirrors).
|
||||||
fn replay(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st: &mut ModelState, features: &[String], mats: &[Mat]) -> Result<()> {
|
fn replay(
|
||||||
|
doc: &Document,
|
||||||
|
vals: &BTreeMap<String, Value>,
|
||||||
|
f: &Feature,
|
||||||
|
st: &mut ModelState,
|
||||||
|
features: &[String],
|
||||||
|
mats: &[Mat],
|
||||||
|
warning: &mut Option<String>,
|
||||||
|
) -> Result<()> {
|
||||||
if features.is_empty() {
|
if features.is_empty() {
|
||||||
return Err(DocError::Invalid("no features selected".into()));
|
return Err(DocError::Invalid("no features selected".into()));
|
||||||
}
|
}
|
||||||
for name in features {
|
// A suppressed feature has nothing to copy: its copies go with it.
|
||||||
|
let suppressed: Vec<&String> = features.iter().filter(|n| doc.find_feature(n).is_some_and(|x| x.suppressed)).collect();
|
||||||
|
if !suppressed.is_empty() {
|
||||||
|
let list = suppressed.iter().map(|n| format!("`{n}`")).collect::<Vec<_>>().join(", ");
|
||||||
|
*warning = Some(if suppressed.len() == features.len() {
|
||||||
|
format!("nothing to copy: {list} suppressed")
|
||||||
|
} else {
|
||||||
|
format!("{list} suppressed and not copied")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for name in features.iter().filter(|n| !suppressed.contains(n)) {
|
||||||
// A sketch and its extrude often share a name; the feature that makes geometry wins.
|
// A sketch and its extrude often share a name; the feature that makes geometry wins.
|
||||||
let src = doc
|
let src = doc
|
||||||
.features
|
.features
|
||||||
@@ -2515,7 +2533,7 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
|||||||
FeatureKind::Pattern { features, pattern, bodies } => {
|
FeatureKind::Pattern { features, pattern, bodies } => {
|
||||||
let mats = pattern_transforms(vals, st, pattern)?;
|
let mats = pattern_transforms(vals, st, pattern)?;
|
||||||
if bodies.is_empty() {
|
if bodies.is_empty() {
|
||||||
return replay(doc, vals, f, st, features, &mats);
|
return replay(doc, vals, f, st, features, &mats, warning);
|
||||||
}
|
}
|
||||||
// Bodies: each copy is a new body named after its source.
|
// Bodies: each copy is a new body named after its source.
|
||||||
for n in bodies {
|
for n in bodies {
|
||||||
@@ -2531,7 +2549,7 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
|||||||
FeatureKind::Mirror { features, plane, bodies, combine } => {
|
FeatureKind::Mirror { features, plane, bodies, combine } => {
|
||||||
let pl = doc.resolve_plane_in(vals, plane, 0, Some(st))?;
|
let pl = doc.resolve_plane_in(vals, plane, 0, Some(st))?;
|
||||||
if bodies.is_empty() {
|
if bodies.is_empty() {
|
||||||
return replay(doc, vals, f, st, features, &[mirror_matrix(&pl)]);
|
return replay(doc, vals, f, st, features, &[mirror_matrix(&pl)], warning);
|
||||||
}
|
}
|
||||||
let m = mirror_matrix(&pl);
|
let m = mirror_matrix(&pl);
|
||||||
for n in bodies {
|
for n in bodies {
|
||||||
|
|||||||
@@ -434,6 +434,8 @@ mod delete_face_tests;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod extent_tests;
|
mod extent_tests;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
mod pattern_suppress_tests;
|
||||||
|
#[cfg(test)]
|
||||||
mod profile_refs_tests;
|
mod profile_refs_tests;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod split_face_tests;
|
mod split_face_tests;
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
//! A pattern of a suppressed feature has nothing to copy (#42): its copies go too, and the
|
||||||
|
//! pattern says so.
|
||||||
|
|
||||||
|
use serde_json::{Value, json};
|
||||||
|
|
||||||
|
use crate::Session;
|
||||||
|
|
||||||
|
fn run(s: &mut Session, id: &str, p: Value) -> Value {
|
||||||
|
match s.execute(id, &p) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => panic!("{id} {p}: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn volume(s: &Session, body: &str) -> f64 {
|
||||||
|
s.model.state().body(body).map(|b| solvecraft_kernel::measure(&b.body).unwrap().volume).unwrap_or(0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn suppressing_a_patterned_feature_removes_its_copies() {
|
||||||
|
let mut s = Session::default();
|
||||||
|
let script: Value = serde_json::from_str(include_str!("../../../examples/acceptance/sensor_enclosure.json")).unwrap();
|
||||||
|
s.run_script(&script).unwrap();
|
||||||
|
let full = volume(&s, "Case");
|
||||||
|
run(&mut s, "timeline.suppress", json!({"feature": "Standoff", "suppressed": true}));
|
||||||
|
let without = volume(&s, "Case");
|
||||||
|
// All four standoffs go (each 116.8 mm³), not just the seed.
|
||||||
|
assert!((full - without - 4.0 * 116.8).abs() < 2.0, "{full} → {without}");
|
||||||
|
let pattern = s.doc.find_feature("RectangularPattern1").unwrap().id;
|
||||||
|
let r = s.model.result(pattern).unwrap();
|
||||||
|
assert!(r.error.is_none() && r.warning.as_deref().is_some_and(|w| w.contains("Standoff")), "{r:?}");
|
||||||
|
// Back on: the four standoffs return.
|
||||||
|
run(&mut s, "timeline.suppress", json!({"feature": "Standoff", "suppressed": false}));
|
||||||
|
assert!((volume(&s, "Case") - full).abs() < 1e-6 * full);
|
||||||
|
}
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
[
|
[
|
||||||
{
|
|
||||||
"pending": "#42: suppressing a patterned feature keeps the pattern's copies"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"note": "Acceptance (#27): suppressing the PCB standoff takes the patterned copies with it."
|
"note": "Acceptance (#27): suppressing the PCB standoff takes the patterned copies with it."
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in new issue
Block a user