Sketch on cut walls, chamfers, pattern copies, join-merged sides and split faces (#625)
A flat face that no construction certificate names on its own now attaches by its exact history name (a new lineage face certificate): cut walls and floors, chamfer faces, each pattern or mirror copy, side walls a Join merged, and the pieces a later feature split a face into. Construction certificates still come first; a seed keeps its certificate when it is patterned. Two flush extrudes of one outline joined end to end keep their exact history in the prism boolean. Face sketch 52% -> 71%, offset plane 52% -> 71%, press-pull/extrude from face 41% -> 60% in the action matrix; baseline blessed. Fixes #491, fixes #492, fixes #493, fixes #557. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
27f20f97cb
commit
6d5df4b9b3
18 files changed
+786
-381
No files matched your search
@@ -59,8 +59,11 @@ pub(super) struct Context<'a> {
|
||||
|
||||
/// How one piece of the result was built.
|
||||
pub(super) enum Built {
|
||||
/// One body of `crate::extrude` of the section's one region between levels `lo` and `hi`.
|
||||
Extruded { section: Section, lo: f64, hi: f64 },
|
||||
/// One body of `crate::extrude` of a section's one region through `layers`: successive
|
||||
/// slabs (the section as read in each, between its two levels), bottom first, all of one
|
||||
/// outline. Several where operands stacked end to end share an outline (one wall then lies
|
||||
/// over each operand's wall in turn, and is made from all of them).
|
||||
Extruded { layers: Vec<(Section, f64, f64)> },
|
||||
/// Sewn (`sew_connected_sections`) from these faces, the sewing saying which each face of
|
||||
/// the piece was built from (`None`: it could not).
|
||||
Sewn { specs: Vec<Spec>, faces: Option<Vec<usize>> },
|
||||
@@ -111,10 +114,11 @@ impl Spec {
|
||||
}
|
||||
}
|
||||
|
||||
/// A face of a piece: the wall over a segment with this tag between two levels, or a cap.
|
||||
/// A face of an extruded piece: a wall or a cap.
|
||||
#[derive(Clone, Copy)]
|
||||
enum Kind {
|
||||
Wall(Tag, f64, f64),
|
||||
/// The wall over this segment of the section.
|
||||
Wall(usize),
|
||||
Cap(usize),
|
||||
}
|
||||
|
||||
@@ -139,7 +143,7 @@ impl<'a> Context<'a> {
|
||||
let mut edges = HashMap::new();
|
||||
for (body, built) in pieces {
|
||||
let (f, e) = match built {
|
||||
Built::Extruded { section, lo, hi } => self.extruded(caps, body, section, *lo, *hi)?,
|
||||
Built::Extruded { layers } => self.extruded(caps, body, layers)?,
|
||||
Built::Sewn { specs, faces } => self.sewn(caps, body, specs, faces.as_deref()?)?,
|
||||
Built::Traced => {
|
||||
let h = body.history()?;
|
||||
@@ -331,28 +335,35 @@ impl<'a> Context<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Faces and edges of a piece `crate::extrude` built from a section of one region. Its
|
||||
/// history says which faces are walls over which segment (numbered over the region's
|
||||
/// loops in order) and which are its caps; the start cap's wires run over the segments in
|
||||
/// loop order (as the extrude builds them), so the vertex after each wire edge is where one
|
||||
/// segment runs into the next, or splits.
|
||||
/// Faces and edges of a piece `crate::extrude` built from a section of one region through
|
||||
/// `layers` (see [`Built::Extruded`]). Its history says which faces are walls over which
|
||||
/// segment (numbered over the region's loops in order) and which are its caps; the start
|
||||
/// cap's wires run over the segments in loop order (as the extrude builds them), so the
|
||||
/// vertex after each wire edge is where one segment runs into the next, or splits. A wall
|
||||
/// through several layers is made from the wall each layer's segment was read beside, and
|
||||
/// an edge between two walls from the operand edges there in each layer.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn extruded(
|
||||
&self,
|
||||
caps: &[Cap],
|
||||
body: &Body,
|
||||
section: &Section,
|
||||
lo: f64,
|
||||
hi: f64,
|
||||
layers: &[(Section, f64, f64)],
|
||||
) -> Option<(Vec<(BTreeSet<FaceOrigin>, bool)>, HashMap<mt::EdgeID, Vec<EdgeOrigin>>)> {
|
||||
let h = body.history()?;
|
||||
let tags = section.segments().1;
|
||||
let tops = [self.cap_origins(caps, section, lo, false), self.cap_origins(caps, section, hi, true)];
|
||||
let ((first, lo, _), (last, _, hi)) = (layers.first()?, layers.last()?);
|
||||
let (lo, hi) = (*lo, *hi);
|
||||
let tags: Vec<Vec<Tag>> = layers.iter().map(|(section, ..)| section.segments().1).collect();
|
||||
let count = tags.first()?.len();
|
||||
if tags.iter().any(|t| t.len() != count) {
|
||||
return None;
|
||||
}
|
||||
let tag = |layer: usize, segment: usize| tags.get(layer).and_then(|t| t.get(segment)).copied();
|
||||
let tops = [self.cap_origins(caps, first, lo, false), self.cap_origins(caps, last, hi, true)];
|
||||
let kinds: Vec<Kind> = h
|
||||
.faces
|
||||
.iter()
|
||||
.map(|f| match f.origins.as_slice() {
|
||||
[FaceOrigin::Profile { segment }] => Some(Kind::Wall(*tags.get(*segment)?, lo, hi)),
|
||||
[FaceOrigin::Profile { segment }] => (*segment < count).then_some(Kind::Wall(*segment)),
|
||||
[FaceOrigin::Cap { end }] => Some(Kind::Cap(usize::from(*end))),
|
||||
_ => None,
|
||||
})
|
||||
@@ -360,7 +371,14 @@ impl<'a> Context<'a> {
|
||||
let faces = kinds
|
||||
.iter()
|
||||
.map(|k| match *k {
|
||||
Kind::Wall(t, lo, hi) => Some((std::iter::once(Self::wall(t)?).collect(), self.whole(caps, t, lo, hi))),
|
||||
Kind::Wall(segment) => {
|
||||
let walls = (0..layers.len()).map(|layer| Self::wall(tag(layer, segment)?)).collect::<Option<BTreeSet<_>>>()?;
|
||||
let whole = match layers {
|
||||
[(_, lo, hi)] => self.whole(caps, tag(0, segment)?, *lo, *hi),
|
||||
_ => false,
|
||||
};
|
||||
Some((walls, whole))
|
||||
}
|
||||
Kind::Cap(end) => Some(tops.get(end).filter(|o| !o.is_empty())?.clone()).map(|o| (o, false)),
|
||||
})
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
@@ -374,7 +392,9 @@ impl<'a> Context<'a> {
|
||||
})
|
||||
};
|
||||
let start = kinds.iter().position(|k| matches!(k, Kind::Cap(0)))?;
|
||||
let mut junctions: HashMap<mt::VertexID, (Tag, Tag)> = HashMap::new();
|
||||
// At each vertex of the start cap: the segment running into it, the one running out,
|
||||
// and whether that is one segment the extrude split there.
|
||||
let mut junctions: HashMap<mt::VertexID, (usize, usize, bool)> = HashMap::new();
|
||||
for w in body.solid.face_iter().nth(start)?.absolute_boundaries() {
|
||||
let es: Vec<mt::Edge> = w.edge_iter().cloned().collect();
|
||||
let segs: Vec<usize> = es.iter().map(segment_of).collect::<Option<_>>()?;
|
||||
@@ -383,20 +403,27 @@ impl<'a> Context<'a> {
|
||||
}
|
||||
for (k, e) in es.iter().enumerate() {
|
||||
let (s, next) = (*segs.get(k)?, *segs.get((k + 1) % segs.len())?);
|
||||
let (t, u) = (*tags.get(s)?, *tags.get(next)?);
|
||||
// A segment the extrude split in two runs on through the vertex between.
|
||||
let pair = if s != next || k + 1 == segs.len() { (t, u) } else { (Tag { end: None, ..t }, Tag { start: None, ..t }) };
|
||||
junctions.insert(e.back().id(), pair);
|
||||
junctions.insert(e.back().id(), (s, next, s == next && k + 1 != segs.len()));
|
||||
}
|
||||
}
|
||||
let mut origins = HashMap::new();
|
||||
for (e, fs) in edges.iter().zip(&bounds) {
|
||||
let [f, g] = fs.as_slice() else { return None };
|
||||
let o = match (kinds.get(*f)?, kinds.get(*g)?) {
|
||||
(Kind::Wall(t, ..), Kind::Cap(end)) | (Kind::Cap(end), Kind::Wall(t, ..)) => self.cap_edge(*t, tops.get(*end)?)?,
|
||||
(Kind::Wall(segment), Kind::Cap(end)) | (Kind::Cap(end), Kind::Wall(segment)) => {
|
||||
let layer = if *end == 0 { 0 } else { layers.len() - 1 };
|
||||
self.cap_edge(tag(layer, *segment)?, tops.get(*end)?)?
|
||||
}
|
||||
(Kind::Wall(..), Kind::Wall(..)) => {
|
||||
let (t1, t2) = [e.absolute_front().id(), e.absolute_back().id()].iter().find_map(|v| junctions.get(v))?;
|
||||
self.junction(*t1, *t2, lo, hi)?
|
||||
let (s, next, split) = *[e.absolute_front().id(), e.absolute_back().id()].iter().find_map(|v| junctions.get(v))?;
|
||||
let mut o = BTreeSet::new();
|
||||
for (layer, (_, lo, hi)) in layers.iter().enumerate() {
|
||||
let (t, u) = (tag(layer, s)?, tag(layer, next)?);
|
||||
// A segment the extrude split in two runs on through the vertex between.
|
||||
let (t, u) = if split { (Tag { end: None, ..t }, Tag { start: None, ..t }) } else { (t, u) };
|
||||
o.extend(self.junction(t, u, *lo, *hi)?);
|
||||
}
|
||||
o.into_iter().collect()
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user