solid.mirror: mirror bodies of placed components where they are shown (#1896)

Thanks @NOWN-DEV.
This commit is contained in:
NOWN-DEV authored and GitHub committed 2026-10-17 04:08:22 -04:00
1 parent 9ce940b0b1
commit 2831426c51
9 files changed
+198 -8

No files matched your search

+15
View File
@@ -244,6 +244,21 @@ impl Document {
m
}
/// World transform of one placement of `component`: the occurrence `path` from the root
/// (empty for the root itself). `None` when the path no longer leads to that component.
pub fn path_transform(&self, component: u64, path: &[u64]) -> Option<Mat> {
if path.len() > MAX_PLACEMENT_DEPTH + 1 {
return None;
}
let (mut m, mut c) = (IDENTITY, 0);
for id in path {
let o = self.occurrences.iter().find(|o| o.id == *id && o.parent == c)?;
m = mat_mul(&m, &o.transform);
c = o.component;
}
(c == component).then_some(m)
}
/// Every placement of each component: (component, occurrence path ids, world transform).
/// A branch stops past [`MAX_PLACEMENT_DEPTH`] levels, the walk past [`MAX_PLACEMENTS`] placements.
pub fn placements(&self) -> Vec<(u64, Vec<u64>, Mat)> {
+5
View File
@@ -682,6 +682,11 @@ pub enum FeatureKind {
/// Join each mirrored body with its original.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
combine: bool,
/// Where each of `bodies` is mirrored from: the occurrence path of the placement it was
/// picked at. The body is mirrored where that placement shows it, as the feature's own
/// component sees it. Empty in older designs, which mirror the bodies' own geometry.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
placements: Vec<Vec<u64>>,
},
/// A drilled hole (simple, counterbore or countersink) at a point, into the material.
Hole {
+31 -4
View File
@@ -1039,7 +1039,9 @@ fn placement_digest(doc: &Document, f: &Feature) -> u64 {
FeatureKind::Revolve { to, .. } => to.is_some(),
_ => false,
};
if to_object {
// A mirror of placed bodies follows where they are placed.
let placed_mirror = matches!(&f.kind, FeatureKind::Mirror { placements, .. } if !placements.is_empty());
if to_object || placed_mirror {
return if doc.occurrences.is_empty() && doc.body_offsets.is_empty() { 0 } else { placements_hash(doc) };
}
let FeatureKind::Sketch { plane, sketch } = &f.kind else { return 0 };
@@ -3741,6 +3743,20 @@ fn mirror_matrix(pl: &Plane) -> Mat {
const MAX_INSTANCES: usize = 1000;
/// Where a mirrored body is, in the frame of the mirror's component: through the placement it
/// was picked at and the feature's component frame. `None` when that is the body's own frame
/// (and in older designs, which saved no placement).
fn mirrored_placement(doc: &Document, f: &Feature, mb: &ModelBody, path: Option<&[u64]>) -> Result<Option<Mat>> {
let Some(path) = path else { return Ok(None) };
let owner = doc.model_body_component(mb);
let lost = || DocError::Invalid(format!("the placement `{}` was mirrored at no longer exists", mb.name));
let world = doc.path_transform(owner, path).ok_or_else(lost)?;
let world = doc.model_body_offset(mb).map(|offset| crate::mat_mul(&world, offset)).unwrap_or(world);
let inverse = crate::mat_inverse(&doc.component_transform(f.component)).ok_or_else(lost)?;
let rel = crate::mat_mul(&inverse, &world);
Ok((!crate::is_identity(&rel)).then_some(rel))
}
/// The placements (column-major matrices) of a pattern's copies, the original excluded.
pub fn pattern_matrices(doc: &Document, st: &ModelState, p: &crate::PatternKind) -> Result<Vec<[[f64; 4]; 4]>> {
let (vals, _) = doc.param_values();
@@ -4670,17 +4686,28 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
}
Ok(())
}
FeatureKind::Mirror { features, plane, bodies, combine } => {
FeatureKind::Mirror { features, plane, bodies, combine, placements } => {
let pl = doc.resolve_plane_in(vals, plane, 0, Some(st))?;
if bodies.is_empty() {
return replay(doc, vals, f, st, &crate::plain(features), &[mirror_matrix(&pl)], warning);
}
let m = mirror_matrix(&pl);
let mut output = 0;
for n in bodies {
for (k, n) in bodies.iter().enumerate() {
let i = st.bodies.iter().position(|b| &b.name == n).ok_or_else(|| DocError::Unknown(format!("body `{n}`")))?;
let Some(mb) = st.bodies.get(i).cloned() else { continue };
let mirrored = moved_copy(&mb, kernel::transform_matrix(&mb.body, m)?, 1);
// A body of a component placed elsewhere is mirrored where it is shown: the copy
// (in this feature's component) from its placement, a joined body in its own frame.
let placed = mirrored_placement(doc, f, &mb, placements.get(k).map(Vec::as_slice))?;
let (copy, own) = match placed {
Some(rel) => {
let inverse = crate::mat_inverse(&rel).ok_or_else(|| DocError::Invalid(format!("the placement of `{n}` is singular")))?;
let copy = crate::mat_mul(&m, &rel);
(copy, crate::mat_mul(&inverse, &copy))
}
None => (m, m),
};
let mirrored = moved_copy(&mb, kernel::transform_matrix(&mb.body, if *combine { own } else { copy })?, 1);
if *combine {
let joined = kernel::boolean(&mb.body, &mirrored.body, BoolOp::Union)?
.ok_or_else(|| DocError::Invalid("the mirror join produced nothing".into()))?;
@@ -0,0 +1,5 @@
//! A Mirror of bodies keeps the placement each body was picked at (`placements`: one occurrence
//! path per body), and mirrors the body where that placement shows it, so a body of a moved or
//! inserted component, or of one of its further instances, is mirrored where it is seen. Older
//! designs need no transformation: a Mirror without `placements` mirrors the bodies' own
//! geometry as before; the last release asks for an update instead of mirroring them there.