mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-04 11:05:56 +00:00
kernel: IGES trim pieces closed onto their meeting points in main's assembly reader
Main's reader (#1609, #3845, #3928) and the branch's met in the rebase, and the tree kept main's gap closing: it moved a trim piece's end vertex onto the mean of the gap's ends without changing its curve, and widened the STEP file's stated accuracy to cover the move. SMLib checks each edge against its vertex at the body's tolerance and refused the box (SmTrimmingTools Edge/Edge Gap 0.005 > 0.00425, rig 3845); the first piece of a trim curve with no start point was also turned against the second (rig 3913, "edge loop is not connected"). The reader keeps main's structure (groups and subfigure instances as products, names, placements, separate lumps as separate solids, the originating system) and sews each group's surfaces the branch's way: each meeting point of two pieces is one vertex, where most piece ends there agree, and a line or clamped B-spline that misses it is rebuilt to end there exactly. Main's limits on which gaps are one corner written loosely (0.1 mm, 5% of the shorter piece) decide which meeting points are joined; the stated accuracy stays the file's. A trim curve's first piece starts at its end away from the second when no start point is given. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+178
-83
@@ -288,7 +288,8 @@ fn parse(text: &str) -> R<Iges> {
|
||||
|
||||
/// A model curve segment ready for an edge: STEP curve id, its end points, points a quarter and
|
||||
/// three quarters of the way along and one at the middle (for matching shared edges), and
|
||||
/// whether the curve runs from `b` to `a`.
|
||||
/// whether the curve runs from `b` to `a`; and its geometry, for a curve that has to be made to
|
||||
/// end where its piece meets the next ([`Tr::closed`]).
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct Seg {
|
||||
curve: u64,
|
||||
@@ -298,11 +299,28 @@ struct Seg {
|
||||
q3: V3,
|
||||
mid: V3,
|
||||
rev: bool,
|
||||
shape: Shape,
|
||||
}
|
||||
|
||||
/// A segment's curve as it can be rebuilt: a line, or the B-spline `Tr::splines[i]`; another
|
||||
/// curve keeps its ends.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
enum Shape {
|
||||
Line,
|
||||
Spline(usize),
|
||||
Other,
|
||||
}
|
||||
|
||||
impl Seg {
|
||||
fn along(curve: u64, f: impl Fn(f64) -> V3) -> Seg {
|
||||
Seg { curve, a: f(0.0), b: f(1.0), q: f(0.25), q3: f(0.75), mid: f(0.5), rev: false }
|
||||
Seg { curve, a: f(0.0), b: f(1.0), q: f(0.25), q3: f(0.75), mid: f(0.5), rev: false, shape: Shape::Other }
|
||||
}
|
||||
fn with(self, shape: Shape) -> Seg {
|
||||
Seg { shape, ..self }
|
||||
}
|
||||
/// Its length, along the points it keeps.
|
||||
fn length(&self) -> f64 {
|
||||
norm(sub(self.q, self.a)) + norm(sub(self.mid, self.q)) + norm(sub(self.q3, self.mid)) + norm(sub(self.b, self.q3))
|
||||
}
|
||||
fn reversed(self) -> Seg {
|
||||
Seg { a: self.b, b: self.a, q: self.q3, q3: self.q, rev: !self.rev, ..self }
|
||||
@@ -343,6 +361,7 @@ fn perp(z: V3) -> V3 {
|
||||
}
|
||||
|
||||
/// A rational B-spline curve evaluated by de Boor's algorithm.
|
||||
#[derive(Clone)]
|
||||
struct Nurbs {
|
||||
deg: usize,
|
||||
knots: Vec<f64>,
|
||||
@@ -431,8 +450,8 @@ struct Tr<'a> {
|
||||
curve_work: usize,
|
||||
/// Composite curves (102) on the path being expanded.
|
||||
composites: Vec<i64>,
|
||||
/// Farthest a trim loop's end was moved to close a gap (mm; see `close_gaps`).
|
||||
snapped: f64,
|
||||
/// The B-splines of segments ([`Shape::Spline`]).
|
||||
splines: Vec<Nurbs>,
|
||||
}
|
||||
|
||||
fn xf_key(x: &Xf) -> [u64; 12] {
|
||||
@@ -539,7 +558,7 @@ impl<'a> Tr<'a> {
|
||||
let dd = self.o.dir(d);
|
||||
let v = self.o.add(format!("VECTOR('',#{dd},{})", r(norm(sub(b, a)))));
|
||||
let c = self.o.add(format!("LINE('',#{pa},#{v})"));
|
||||
Ok(vec![Seg::along(c, |t| add(a, mul(sub(b, a), t)))])
|
||||
Ok(vec![Seg::along(c, |t| add(a, mul(sub(b, a), t))).with(Shape::Line)])
|
||||
}
|
||||
100 => {
|
||||
let Arc { c, rad, a0, a1 } = self.arc(seq)?;
|
||||
@@ -577,7 +596,9 @@ impl<'a> Tr<'a> {
|
||||
nb.eval(t0 + (t1 - t0) * k as f64 / 4.0).ok_or_else(|| format!("entity {seq}: B-spline cannot be evaluated"))?;
|
||||
}
|
||||
let c = self.write_nurbs_curve(&nb);
|
||||
Ok(vec![Seg::along(c, |t| nb.eval(t0 + (t1 - t0) * t).unwrap_or([0.0; 3]))])
|
||||
let seg = Seg::along(c, |t| nb.eval(t0 + (t1 - t0) * t).unwrap_or([0.0; 3]));
|
||||
self.splines.push(nb);
|
||||
Ok(vec![seg.with(Shape::Spline(self.splines.len() - 1))])
|
||||
}
|
||||
104 => {
|
||||
// Ellipse (form 1) in canonical position: A x² + C y² + F = 0.
|
||||
@@ -970,6 +991,59 @@ impl<'a> Tr<'a> {
|
||||
Nurbs { deg: m2, knots: t_knots, pts: column }.eval(v).ok_or_else(|| "surface cannot be evaluated".into())
|
||||
}
|
||||
|
||||
/// Segment `s` made to run from `pa` to `pb`, the vertices its piece meets the pieces before
|
||||
/// and after it at. A line is the line between them; a clamped B-spline has its end control
|
||||
/// points moved onto them (it ends at them exactly, its shape inside changed by no more than
|
||||
/// the gap). Any other curve keeps its ends, and the gap is reported.
|
||||
fn closed(&mut self, s: &Seg, pa: V3, pb: V3) -> Seg {
|
||||
let gap = norm(sub(s.a, pa)).max(norm(sub(s.b, pb)));
|
||||
if gap <= self.tol {
|
||||
return *s;
|
||||
}
|
||||
match s.shape {
|
||||
Shape::Line if norm(sub(pb, pa)) > self.tol => {
|
||||
let (p, d) = (self.o.point(pa), unit(sub(pb, pa)).unwrap_or([1.0, 0.0, 0.0]));
|
||||
let dd = self.o.dir(d);
|
||||
let v = self.o.add(format!("VECTOR('',#{dd},{})", r(norm(sub(pb, pa)))));
|
||||
let c = self.o.add(format!("LINE('',#{p},#{v})"));
|
||||
return Seg::along(c, |t| add(pa, mul(sub(pb, pa), t))).with(Shape::Line);
|
||||
}
|
||||
Shape::Spline(i) => {
|
||||
if let Some(mut nb) = self.splines.get(i).cloned() {
|
||||
let (n, p) = (nb.pts.len(), nb.deg);
|
||||
let clamped = n > p
|
||||
&& nb.knots.len() == n + p + 1
|
||||
&& nb.knots[..=p].iter().all(|k| *k == nb.knots[0])
|
||||
&& nb.knots[n..].iter().all(|k| *k == nb.knots[n + p]);
|
||||
if clamped {
|
||||
// The curve runs from `a` to `b`, or from `b` to `a` when reversed.
|
||||
let (start, end) = if s.rev { (pb, pa) } else { (pa, pb) };
|
||||
for (k, q) in [(0, start), (n - 1, end)] {
|
||||
let w = nb.pts[k][3];
|
||||
nb.pts[k] = [q[0] * w, q[1] * w, q[2] * w, w];
|
||||
}
|
||||
let (t0, t1) = nb.range();
|
||||
let c = self.write_nurbs_curve(&nb);
|
||||
let seg = Seg::along(c, |t| nb.eval(t0 + (t1 - t0) * t).unwrap_or([0.0; 3]));
|
||||
self.splines.push(nb);
|
||||
let seg = seg.with(Shape::Spline(self.splines.len() - 1));
|
||||
return if s.rev { seg.reversed() } else { seg };
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
let near = if norm(sub(s.a, pa)) >= norm(sub(s.b, pb)) { pa } else { pb };
|
||||
self.warn(format!(
|
||||
"trim curve pieces miss each other by {} mm at ({}, {}, {}): the gap is left open within the vertex's tolerance",
|
||||
r(gap),
|
||||
r(near[0]),
|
||||
r(near[1]),
|
||||
r(near[2])
|
||||
));
|
||||
*s
|
||||
}
|
||||
|
||||
/// The natural boundary of a B-spline surface (its four edge curves), for trimmed surfaces
|
||||
/// with no outer curve.
|
||||
fn natural_boundary(&mut self, seq: i64, xf: &Xf) -> R<Vec<Seg>> {
|
||||
@@ -1004,7 +1078,8 @@ impl<'a> Tr<'a> {
|
||||
continue; // a pole
|
||||
}
|
||||
let c = self.write_nurbs_curve(&nb);
|
||||
let seg = Seg::along(c, |t| ev(t).unwrap_or([0.0; 3]));
|
||||
let seg = Seg::along(c, |t| ev(t).unwrap_or([0.0; 3])).with(Shape::Spline(self.splines.len()));
|
||||
self.splines.push(nb.clone());
|
||||
out.push(if rev { seg.reversed() } else { seg });
|
||||
}
|
||||
Ok(out)
|
||||
@@ -1075,6 +1150,10 @@ impl Sewer {
|
||||
/// the segment runs along it.
|
||||
fn edge(&mut self, s: &Seg) -> (usize, bool) {
|
||||
let (a, b) = (self.vertex(s.a), self.vertex(s.b));
|
||||
self.edge_between(s, a, b)
|
||||
}
|
||||
/// [`Sewer::edge`] for a segment from vertex `a` to vertex `b`.
|
||||
fn edge_between(&mut self, s: &Seg, a: usize, b: usize) -> (usize, bool) {
|
||||
let key = (a.min(b), a.max(b));
|
||||
let close = |x: V3, y: V3| norm(sub(x, y)) <= self.tol * 10.0 + 1e-9;
|
||||
if let Some(list) = self.by_ends.get(&key) {
|
||||
@@ -1253,13 +1332,12 @@ fn translate(text: &str) -> std::result::Result<(String, Vec<String>), String> {
|
||||
surf_cache: HashMap::new(),
|
||||
curve_work: 0,
|
||||
composites: Vec::new(),
|
||||
snapped: 0.0,
|
||||
splines: Vec::new(),
|
||||
};
|
||||
// Header: units and context.
|
||||
let mm = t.o.add("(LENGTH_UNIT()NAMED_UNIT(*)SI_UNIT(.MILLI.,.METRE.))");
|
||||
let rad = t.o.add("(NAMED_UNIT(*)PLANE_ANGLE_UNIT()SI_UNIT($,.RADIAN.))");
|
||||
let sr = t.o.add("(NAMED_UNIT(*)SI_UNIT($,.STERADIAN.)SOLID_ANGLE_UNIT())");
|
||||
let unc_line = t.o.lines.len();
|
||||
let unc = t.o.add(format!("UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE({}),#{mm},'distance_accuracy_value','')", r(tol)));
|
||||
let ctx = t.o.add(format!(
|
||||
"(GEOMETRIC_REPRESENTATION_CONTEXT(3)GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#{unc}))GLOBAL_UNIT_ASSIGNED_CONTEXT((#{mm},#{rad},#{sr}))REPRESENTATION_CONTEXT('',''))"
|
||||
@@ -1305,13 +1383,6 @@ fn translate(text: &str) -> std::result::Result<(String, Vec<String>), String> {
|
||||
}
|
||||
}
|
||||
assembly.write(&mut t, &seqs, &solids, &mut styles, ctx)?;
|
||||
if t.snapped > 0.0 {
|
||||
// The STEP reader takes vertices up to ten times the stated accuracy off their curves:
|
||||
// state enough to cover the ends `close_gaps` moved, and no more.
|
||||
if let Some(l) = t.o.lines.get_mut(unc_line) {
|
||||
*l = format!("UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE({}),#{mm},'distance_accuracy_value','')", r(tol.max(t.snapped * 0.2)));
|
||||
}
|
||||
}
|
||||
for (item, c) in styles {
|
||||
let rgb = t.o.add(format!("COLOUR_RGB('',{},{},{})", r(c[0] as f64), r(c[1] as f64), r(c[2] as f64)));
|
||||
let fasc = t.o.add(format!("FILL_AREA_STYLE_COLOUR('',#{rgb})"));
|
||||
@@ -1366,11 +1437,7 @@ fn sew_surfaces(
|
||||
Err(e) => t.warn(format!("surface {seq}: not read: {e}")),
|
||||
}
|
||||
}
|
||||
close_gaps(t, &mut drafts, sw.tol);
|
||||
for d in drafts {
|
||||
let loops = d.loops.iter().map(|l| l.iter().map(|s| sw.edge(s)).collect()).collect();
|
||||
faces.push(FaceB { surface: d.surface, sense: true, loops, color: d.color });
|
||||
}
|
||||
faces.extend(sew(t, sw, drafts));
|
||||
if faces.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
@@ -1441,75 +1508,97 @@ const MAX_TRIM_GAP: f64 = 0.1;
|
||||
/// ...and a gap must also be at most this share of the shorter piece on either side.
|
||||
const MAX_TRIM_GAP_SHARE: f64 = 0.05;
|
||||
|
||||
/// Close the small gaps between consecutive pieces of the faces' trim loops. Some exporters
|
||||
/// write each piece's model-space curve separately, and consecutive pieces can then miss by a
|
||||
/// few micrometres to a tenth of a millimetre, well past the file's stated resolution: the loop
|
||||
/// does not close, its face is lost and the shell stays open. The two ends of each such gap, and
|
||||
/// every other piece end at either of them (the neighbouring faces at that corner), move to
|
||||
/// their mean, so all the faces there sew to one vertex. A gap wider than `MAX_TRIM_GAP`, or not
|
||||
/// small next to its pieces, is left open; the sewing tolerance stays the file's.
|
||||
fn close_gaps(t: &mut Tr, faces: &mut [Draft], tol: f64) {
|
||||
let length = |s: &Seg| norm(sub(s.q, s.a)) + norm(sub(s.mid, s.q)) + norm(sub(s.q3, s.mid)) + norm(sub(s.b, s.q3));
|
||||
// The gaps' ends as vertices, joined into groups (union-find).
|
||||
let mut ends = Sewer { tol, verts: Vec::new(), grid: HashMap::new(), edges: Vec::new(), by_ends: HashMap::new() };
|
||||
let mut parent: Vec<usize> = Vec::new();
|
||||
let root = |parent: &[usize], mut i: usize| {
|
||||
while let Some(&p) = parent.get(i).filter(|p| **p != i) {
|
||||
/// Faces read from trimmed surfaces, sewn by their shared edges. A boundary loop is one closed
|
||||
/// curve: each piece ends where the next one starts (IGES 5.3, 4.24 composite curve, and
|
||||
/// 4.34/4.35 a trimmed surface's boundaries are closed curves). Some exporters write each
|
||||
/// piece's model-space curve separately, and consecutive pieces can then miss by a few
|
||||
/// micrometres to a tenth of a millimetre, well past the file's stated resolution. Each meeting
|
||||
/// point of two pieces is then still one vertex, unless the gap is wider than `MAX_TRIM_GAP` or
|
||||
/// not small next to its pieces (`MAX_TRIM_GAP_SHARE`): past that the ends may be different
|
||||
/// corners of the model, and the gap stays open. The vertex goes where most of the piece ends
|
||||
/// meeting there agree (the neighbouring faces at that corner), and a piece that misses it is
|
||||
/// made to end there ([`Tr::closed`]): the edges then meet at their vertices exactly, as the
|
||||
/// file means them to, and the sewing tolerance stays the file's.
|
||||
fn sew(t: &mut Tr, sw: &mut Sewer, faces: Vec<Draft>) -> Vec<FaceB> {
|
||||
// Every piece's end vertices, then the vertices each meeting point joins made one.
|
||||
let ends: Vec<Vec<Vec<[usize; 2]>>> =
|
||||
faces.iter().map(|f| f.loops.iter().map(|l| l.iter().map(|s| [sw.vertex(s.a), sw.vertex(s.b)]).collect()).collect()).collect();
|
||||
let mut same: Vec<usize> = (0..sw.verts.len()).collect();
|
||||
fn root(same: &mut [usize], mut i: usize) -> usize {
|
||||
while let Some(&p) = same.get(i) {
|
||||
if p == i {
|
||||
break;
|
||||
}
|
||||
let up = same.get(p).copied().unwrap_or(p);
|
||||
if let Some(slot) = same.get_mut(i) {
|
||||
*slot = up;
|
||||
}
|
||||
i = p;
|
||||
}
|
||||
i
|
||||
};
|
||||
for l in faces.iter().flat_map(|f| f.loops.iter()) {
|
||||
for (i, prev) in l.iter().enumerate() {
|
||||
let Some(next) = l.get((i + 1) % l.len()) else { continue };
|
||||
let gap = norm(sub(next.a, prev.b));
|
||||
if gap <= tol || gap > MAX_TRIM_GAP || gap > MAX_TRIM_GAP_SHARE * length(prev).min(length(next)) {
|
||||
continue;
|
||||
}
|
||||
let (a, b) = (ends.vertex(prev.b), ends.vertex(next.a));
|
||||
while parent.len() < ends.verts.len() {
|
||||
parent.push(parent.len());
|
||||
}
|
||||
let (ra, rb) = (root(&parent, a), root(&parent, b));
|
||||
if let Some(x) = parent.get_mut(ra) {
|
||||
*x = rb;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ends.verts.is_empty() {
|
||||
return;
|
||||
}
|
||||
// Each group's mean; a group that spreads wider than a gap may be is left alone.
|
||||
let mut sums: HashMap<usize, (V3, usize)> = HashMap::new();
|
||||
for (i, p) in ends.verts.iter().enumerate() {
|
||||
let e = sums.entry(root(&parent, i)).or_insert(([0.0; 3], 0));
|
||||
e.0 = add(e.0, *p);
|
||||
e.1 += 1;
|
||||
}
|
||||
let mean: HashMap<usize, V3> = sums.into_iter().map(|(r, (s, n))| (r, mul(s, 1.0 / n as f64))).collect();
|
||||
let mut to: Vec<Option<V3>> = ends.verts.iter().enumerate().map(|(i, _)| mean.get(&root(&parent, i)).copied()).collect();
|
||||
for (i, p) in ends.verts.iter().enumerate() {
|
||||
if to.get(i).copied().flatten().is_some_and(|m| norm(sub(m, *p)) > MAX_TRIM_GAP / 2.0) {
|
||||
let r = root(&parent, i);
|
||||
for (k, x) in to.iter_mut().enumerate() {
|
||||
if root(&parent, k) == r {
|
||||
*x = None;
|
||||
for (f, fe) in faces.iter().zip(&ends) {
|
||||
for (l, le) in f.loops.iter().zip(fe) {
|
||||
for (k, (prev, e)) in l.iter().zip(le).enumerate() {
|
||||
let (Some(next), Some(next_ends)) = (l.get((k + 1) % l.len()), le.get((k + 1) % le.len())) else { continue };
|
||||
let gap = norm(sub(next.a, prev.b));
|
||||
if gap > MAX_TRIM_GAP || gap > MAX_TRIM_GAP_SHARE * prev.length().min(next.length()) {
|
||||
continue;
|
||||
}
|
||||
let (a, b) = (root(&mut same, e[1]), root(&mut same, next_ends[0]));
|
||||
if let Some(slot) = same.get_mut(a.max(b)) {
|
||||
*slot = a.min(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut moved = 0.0f64;
|
||||
let mut snap = |p: &mut V3| {
|
||||
if let Some(m) = ends.find(*p).and_then(|i| to.get(i).copied().flatten()) {
|
||||
moved = moved.max(norm(sub(m, *p)));
|
||||
*p = m;
|
||||
// The piece ends at each joined vertex, and the vertex where most of them are.
|
||||
let mut at: HashMap<usize, Vec<V3>> = HashMap::new();
|
||||
for (f, fe) in faces.iter().zip(&ends) {
|
||||
for (l, le) in f.loops.iter().zip(fe) {
|
||||
for (s, [a, b]) in l.iter().zip(le) {
|
||||
at.entry(root(&mut same, *a)).or_default().push(s.a);
|
||||
at.entry(root(&mut same, *b)).or_default().push(s.b);
|
||||
}
|
||||
}
|
||||
};
|
||||
for s in faces.iter_mut().flat_map(|f| f.loops.iter_mut()).flat_map(|l| l.iter_mut()) {
|
||||
snap(&mut s.a);
|
||||
snap(&mut s.b);
|
||||
}
|
||||
t.snapped = t.snapped.max(moved);
|
||||
for (v, pts) in at {
|
||||
let agree = |p: V3| pts.iter().filter(|q| norm(sub(**q, p)) <= sw.tol).count();
|
||||
let Some(here) = sw.verts.get(v).copied() else { continue };
|
||||
let mut best = (agree(here), here);
|
||||
for p in &pts {
|
||||
let n = agree(*p);
|
||||
if n > best.0 {
|
||||
best = (n, *p);
|
||||
}
|
||||
}
|
||||
if let Some(slot) = sw.verts.get_mut(v) {
|
||||
*slot = best.1;
|
||||
}
|
||||
}
|
||||
faces
|
||||
.into_iter()
|
||||
.zip(ends)
|
||||
.map(|(f, ends)| {
|
||||
let loops = f
|
||||
.loops
|
||||
.iter()
|
||||
.zip(ends)
|
||||
.map(|(l, e)| {
|
||||
l.iter()
|
||||
.zip(e)
|
||||
.map(|(s, [a, b])| {
|
||||
let (a, b) = (root(&mut same, a), root(&mut same, b));
|
||||
let (pa, pb) = (sw.verts.get(a).copied().unwrap_or(s.a), sw.verts.get(b).copied().unwrap_or(s.b));
|
||||
let s = t.closed(s, pa, pb);
|
||||
sw.edge_between(&s, a, b)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
FaceB { surface: f.surface, sense: true, loops, color: f.color }
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// A trimmed (144) or bounded (143) surface, or a bare B-spline surface, as a face.
|
||||
@@ -1588,10 +1677,16 @@ fn cos_curve(t: &mut Tr, seq: i64, xf: &Xf, surface: i64) -> R<Vec<Seg>> {
|
||||
}
|
||||
|
||||
/// Pieces of a composite curve turned so that each starts where the one before ends, the first
|
||||
/// at the end nearer `start` (when known). A closed piece (its ends within `tol`, the sewing
|
||||
/// tolerance that decides they are one vertex) keeps its direction.
|
||||
/// at the end nearer `start` (when known), else at its end away from the second piece. A closed
|
||||
/// piece (its ends within `tol`, the sewing tolerance that decides they are one vertex) keeps
|
||||
/// its direction.
|
||||
fn head_to_tail(segs: Vec<Seg>, start: Option<V3>, tol: f64) -> Vec<Seg> {
|
||||
let mut prev = start;
|
||||
let from_second = || {
|
||||
let (f, n) = (segs.first()?, segs.get(1)?);
|
||||
let to_second = |p: V3| norm(sub(p, n.a)).min(norm(sub(p, n.b)));
|
||||
Some(if to_second(f.a) < to_second(f.b) { f.b } else { f.a })
|
||||
};
|
||||
let mut prev = start.or_else(from_second);
|
||||
segs.into_iter()
|
||||
.map(|s| {
|
||||
let s = match prev {
|
||||
|
||||
Reference in New Issue
Block a user