From 52f25ca30b242df5f331f22b9af0a672d74fa484 Mon Sep 17 00:00:00 2001 From: jyviko <2501648+jyviko@users.noreply.github.com> Date: Thu, 22 Oct 2026 00:22:51 -0400 Subject: [PATCH] doc: a pattern or mirror of a pattern or mirror copies what that one made Replaying a Mirror or Pattern feature asked it for its tools, which only primitives and sketch features have: 'Mirror1 cannot be patterned or mirrored yet' (#3652). A copy of a pattern or mirror of features now replays that feature's own features with its moves followed by these (a mirror of Mirror1 reflects the hole Mirror1 made), nested at most 8 deep, never through itself, and to at most MAX_INSTANCES copies in all. (not built: base in progress) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017itGh5fpMEousnZ91cYcVt --- crates/doc/src/eval.rs | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/crates/doc/src/eval.rs b/crates/doc/src/eval.rs index 726e525333..8d3488b795 100644 --- a/crates/doc/src/eval.rs +++ b/crates/doc/src/eval.rs @@ -4679,6 +4679,9 @@ fn path_at(segs: &[kernel::PathSeg], d: f64) -> (Vec3, Vec3) { (Vec3::ZERO, Vec3::X) } +/// Most patterns and mirrors of features nested in one another (a mirror of a mirror). +const MAX_REPLAY_DEPTH: usize = 8; + /// Apply transformed copies of the tools of `features` (patterns, mirrors). fn replay( doc: &Document, @@ -4688,6 +4691,21 @@ fn replay( features: &[String], mats: &[Mat], warning: &mut Option, +) -> Result<()> { + replay_at(doc, vals, f, st, features, mats, warning, 0) +} + +/// [`replay`], `depth` patterns or mirrors of features deep. +#[allow(clippy::too_many_arguments)] +fn replay_at( + doc: &Document, + vals: &BTreeMap, + f: &Feature, + st: &mut ModelState, + features: &[String], + mats: &[Mat], + warning: &mut Option, + depth: usize, ) -> Result<()> { if features.is_empty() { return Err(DocError::Invalid("no features selected".into())); @@ -4710,6 +4728,29 @@ fn replay( .find(|x| &x.name == name && !matches!(x.kind, FeatureKind::Sketch { .. } | FeatureKind::ConstructionPlane { .. })) .or_else(|| doc.find_feature(name)) .ok_or_else(|| DocError::Unknown(format!("feature `{name}`")))?; + // A copy of a pattern or mirror of features copies what that one copied: its features, + // moved by its moves and then by these. + let nested = match &src.kind { + FeatureKind::Pattern { features, pattern, bodies } if bodies.is_empty() => { + Some((features, pattern_transforms(doc, vals, st, src.component, pattern)?)) + } + FeatureKind::Mirror { features, plane, bodies, .. } if bodies.is_empty() => { + Some((features, vec![mirror_matrix(&doc.resolve_plane_in(vals, plane, 0, Some(st))?)])) + } + _ => None, + }; + if let Some((inner, inner_mats)) = nested { + if depth >= MAX_REPLAY_DEPTH || src.id == f.id { + return Err(DocError::Invalid(format!("{} copies patterns or mirrors nested too deeply", f.name))); + } + // As many copies in all as one pattern may make. + if mats.len().saturating_mul(inner_mats.len()) > MAX_INSTANCES { + return Err(DocError::Invalid(format!("{} would make more than {MAX_INSTANCES} copies", f.name))); + } + let composed: Vec = mats.iter().flat_map(|m| inner_mats.iter().map(move |i| crate::mat_mul(m, i))).collect(); + replay_at(doc, vals, f, st, &crate::plain(inner), &composed, warning, depth + 1)?; + continue; + } // A fillet or chamfer repeats at the copied edges. if let FeatureKind::Fillet { edges, radius, .. } | FeatureKind::Chamfer { edges, distance: radius, .. } = &src.kind { let r = val(vals, radius, Kind::Length)?;