From ce902be66cc1c35606aa8eeec4ca2d92c41d602d Mon Sep 17 00:00:00 2001 From: bherbruck Date: Wed, 14 Oct 2026 00:28:09 -0400 Subject: [PATCH] Fillets and chamfers ending on a boss's pieced wall or beside it, on a face that runs along its seam twice, and loops the loop blend refuses (#926) Fillets and chamfers ending on a boss's pieced wall or beside it, on a face that runs along its seam twice, and loops the loop blend refuses - A straight edge whose end meets one curved surface in several faces (a cylinder a join left in pieces) ends on that surface: the section is cut where it crosses each seam, which is shortened there; where no seam is crossed (the wall missing inside the other body) the section lies on the bared surface and the rest of the side edge becomes a seam. - Two end faces that aren't both planes meeting in lines: each face takes the section's points on it, cut where it crosses the edge between them. Not where an end face runs on smoothly from a side (an earlier round): the corner build takes those. - Face boundaries close over several connector edges. - A face on a periodic surface running along its seam twice (as a STEP file gives it) is rebuilt the same way (#691). - A curved chain ending square on a face that runs on past its side extends that face's seam. - A smooth chain stops before an edge between faces that meet smoothly. - A loop the loop blend refuses for its shape is blended edge by edge; refusals about its size stand. - Action matrix: ACTION_MATRIX_ACTIONS and ACTION_MATRIX_VALIDITY for quick local runs; the fillets and chamfers that pass now recorded. Co-Authored-By: Claude Opus 5.5 --- apps/solidcraft-cli/tests/action_matrix.rs | 12 + crates/kernel/src/blend.rs | 598 +++++++++++++++++---- crates/kernel/src/curveblend.rs | 7 +- docs/action-matrix.md | 3 + tests/action_matrix/baseline.json | 50 +- 5 files changed, 537 insertions(+), 133 deletions(-) diff --git a/apps/solidcraft-cli/tests/action_matrix.rs b/apps/solidcraft-cli/tests/action_matrix.rs index 5ae35869..047d5076 100644 --- a/apps/solidcraft-cli/tests/action_matrix.rs +++ b/apps/solidcraft-cli/tests/action_matrix.rs @@ -639,7 +639,14 @@ fn action_matrix_worker() { } let list = actions(&s); let edit = driving_parameter(&s); + // Local runs only: `ACTION_MATRIX_ACTIONS=edge.fillet,edge.chamfer` tries just those actions + // (don't bless such a run), `ACTION_MATRIX_VALIDITY=1` adds each passing action's body check. + let only: Option> = std::env::var("ACTION_MATRIX_ACTIONS").ok().map(|v| v.split(',').map(str::to_string).collect()); + let validity = std::env::var_os("ACTION_MATRIX_VALIDITY").is_some(); for (i, a) in list.iter().enumerate().skip(from) { + if only.as_ref().is_some_and(|o| !o.iter().any(|x| x == a.action)) { + continue; + } // Whole families of pieces are sampled together (see [`family`]). let k = key(&design, &family(&a.entity), a.action); if !stable_hash(&k).is_multiple_of(sample) { @@ -650,6 +657,11 @@ fn action_matrix_worker() { let mut scratch = s.scratch(); let r = perform(&mut scratch, &a.act, &before); let mut line = outcome(&design, &a.entity, a.action, &r, t.elapsed().as_millis()); + if validity && r.is_ok() { + let st = scratch.model.state(); + let bad: Vec = st.bodies.iter().flat_map(|b| b.body.validity().into_iter().map(|v| format!("{}: {v}", b.name))).collect(); + line["validity"] = json!(bad); + } // What was sent, for reproducing a failure (kept in the results, not the baseline). line["command"] = match &a.act { Act::Run(id, p) => json!({"command": id, "params": p}), diff --git a/crates/kernel/src/blend.rs b/crates/kernel/src/blend.rs index 84d2b684..cd2d49f9 100644 --- a/crates/kernel/src/blend.rs +++ b/crates/kernel/src/blend.rs @@ -76,26 +76,73 @@ fn rebuild_face_dropping(f: &mt::Face, subst: &HashMap, dr let (Some(cur), Some(next)) = (edges.get(i), edges.get((i + 1) % n)) else { continue }; out.push(cur.clone()); if cur.back() != next.front() { - let c = connectors - .iter() - .find_map(|c| { - if c.front() == cur.back() && c.back() == next.front() { - Some(c.clone()) - } else if c.back() == cur.back() && c.front() == next.front() { - Some(c.inverse()) - } else { - None - } - }) - .ok_or_else(|| KernelError::Failed("blend: could not close a face boundary".into()))?; - out.push(c); + out.extend( + connect(cur.back(), next.front(), connectors) + .ok_or_else(|| KernelError::Failed("blend: could not close a face boundary".into()))?, + ); } } wires.push(mt::Wire::from(out)); } + // A face on a periodic surface running along its seam edge twice (as a STEP file gives it) + // keeps doing so: such a wire isn't simple, which is valid there. + let seam_twice = |ws: &[mt::Wire]| { + ws.iter().any(|w| { + let ids: Vec = w.edge_iter().map(|e| e.id()).collect(); + ids.iter().enumerate().any(|(k, x)| ids.iter().skip(k + 1).any(|y| y == x)) + }) + }; + let had_seam = seam_twice(f.absolute_boundaries()); + let closed = wires.iter().all(|w| w.is_closed()); + if had_seam && closed && seam_twice(&wires) && mt::Face::try_new(wires.clone(), f.surface()).is_err() { + let mut nf = mt::Face::new_unchecked(wires, f.surface()); + if !f.orientation() { + nf.invert(); + } + return Ok(nf); + } crate::heal::absolute_face(f, wires).ok_or_else(|| KernelError::Failed("blend: a face could not be rebuilt".into())) } +/// A loop blend's refusal that is about its size: too large, or using up only some of the walls. +fn size_refusal(e: &KernelError) -> bool { + match e { + KernelError::TooLarge(_) => true, + KernelError::Failed(m) => m.contains("uses up"), + _ => false, + } +} + +/// The shortest run of `connectors` (each used either way, at most four) from `a` to `b`. +fn connect(a: &mt::Vertex, b: &mt::Vertex, connectors: &[mt::Edge]) -> Option> { + let mut paths: Vec<(mt::Vertex, Vec, Vec)> = vec![(a.clone(), Vec::new(), Vec::new())]; + for _ in 0..4 { + let mut next = Vec::new(); + for (at, path, used) in &paths { + for (i, c) in connectors.iter().enumerate().filter(|(i, _)| !used.contains(i)) { + let step = if c.front() == at { + c.clone() + } else if c.back() == at { + c.inverse() + } else { + continue; + }; + let to = step.back().clone(); + let mut p = path.clone(); + p.push(step); + if &to == b { + return Some(p); + } + let mut u = used.clone(); + u.push(i); + next.push((to, p, u)); + } + } + paths = next; + } + None +} + /// Blend one edge of `solid`, with what was built from it (see [`Rebuild`]). fn blend_one(solid: &Solid, edge: &mt::Edge, size: f64, r: f64, shape: Shape) -> Result<(Solid, Rebuild)> { if !matches!(edge.curve(), mt::Curve::Line(_)) { @@ -235,6 +282,28 @@ fn line_surface_hits(surf: &mt::Surface, x: Vec3, d: Vec3, reach: f64, tol: f64) out } +/// Whether the faces `ids` are curved pieces of one surface: every piece's boundary lies on the +/// first one's surface (a cylinder a boolean left in pieces). +fn same_surface(faces: &[&mt::Face], ids: &[usize], tol: f64) -> bool { + use mt::{BoundedCurve, ParametricCurve, ParametricSurface, SearchNearestParameter}; + let Some(first) = ids.first().and_then(|i| faces.get(*i)) else { return false }; + if ids.iter().any(|i| faces.get(*i).is_none_or(|f| plane_normal(f).is_some())) { + return false; + } + let surf = first.surface(); + let on = |q: Vec3| { + // From the nearest of a few starts: a periodic surface has more than one local minimum. + surf.search_nearest_parameter(p3(q), None, 100).is_some_and(|(u, v)| from_p3(surf.subs(u, v)).dist(q) <= tol) + }; + ids.iter().skip(1).filter_map(|i| faces.get(*i)).all(|f| { + f.edge_iter().all(|e| { + let c = e.curve(); + let (t0, t1) = c.range_tuple(); + [0.0, 0.3, 0.7, 1.0].iter().all(|k| on(from_p3(c.subs(t0 + (t1 - t0) * k)))) + }) + }) +} + /// A circle through the curve of `e` (centre, axis, radius), when it is one. fn edge_circle(e: &mt::Edge, tol: f64) -> Option<(Vec3, Vec3, f64)> { use mt::{BoundedCurve, ParametricCurve}; @@ -355,8 +424,15 @@ fn blend_geometry( planes: Vec<(Vec3, Vec3)>, ca: Option, cb: Option, - /// Points of the end's section on a curved end face, from A to B. - cap: Option>, + /// Points of the end's section on a curved end face, from A to B: one run per face it + /// crosses (an end surface in pieces), split where it crosses their seams. + cap: Option>>, + /// The seams between pieces of the end surface at the vertex, where the section crosses + /// them (in its order from A to B). + seams: Vec<(mt::Edge, Vec3)>, + /// The section lies where the end surface was missing round the vertex: the side edge + /// `e2` is split at B, its piece from B to the vertex a seam of the end surface. + concave: bool, /// How far along the edge the section reaches from the end vertex. reach: f64, } @@ -369,8 +445,19 @@ fn blend_geometry( let (f, gf) = (faces.get(fi)?, faces.get(g)?); f.edge_iter().find(|e| e.id() != edge.id() && (e.front() == v || e.back() == v) && gf.edge_iter().any(|x| x.id() == e.id())) }; + // One curved surface in several faces round the vertex (a cylinder a boolean left in + // pieces meeting there): the end face is that surface, its pieces' seams cut back. + let one_surface = others.len() >= 2 && same_surface(faces, &others, (size * 1e-5).max(1e-6)); let (ga, gb) = match others[..] { [g] => (g, g), + _ if one_surface => { + let x = others.iter().copied().find(|g| shared(i1, *g).is_some()); + let y = others.iter().copied().find(|g| shared(i2, *g).is_some()); + match (x, y) { + (Some(x), Some(y)) => (x, y), + _ => return Err(unsupported("each end of the edge must touch exactly one more face")), + } + } [x, y] if shared(i1, x).is_some() && shared(i2, y).is_some() => (x, y), [x, y] if shared(i1, y).is_some() && shared(i2, x).is_some() => (y, x), _ => return Err(unsupported("each end of the edge must touch exactly one more face")), @@ -379,12 +466,28 @@ fn blend_geometry( // they reach its surface, and the blend's end is where its surface meets the end face // (the cross-section through `p` slid along d onto it, point by point). let is_line = |e: &mt::Edge| matches!(e.curve(), mt::Curve::Line(_)); - if ga == gb + // Two end faces, not both planes meeting the sides and each other in lines (a sheet metal + // rim edge ending where a bend meets the next panel): the same, each face taking the + // section's points on it, cut where it crosses the edge between them. + let two_curved = !one_surface && ga != gb && { + let curved = |g: usize| faces.get(g).is_none_or(|f| plane_normal(f).is_none()); + let e3 = shared(ga, gb); + curved(ga) || curved(gb) || [shared(i1, ga), shared(i2, gb), e3].iter().any(|e| e.as_ref().is_none_or(|e| !is_line(e))) + }; + // Not where an end face runs on smoothly from a side (an earlier round): the corner build + // takes those ends. + let tangent = others.iter().any(|g| { + let n = |i: usize| faces.get(i).and_then(|f| crate::curveblend::project(f, p, None)).map(|x| x.1); + n(*g).is_some_and(|ng| [i1, i2].iter().filter_map(|i| n(*i)).any(|ns| ns.dot(ng) > 1.0 - 1e-6)) + }); + let multi = (one_surface || two_curved) && !tangent; + if (ga == gb || multi) && let (Some(e1), Some(e2)) = (shared(i1, ga), shared(i2, gb)) - && (faces.get(ga).and_then(|f| plane_normal(f)).is_none() || !is_line(&e1) || !is_line(&e2)) + && (multi || faces.get(ga).and_then(|f| plane_normal(f)).is_none() || !is_line(&e1) || !is_line(&e2)) { let g = faces.get(ga).ok_or_else(|| KernelError::Failed("face".into()))?; let surf = g.surface(); + let surf_b = faces.get(gb).ok_or_else(|| KernelError::Failed("face".into()))?.surface(); let on_tol = (size * 1e-5).max(1e-6); let reach = 3.0 * (s + r); let (sa, sb) = (p + t1 * s, p + t2 * s); @@ -397,8 +500,8 @@ fn blend_geometry( } }; // A rail's end: on the side edge, else on its continuation within the side face. - let rail_end = |x: Vec3, e: &mt::Edge, fi: usize| -> Result<(Vec3, f64)> { - let hits = line_surface_hits(&surf, x, d, reach, tol); + let rail_end = |x: Vec3, e: &mt::Edge, fi: usize, surf: &mt::Surface| -> Result<(Vec3, f64)> { + let hits = line_surface_hits(surf, x, d, reach, tol); if let Some(l) = hits.iter().find(|l| on_edge(e, x + d * **l, on_tol).is_some()) { return Ok((x + d * *l, *l)); } @@ -408,66 +511,196 @@ fn blend_geometry( .map(|l| (x + d * *l, *l)) .ok_or_else(|| too_large("the blend is larger than the neighbouring faces")) }; - let ((a, la), (b, lb)) = (rail_end(sa, &e1, i1)?, rail_end(sb, &e2, i2)?); - let flat_plane = shape == Shape::Flat && plane_normal(g).is_some(); - let mut cap: Option> = None; - let mut far = la.abs().max(lb.abs()); - for n in [16usize, 32, 64] { - if flat_plane { - cap = Some(vec![a, b]); - break; - } - // The section's points on the end face, each continuing from the last. - let mut pts = vec![a]; - let mut last = la; - let mut ok = true; - for i in 1..n { - let x = profile(i as f64 / n as f64); - match line_surface_hits(&surf, x, d, reach, tol).into_iter().min_by(|u, v| (u - last).abs().total_cmp(&(v - last).abs())) { - Some(l) => { - pts.push(x + d * l); - last = l; - far = far.max(l.abs()); - } - None => { - ok = false; - break; - } + let ((a, la), (b, lb)) = (rail_end(sa, &e1, i1, &surf)?, rail_end(sb, &e2, i2, &surf_b)?); + let flat_plane = shape == Shape::Flat && plane_normal(g).is_some() && !multi; + // Where a point of the section lies across it: 0 at A's side, 1 at B's. + let perp = |x: Vec3| x - d * (x - p).dot(d); + let theta_ab = { + let (u, w) = (sa - c0, sb - c0); + (u.dot(w) / (u.len() * w.len()).max(1e-300)).clamp(-1.0, 1.0).acos() + }; + let fraction = |x: Vec3| -> f64 { + let x = perp(x); + match shape { + Shape::Flat => (x - sa).dot(sb - sa) / (sb - sa).len2().max(1e-300), + Shape::Round => { + let (u, w) = (sa - c0, x - c0); + let ang = (u.dot(w) / (u.len() * w.len()).max(1e-300)).clamp(-1.0, 1.0).acos(); + ang / theta_ab.max(1e-300) } } - if !ok { - break; - } - pts.push(b); - // The fitted curve must stay on both surfaces between the points. - let Some(bs) = crate::build::interpolate_cubic(&pts) else { break }; - let fits = (0..n).all(|i| { - use mt::{ParametricCurve, ParametricSurface, SearchNearestParameter}; - let q = from_p3(bs.subs((i as f64 + 0.5) / n as f64)); - let on_g = surf.search_nearest_parameter(p3(q), None, 100).is_some_and(|(u, v)| from_p3(surf.subs(u, v)).dist(q) <= on_tol); - let on_blend = match shape { - Shape::Round => { - let w = q - c0; - ((w - d * w.dot(d)).len() - r).abs() <= on_tol + }; + // The seams between the end surface's pieces at the vertex, and where the blend's + // surface crosses each (walking out from the vertex to the first crossing). + let mut seams: Vec<(mt::Edge, Vec3, f64)> = Vec::new(); + let mut concave = false; + if multi { + let gap = |x: Vec3| -> f64 { + match shape { + Shape::Round => (perp(x) - c0).len() - r, + Shape::Flat => (sb - sa).cross(d).normalized().map_or(f64::NAN, |m| (x - sa).dot(m)), + } + }; + let mut seen: Vec = vec![edge.id(), e1.id(), e2.id()]; + let mut uncrossed = 0usize; + for gi in &others { + let Some(gf) = faces.get(*gi) else { continue }; + for e in gf.edge_iter() { + if seen.contains(&e.id()) || !(e.front() == v || e.back() == v) { + continue; } - Shape::Flat => (sb - sa).cross(d).normalized().is_some_and(|m| (q - sa).dot(m).abs() <= on_tol), - }; - on_g && on_blend - }); - if fits { - cap = Some(pts); - break; + seen.push(e.id()); + let owners: Vec = + (0..faces.len()).filter(|k| faces.get(*k).is_some_and(|f| f.edge_iter().any(|x| x.id() == e.id()))).collect(); + if !owners.iter().all(|k| others.contains(k)) { + return Err(unsupported("each end of the edge must touch exactly one more face")); + } + use mt::{BoundedCurve, ParametricCurve}; + let c = e.curve(); + let (t0, t1) = c.range_tuple(); + let from_front = e.absolute_front() == v; + let at = |k: f64| from_p3(c.subs(if from_front { t0 + (t1 - t0) * k } else { t1 - (t1 - t0) * k })); + let mut prev = (0.0, gap(at(0.0))); + let mut hit = None; + for i in 1..=400 { + let k = f64::from(i) / 400.0; + let x = at(k); + let gk = gap(x); + if !gk.is_finite() || x.dist(p) > 2.0 * reach { + break; + } + if (gk > 0.0) != (prev.1 > 0.0) { + let (mut lo, mut hi) = (prev.0, k); + for _ in 0..60 { + let mid = 0.5 * (lo + hi); + if (gap(at(mid)) > 0.0) == (prev.1 > 0.0) { + lo = mid; + } else { + hi = mid; + } + } + hit = Some(at(0.5 * (lo + hi))); + break; + } + prev = (k, gk); + } + let Some(q) = hit else { + uncrossed += 1; + continue; + }; + let f = fraction(q); + if !(f > 1e-6 && f < 1.0 - 1e-6) { + return Err(unsupported("the blend's end across the end face's seams")); + } + seams.push((e.absolute_clone(), q, f)); + } } + seams.sort_by(|x, y| x.2.total_cmp(&y.2)); + // No seam crossed: the surface turns back round the vertex (a boss joined beside + // the edge, its wall missing where the other body was). The section lies where + // that wall was, now laid bare: one piece takes it, and the rest of the edge + // from B to the vertex is a seam between them. + if uncrossed > 0 && (!seams.is_empty() || ga == gb || !one_surface) { + return Err(too_large("the blend is larger than the neighbouring faces")); + } + concave = uncrossed > 0; + } + // Runs of the section between A, the seam crossings and B, each on the end surface. + let mut stops: Vec<(f64, Vec3, f64)> = vec![(0.0, a, la)]; + stops.extend(seams.iter().map(|(_, q, f)| (*f, *q, (*q - p).dot(d)))); + stops.push((1.0, b, lb)); + let mut far = la.abs().max(lb.abs()); + // The face each run lies in: the one holding the edges at both its ends. + let bounds: Vec = std::iter::once(e1.id()).chain(seams.iter().map(|x| x.0.id())).chain(std::iter::once(e2.id())).collect(); + let mut run_surfaces: Vec = Vec::new(); + for w in bounds.windows(2) { + let [x, y] = w else { continue }; + let fi = if one_surface || ga == gb { + Some(ga) + } else { + others + .iter() + .copied() + .find(|g| faces.get(*g).is_some_and(|f| f.edge_iter().any(|e| e.id() == *x) && f.edge_iter().any(|e| e.id() == *y))) + }; + run_surfaces.push(fi.and_then(|g| faces.get(g)).ok_or_else(|| unsupported("the blend's end across the end faces"))?.surface()); + } + let mut runs: Vec> = Vec::new(); + for (pair, surf) in stops.windows(2).zip(&run_surfaces) { + let [(f0, x0, l0), (f1, x1, _)] = pair else { continue }; + if flat_plane { + runs.push(vec![*x0, *x1]); + continue; + } + let mut found = None; + for n in [16usize, 32, 64] { + // The section's points on the end face, each continuing from the last. + let mut pts = vec![*x0]; + let mut last = *l0; + let mut ok = true; + for i in 1..n { + let x = profile(f0 + (f1 - f0) * i as f64 / n as f64); + match line_surface_hits(surf, x, d, reach, tol).into_iter().min_by(|u, v| (u - last).abs().total_cmp(&(v - last).abs())) { + Some(l) => { + pts.push(x + d * l); + last = l; + far = far.max(l.abs()); + } + None => { + ok = false; + break; + } + } + } + if !ok { + break; + } + pts.push(*x1); + // The fitted curve must stay on both surfaces between the points. + let Some(bs) = crate::build::interpolate_cubic(&pts) else { break }; + let fits = (0..n).all(|i| { + use mt::{ParametricCurve, ParametricSurface, SearchNearestParameter}; + let q = from_p3(bs.subs((i as f64 + 0.5) / n as f64)); + let on_g = surf.search_nearest_parameter(p3(q), None, 100).is_some_and(|(u, v)| from_p3(surf.subs(u, v)).dist(q) <= on_tol); + let on_blend = match shape { + Shape::Round => { + let w = q - c0; + ((w - d * w.dot(d)).len() - r).abs() <= on_tol + } + Shape::Flat => (sb - sa).cross(d).normalized().is_some_and(|m| (q - sa).dot(m).abs() <= on_tol), + }; + on_g && on_blend + }); + if fits { + found = Some(pts); + break; + } + } + runs.push(found.ok_or_else(|| unsupported("the blend's end on a curved face"))?); } - let cap = cap.ok_or_else(|| unsupported("the blend's end on a curved face"))?; // A rail that ends at the far end of its side edge uses that edge up (an earlier round's // end arc, when the new blend meets it exactly where that round meets the next face). let far_end = |e: &mt::Edge, q: Vec3| { let f = if e.front() == v { e.back() } else { e.front() }; - (vtx(f).dist(q) <= on_tol).then(|| f.clone()) + (!multi && vtx(f).dist(q) <= on_tol).then(|| f.clone()) }; let (ca, cb) = (far_end(&e1, a), far_end(&e2, b)); - ends.push(End { v: v.clone(), g: vec![ga], e1, e2, e3: None, a, b, planes: Vec::new(), ca, cb, cap: Some(cap), reach: far }); + ends.push(End { + v: v.clone(), + g: if multi { others.clone() } else { vec![ga] }, + e1, + e2, + e3: None, + a, + b, + planes: Vec::new(), + ca, + cb, + cap: Some(runs), + seams: seams.into_iter().map(|(e, q, _)| (e, q)).collect(), + concave, + reach: far, + }); continue; } let normal = |g: usize| -> Result { @@ -551,6 +784,8 @@ fn blend_geometry( ca: None, cb: None, cap: None, + seams: Vec::new(), + concave: false, reach: 0.0, }); continue; @@ -574,7 +809,22 @@ fn blend_geometry( }; let planes = if ga == gb { vec![(p, na)] } else { vec![(p, na), (p, nb)] }; let [ca, cb] = consumed; - ends.push(End { v: v.clone(), g: if ga == gb { vec![ga] } else { vec![ga, gb] }, e1, e2, e3, a, b, planes, ca, cb, cap: None, reach: 0.0 }); + ends.push(End { + v: v.clone(), + g: if ga == gb { vec![ga] } else { vec![ga, gb] }, + e1, + e2, + e3, + a, + b, + planes, + ca, + cb, + cap: None, + seams: Vec::new(), + concave: false, + reach: 0.0, + }); } let [end0, end1] = &ends[..] else { return Err(KernelError::Failed("ends".into())) }; if end0.g.iter().any(|g| end1.g.contains(g)) { @@ -629,20 +879,33 @@ fn blend_geometry( mt::Transformed::transform_by(&mut curve, mat); Ok(mt::Edge::new(va, vb, curve)) }; - let mut corners: Vec<(mt::Vertex, mt::Vertex, Vec, Option)> = Vec::new(); + #[allow(clippy::type_complexity)] + let mut corners: Vec<(mt::Vertex, mt::Vertex, Vec, Option, Vec, Vec)> = Vec::new(); + let on_tol = (size * 1e-5).max(1e-6); for (end, p) in [(end0, p0), (end1, p1)] { let (va, vb) = (vertex(&end.ca, end.a), vertex(&end.cb, end.b)); let (sa, sb) = (p + t1 * s, p + t2 * s); + let mut seam_vs: Vec = Vec::new(); let (chain, vq) = match (&end.e3, &end.planes[..]) { _ if end.cap.is_some() => { - let pts = end.cap.as_deref().unwrap_or_default(); - let curve = if pts.len() == 2 { - builder::line(&va, &vb) - } else { - let bs = crate::build::interpolate_cubic(pts).ok_or_else(|| unsupported("the blend's end on a curved face"))?; - mt::Edge::try_new(&va, &vb, mt::Curve::BSplineCurve(bs)).map_err(|e| KernelError::Failed(format!("blend end: {e}")))? - }; - (vec![curve], None) + // A run from each stop to the next: A, the seam crossings, B. + seam_vs = end.seams.iter().map(|(_, q)| builder::vertex(p3(*q))).collect(); + let stops: Vec<&mt::Vertex> = std::iter::once(&va).chain(seam_vs.iter()).chain(std::iter::once(&vb)).collect(); + let runs = end.cap.as_deref().unwrap_or_default(); + if runs.len() + 1 != stops.len() { + return Err(KernelError::Failed("blend end".into())); + } + let mut chain = Vec::with_capacity(runs.len()); + for (k, pts) in runs.iter().enumerate() { + let (Some(x), Some(y)) = (stops.get(k), stops.get(k + 1)) else { return Err(KernelError::Failed("blend end".into())) }; + chain.push(if pts.len() == 2 { + builder::line(x, y) + } else { + let bs = crate::build::interpolate_cubic(pts).ok_or_else(|| unsupported("the blend's end on a curved face"))?; + mt::Edge::try_new(x, y, mt::Curve::BSplineCurve(bs)).map_err(|e| KernelError::Failed(format!("blend end: {e}")))? + }); + } + (chain, None) } (None, [plane]) => (vec![section(p, &va, &vb, sa, sb, *plane)?], None), (Some((_, q)), [pa, pb]) => { @@ -652,9 +915,17 @@ fn blend_geometry( } _ => return Err(KernelError::Failed("blend end".into())), }; - corners.push((va, vb, chain, vq)); + // The rest of e2, from B to the vertex: a seam between the end surface's pieces. + let mut extra = Vec::new(); + if end.concave { + let far = if end.e2.absolute_front() == &end.v { end.e2.absolute_back().clone() } else { end.e2.absolute_front().clone() }; + extra.push(retarget(&end.e2, &far, &vb, on_tol)?); + } + corners.push((va, vb, chain, vq, seam_vs, extra)); } - let [(a0, b0, c0, q0), (a1, b1, c1, q1)] = &corners[..] else { return Err(KernelError::Failed("ends".into())) }; + let [(a0, b0, c0, q0, s0, x0), (a1, b1, c1, q1, s1, x1)] = &corners[..] else { return Err(KernelError::Failed("ends".into())) }; + let link0: Vec = c0.iter().chain(x0).cloned().collect(); + let link1: Vec = c1.iter().chain(x1).cloned().collect(); let la = rail_a.clone().unwrap_or_else(|| builder::line(a0, a1)); let lb = rail_b.clone().unwrap_or_else(|| builder::line(b0, b1)); @@ -662,9 +933,12 @@ fn blend_geometry( // between two end faces stops where the blend crosses it; the blended edge moves to A or B. let mut subst: HashMap = HashMap::new(); let mut dropped: Vec = Vec::new(); - let on_tol = (size * 1e-5).max(1e-6); let shorten = |e: &mt::Edge, v: &mt::Vertex, nv: &mt::Vertex| retarget(e, v, nv, on_tol); - for (end, a, b, q) in [(end0, a0, b0, q0), (end1, a1, b1, q1)] { + for (end, a, b, q, sv) in [(end0, a0, b0, q0, s0), (end1, a1, b1, q1, s1)] { + // Seams between the end surface's pieces stop where the blend's end crosses them. + for ((e, _), nv) in end.seams.iter().zip(sv) { + subst.insert(e.id(), shorten(e, &end.v, nv)?); + } for (e, nv, used) in [(&end.e1, a, &end.ca), (&end.e2, b, &end.cb)] { if used.is_some() { dropped.push(e.id()); @@ -693,9 +967,9 @@ fn blend_geometry( m.insert(edge.id(), lb.clone()); rebuild_face_dropping(f, &m, &dropped, &[])? } else if end0.g.contains(&i) { - rebuild_face_dropping(f, &subst, &dropped, c0)? + rebuild_face_dropping(f, &subst, &dropped, &link0)? } else if end1.g.contains(&i) { - rebuild_face_dropping(f, &subst, &dropped, c1)? + rebuild_face_dropping(f, &subst, &dropped, &link1)? } else if f.edge_iter().any(|e| subst.contains_key(&e.id())) { rebuild_face(f, &subst, &[])? } else { @@ -865,21 +1139,38 @@ fn blend_in_order(body: &Body, edges: &[Vec3], r: f64, shape: Shape, what: &str) cur = record::after(blend_in_order(&cur, &rest, r, shape, what)?, &cur, &counts); return Ok(record::after(blend_in_order(&cur, &loop_pts, r, shape, what)?, &cur, &counts)); } + // A loop the loop blend refuses for its shape (seams that aren't straight, walls it can't + // trim) is blended edge by edge below instead: a ball rolls along its smooth chain. if let Some(groups) = loop_groups(&cur, edges)? { - for g in groups { - let size = cur.size(); - let solid = cur.deep_copy(); - let ids = edge_ids(&cur, &solid, &g)?; - match crate::loopblend::loop_blend(&solid, &ids, size, r, shape == Shape::Round) { - Some(res) => { - let (made, rec) = guard(what, || res)?; - cur = record::after(record::rebuilt(made, &[&solid], &rec)?, &cur, &counts); + let whole = || -> Result { + let mut lc = cur.clone(); + for g in groups { + let size = lc.size(); + let solid = lc.deep_copy(); + let ids = edge_ids(&lc, &solid, &g)?; + match crate::loopblend::loop_blend(&solid, &ids, size, r, shape == Shape::Round) { + Some(res) => { + let (made, rec) = guard(what, || res)?; + lc = record::after(record::rebuilt(made, &[&solid], &rec)?, &lc, &counts); + } + None => return Err(unsupported("the edges are not a whole loop of a planar face")), } - None => return Err(unsupported("the edges are not a whole loop of a planar face")), } + Ok(lc) + }; + match whole() { + Ok(b) => return Ok(b), + // Too large for the loop (or using up some of its walls) is so edge by edge too. + Err(e) if size_refusal(&e) => return Err(e), + Err(_) => {} } - return Ok(cur); } + blend_each(cur, edges, r, shape, what, &counts) +} + +/// The edges blended one at a time (see [`blend`]), `cur` being the healed body. +fn blend_each(mut cur: Body, edges: &[Vec3], r: f64, shape: Shape, what: &str, counts: &[(usize, usize)]) -> Result { + let counts = counts.to_vec(); // One at a time, in whatever order works: a run along a planar face's loop (bridging arcs // that earlier blends left between selected edges, so corners round off), a single edge, // or a chain of curved edges. @@ -1188,7 +1479,7 @@ fn smooth_chain(solid: &Solid, edge: &mt::Edge) -> Option> { let next = all.iter().filter(|e| !ids.contains(&e.id()) && (*e.front() == v || *e.back() == v)).find_map(|e| { let fwd = *e.front() == v; let t = if fwd { dir(e, false)? } else { -dir(e, true)? }; - (t.dot(d) > 1.0 - 1e-4).then(|| (e.clone(), fwd)) + (t.dot(d) > 1.0 - 1e-4 && !smooth_seam(solid, e)).then(|| (e.clone(), fwd)) }); let Some((e, fwd)) = next else { return Some(false) }; ids.push(e.id()); @@ -1209,6 +1500,18 @@ fn smooth_chain(solid: &Solid, edge: &mt::Edge) -> Option> { Some(ids) } +/// Whether the faces on either side of `e` meet smoothly there (a seam between pieces of one +/// surface, or a round's tangent edge): nothing to blend, so a chain stops before it. +fn smooth_seam(solid: &Solid, e: &mt::Edge) -> bool { + let Some(m) = edge_mid(e) else { return false }; + let normals: Vec = solid + .face_iter() + .filter(|f| f.edge_iter().any(|x| x.id() == e.id())) + .filter_map(|f| crate::curveblend::project(f, m, None).map(|x| x.1)) + .collect(); + matches!(normals[..], [a, b] if a.dot(b) > 1.0 - 1e-6) +} + pub(crate) fn edge_mid(e: &mt::Edge) -> Option { use mt::{BoundedCurve, ParametricCurve}; let c = e.curve(); @@ -1537,19 +1840,29 @@ pub fn chamfer(body: &Body, edges: &[Vec3], distance: f64) -> Result { // Coplanar neighbours as one face (as after a join), so corners look as they should. let mut cur = crate::boolean_record::healed(body)?; if let Some(groups) = loop_groups(&cur, edges)? { - for g in groups { - let size = cur.size(); - let solid = cur.deep_copy(); - let ids = edge_ids(&cur, &solid, &g)?; - match crate::loopblend::loop_blend(&solid, &ids, size, distance, false) { - Some(res) => { - let (made, rec) = guard("chamfer", || res)?; - cur = record::after(record::rebuilt(made, &[&solid], &rec)?, &cur, &counts); + let whole = || -> Result { + let mut lc = cur.clone(); + for g in groups { + let size = lc.size(); + let solid = lc.deep_copy(); + let ids = edge_ids(&lc, &solid, &g)?; + match crate::loopblend::loop_blend(&solid, &ids, size, distance, false) { + Some(res) => { + let (made, rec) = guard("chamfer", || res)?; + lc = record::after(record::rebuilt(made, &[&solid], &rec)?, &lc, &counts); + } + None => return Err(unsupported("the edges are not a whole loop of a planar face")), } - None => return Err(unsupported("the edges are not a whole loop of a planar face")), } + Ok(lc) + }; + // Refused for its shape, the loop is chamfered edge by edge below ([`blend`] falls back + // the same way); too large stays too large. + match whole() { + Ok(b) => return Ok(b), + Err(e) if size_refusal(&e) => return Err(e), + Err(_) => {} } - return Ok(cur); } for p in edges { let step = match chamfer_tool(&cur, *p, distance, 0).and_then(|t| cut_one(&cur, &t)) { @@ -1862,6 +2175,81 @@ mod tests { } } + #[test] + fn a_blend_ends_where_a_joined_boss_turns_back_round_its_end() { + // The box's top right edge runs into the cylinder at (40, 8, 20), where the cylinder's + // wall is in pieces (inside the box it is gone): the blend's end lies on the cylinder + // where the box was, now laid bare. + let body = box_and_boss(); + let vol = |b: &Body| b.tessellate(1e-4).unwrap().measure().volume; + let v0 = vol(&body); + for (shape, area) in [(Shape::Round, (1.0 - std::f64::consts::PI / 4.0) * 0.64), (Shape::Flat, 0.32)] { + let out = blend(&body, &[Vec3::new(40.0, 4.0, 20.0)], 0.8, shape, "blend").unwrap(); + assert!(out.validity().is_empty(), "{:?}", out.validity()); + assert_eq!(out.lumps().unwrap().len(), 1); + // Along y = 0 … 8 (and on a hair's breadth, to the cylinder's wall: y − 8 < 0.03). + let removed = v0 - vol(&out); + assert!((removed - area * 8.0).abs() < area * 8.0 * 1e-3, "{removed} vs {}", area * 8.0); + } + } + + #[test] + fn a_blend_along_a_boss_ends_on_the_box_side_beside_it() { + // The boss's bottom edge outside the box (a half circle) ends at the box's side, square to + // it: the side face takes the end section, its bottom edge running on to the blend. + let body = box_and_boss(); + let vol = |b: &Body| b.tessellate(1e-4).unwrap().measure().volume; + let v0 = vol(&body); + for (shape, area, c) in [ + ( + Shape::Round, + (1.0 - std::f64::consts::PI / 4.0) * 0.64, + 0.8 * (10.0 - 3.0 * std::f64::consts::PI) / (3.0 * (4.0 - std::f64::consts::PI)), + ), + (Shape::Flat, 0.32, 0.8 / 3.0), + ] { + let out = blend(&body, &[Vec3::new(52.0, 20.0, 0.0)], 0.8, shape, "blend").unwrap(); + assert!(out.validity().is_empty(), "{:?}", out.validity()); + // Pappus: the section turned half round the axis at its centroid's radius. + let want = area * std::f64::consts::PI * (12.0 - c); + let removed = v0 - vol(&out); + assert!((removed - want).abs() < want * 2e-3, "{removed} vs {want}"); + } + } + + #[test] + fn a_blend_ends_on_a_face_running_along_its_seam_twice() { + // #691: a pocket's floor edge along its straight wall ends on a boss's cylinder, which a + // STEP file gives as one face running along its seam edge twice. The rebuilt face keeps + // doing so. + let b = crate::build::box_solid(Vec3::new(-55.0, -20.0, -30.0), Vec3::new(55.0, 50.0, 0.0)).unwrap(); + let boss = crate::build::cylinder(Vec3::new(55.0, 15.0, -50.0), Vec3::Z, 25.0, 50.0).unwrap(); + let b = crate::ops::boolean(&b, &boss, crate::BoolOp::Union).unwrap().unwrap(); + let pocket = crate::build::box_solid(Vec3::new(-45.0, -21.0, -15.0), Vec3::new(55.0, 10.0, 1.0)).unwrap(); + let wall = crate::build::cylinder(Vec3::new(55.0, 15.0, -20.0), Vec3::Z, 25.0, 30.0).unwrap(); + let pocket = crate::ops::boolean(&pocket, &wall, crate::BoolOp::Cut).unwrap().unwrap(); + let made = crate::ops::boolean(&b, &pocket, crate::BoolOp::Cut).unwrap().unwrap(); + let body = crate::step_import(&crate::step_export(&[&made], "test").unwrap()).unwrap().bodies.remove(0).body; + let seam_twice = body.solid.face_iter().any(|f| { + f.absolute_boundaries().iter().any(|w| { + let ids: Vec = w.edge_iter().map(|e| e.id()).collect(); + ids.iter().enumerate().any(|(k, x)| ids.iter().skip(k + 1).any(|y| y == x)) + }) + }); + assert!(seam_twice); + let vol = |b: &Body| b.tessellate(1e-4).unwrap().measure().volume; + let v0 = vol(&body); + // The edge runs from x = −45 to where the wall meets the cylinder, x = 55 − √(25² − 5²). + let len = 45.0 + 55.0 - (600f64).sqrt(); + for (shape, area) in [(Shape::Round, 1.0 - std::f64::consts::PI / 4.0), (Shape::Flat, 0.5)] { + let out = blend(&body, &[Vec3::new(0.0, 10.0, -15.0)], 1.0, shape, "blend").unwrap(); + assert!(out.validity().is_empty(), "{:?}", out.validity()); + // A concave edge: the blend adds material. + let added = vol(&out) - v0; + assert!((added - area * len).abs() < area * len * 0.01, "{added} vs {}", area * len); + } + } + #[test] fn local_chamfer_intersects_sloped_end_planes() { for slope in [-0.5, 0.5] { diff --git a/crates/kernel/src/curveblend.rs b/crates/kernel/src/curveblend.rs index 2a05786a..7d7bd27e 100644 --- a/crates/kernel/src/curveblend.rs +++ b/crates/kernel/src/curveblend.rs @@ -1187,15 +1187,16 @@ fn curve_blend_with_limit(solid: &Solid, ids: &[mt::EdgeID], size: f64, r: f64, if consumed.contains_key(&id) { continue; } - // At a slanted end the blend may stop past the seam's old end: the seam then runs - // on along its line or circle, within its planar face. + // At an end of an open chain the blend may stop past the seam's old end (a slanted end + // face, or one turning back past the side, as a box side beside a boss joined to it): + // the seam then runs on along its line or circle, within its planar face. let one_end = match (&a, &b) { (Some(nv), None) => Some((seam.front().clone(), nv.clone())), (None, Some(nv)) => Some((seam.back().clone(), nv.clone())), _ => None, }; if let Some((old, nv)) = one_end - && slanted.iter().any(|(k, ..)| verts.get(*k) == Some(&old)) + && end_faces.iter().any(|(k, _)| verts.get(*k) == Some(&old)) { let on_tol = (size * 1e-5).max(1e-6); let q = from_p3(nv.point()); diff --git a/docs/action-matrix.md b/docs/action-matrix.md index 39d7fdb0..27ea4f9c 100644 --- a/docs/action-matrix.md +++ b/docs/action-matrix.md @@ -18,6 +18,9 @@ The test is [`apps/solidcraft-cli/tests/action_matrix.rs`](../apps/solidcraft-cl `--results FILE` to keep every outcome with its message and the command that was sent. `ACTION_MATRIX_FILTER=oracle/23` runs only the designs whose name contains it; `ACTION_MATRIX_JOBS` sets how many designs run at once. + For a quick local look, `ACTION_MATRIX_ACTIONS=edge.fillet,edge.chamfer` tries only those actions (never + bless such a run) and `ACTION_MATRIX_VALIDITY=1` adds each passing action's body check (`Body::validity`) + to the results. Each design runs in its own process, so an action that hangs (90 s without a result) or aborts is recorded as `timeout` or `crash` and the rest of the design still runs. Loading a design, before diff --git a/tests/action_matrix/baseline.json b/tests/action_matrix/baseline.json index 5dc80c33..93d3da5d 100644 --- a/tests/action_matrix/baseline.json +++ b/tests/action_matrix/baseline.json @@ -2251,16 +2251,16 @@ "Body1/F14:side:l8|F2:start": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project edge.project+edit", "Body1/F14:side:l8|F4:inner:F2:start#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1]#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]#[F14:side:l5,F14:side:l5@F15.1,F14:side:l5@F15.2,F14:side:l5@F15.3,F14:side:l5@F15.4,F14:side:l6,F14:side:l6@F15.1,F14:side:l6@F15.2,F14:side:l6@F15.3,F14:side:l6@F15.4,F14:side:l7,F14:side:l7@F15.1,F14:side:l7@F15.2,F14:side:l7@F15.3,F14:side:l7@F15.4,F14:side:l8,F14:side:l8@F15.1,F14:side:l8@F15.2,F14:side:l8@F15.3,F14:side:l8@F15.4,F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l65,F17:side:l66,F17:side:l67,F17:side:l68,F17:side:l69,F17:side:l70,F17:side:l71,F17:side:l72,F17:side:l76,F17:side:l77,F17:side:l78,F17:side:l79,F17:side:l80,F17:side:l81,F17:side:l82,F17:side:l83,F17:side:l84,F17:side:l85,F17:side:l86,F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project edge.project+edit", "Body1/F17:bottom#[F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k13,F17:side:k14,F17:side:k15,F17:side:k16,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l62,F17:side:l63,F17:side:l64]": "face.angle_plane !face.draft_neutral !face.extrude !face.hole face.midplane face.offset_plane !face.press_pull !face.shell face.sketch", - "Body1/F17:bottom#[F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k13,F17:side:k14,F17:side:k15,F17:side:k16,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l62,F17:side:l63,F17:side:l64]|F17:side:k10": "!edge.chamfer !edge.fillet edge.normal_plane edge.project", - "Body1/F17:bottom#[F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k13,F17:side:k14,F17:side:k15,F17:side:k16,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l62,F17:side:l63,F17:side:l64]|F17:side:k11": "!edge.chamfer !edge.fillet edge.normal_plane edge.project", - "Body1/F17:bottom#[F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k13,F17:side:k14,F17:side:k15,F17:side:k16,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l62,F17:side:l63,F17:side:l64]|F17:side:k12": "!edge.chamfer !edge.fillet edge.normal_plane edge.project", + "Body1/F17:bottom#[F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k13,F17:side:k14,F17:side:k15,F17:side:k16,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l62,F17:side:l63,F17:side:l64]|F17:side:k10": "edge.chamfer edge.fillet edge.normal_plane edge.project", + "Body1/F17:bottom#[F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k13,F17:side:k14,F17:side:k15,F17:side:k16,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l62,F17:side:l63,F17:side:l64]|F17:side:k11": "edge.chamfer edge.fillet edge.normal_plane edge.project", + "Body1/F17:bottom#[F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k13,F17:side:k14,F17:side:k15,F17:side:k16,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l62,F17:side:l63,F17:side:l64]|F17:side:k12": "edge.chamfer edge.fillet edge.normal_plane edge.project", "Body1/F17:bottom#[F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k13,F17:side:k14,F17:side:k15,F17:side:k16,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l62,F17:side:l63,F17:side:l64]|F17:side:k13": "!edge.chamfer !edge.fillet edge.normal_plane edge.project", "Body1/F17:bottom#[F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k13,F17:side:k14,F17:side:k15,F17:side:k16,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l62,F17:side:l63,F17:side:l64]|F17:side:k14": "!edge.chamfer !edge.fillet edge.normal_plane edge.project", "Body1/F17:bottom#[F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k13,F17:side:k14,F17:side:k15,F17:side:k16,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l62,F17:side:l63,F17:side:l64]|F17:side:k15": "!edge.chamfer !edge.fillet edge.normal_plane edge.project", "Body1/F17:bottom#[F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k13,F17:side:k14,F17:side:k15,F17:side:k16,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l62,F17:side:l63,F17:side:l64]|F17:side:k16": "!edge.chamfer !edge.fillet edge.normal_plane edge.project", - "Body1/F17:bottom#[F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k13,F17:side:k14,F17:side:k15,F17:side:k16,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l62,F17:side:l63,F17:side:l64]|F17:side:k9": "!edge.chamfer !edge.fillet edge.normal_plane edge.normal_plane+edit edge.project", - "Body1/F17:bottom#[F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k13,F17:side:k14,F17:side:k15,F17:side:k16,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l62,F17:side:l63,F17:side:l64]|F17:side:l57": "edge.axis !edge.chamfer !edge.fillet edge.normal_plane edge.project", - "Body1/F17:bottom#[F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k13,F17:side:k14,F17:side:k15,F17:side:k16,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l62,F17:side:l63,F17:side:l64]|F17:side:l58": "edge.axis edge.axis+edit !edge.chamfer !edge.fillet edge.normal_plane edge.project", + "Body1/F17:bottom#[F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k13,F17:side:k14,F17:side:k15,F17:side:k16,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l62,F17:side:l63,F17:side:l64]|F17:side:k9": "edge.chamfer edge.fillet edge.normal_plane edge.normal_plane+edit edge.project", + "Body1/F17:bottom#[F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k13,F17:side:k14,F17:side:k15,F17:side:k16,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l62,F17:side:l63,F17:side:l64]|F17:side:l57": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project", + "Body1/F17:bottom#[F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k13,F17:side:k14,F17:side:k15,F17:side:k16,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l62,F17:side:l63,F17:side:l64]|F17:side:l58": "edge.axis edge.axis+edit edge.chamfer edge.fillet edge.normal_plane edge.project", "Body1/F17:bottom#[F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k13,F17:side:k14,F17:side:k15,F17:side:k16,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l62,F17:side:l63,F17:side:l64]|F17:side:l59": "edge.axis edge.chamfer edge.fillet edge.fillet+edit edge.normal_plane edge.project", "Body1/F17:bottom#[F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k13,F17:side:k14,F17:side:k15,F17:side:k16,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l62,F17:side:l63,F17:side:l64]|F17:side:l60": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.normal_plane+edit edge.project", "Body1/F17:bottom#[F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k13,F17:side:k14,F17:side:k15,F17:side:k16,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l62,F17:side:l63,F17:side:l64]|F17:side:l61": "edge.axis edge.axis+edit edge.chamfer edge.fillet edge.normal_plane edge.project edge.project+edit", @@ -2294,11 +2294,11 @@ "Body1/F17:bottom#[F17:side:l73,F17:side:l74,F17:side:l75,F17:side:l76,F17:side:l77,F17:side:l78,F17:side:l79,F17:side:l80,F17:side:l81,F17:side:l82,F17:side:l83,F17:side:l84,F17:side:l85,F17:side:l86]|F17:side:l86": "edge.axis edge.chamfer edge.fillet edge.fillet+edit edge.normal_plane edge.project", "Body1/F17:side:k10|F17:side:k11": "edge.axis edge.normal_plane edge.project", "Body1/F17:side:k10|F17:side:k9": "edge.axis edge.normal_plane edge.project edge.project+edit", - "Body1/F17:side:k10|F4:inner:F2:start#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1]#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]#[F14:side:l5,F14:side:l5@F15.1,F14:side:l5@F15.2,F14:side:l5@F15.3,F14:side:l5@F15.4,F14:side:l6,F14:side:l6@F15.1,F14:side:l6@F15.2,F14:side:l6@F15.3,F14:side:l6@F15.4,F14:side:l7,F14:side:l7@F15.1,F14:side:l7@F15.2,F14:side:l7@F15.3,F14:side:l7@F15.4,F14:side:l8,F14:side:l8@F15.1,F14:side:l8@F15.2,F14:side:l8@F15.3,F14:side:l8@F15.4,F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l65,F17:side:l66,F17:side:l67,F17:side:l68,F17:side:l69,F17:side:l70,F17:side:l71,F17:side:l72,F17:side:l76,F17:side:l77,F17:side:l78,F17:side:l79,F17:side:l80,F17:side:l81,F17:side:l82,F17:side:l83,F17:side:l84,F17:side:l85,F17:side:l86,F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]": "!edge.chamfer !edge.fillet edge.normal_plane edge.project", + "Body1/F17:side:k10|F4:inner:F2:start#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1]#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]#[F14:side:l5,F14:side:l5@F15.1,F14:side:l5@F15.2,F14:side:l5@F15.3,F14:side:l5@F15.4,F14:side:l6,F14:side:l6@F15.1,F14:side:l6@F15.2,F14:side:l6@F15.3,F14:side:l6@F15.4,F14:side:l7,F14:side:l7@F15.1,F14:side:l7@F15.2,F14:side:l7@F15.3,F14:side:l7@F15.4,F14:side:l8,F14:side:l8@F15.1,F14:side:l8@F15.2,F14:side:l8@F15.3,F14:side:l8@F15.4,F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l65,F17:side:l66,F17:side:l67,F17:side:l68,F17:side:l69,F17:side:l70,F17:side:l71,F17:side:l72,F17:side:l76,F17:side:l77,F17:side:l78,F17:side:l79,F17:side:l80,F17:side:l81,F17:side:l82,F17:side:l83,F17:side:l84,F17:side:l85,F17:side:l86,F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]": "edge.chamfer edge.fillet edge.normal_plane edge.project", "Body1/F17:side:k11|F17:side:k12": "edge.axis edge.normal_plane edge.project", - "Body1/F17:side:k11|F4:inner:F2:start#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1]#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]#[F14:side:l5,F14:side:l5@F15.1,F14:side:l5@F15.2,F14:side:l5@F15.3,F14:side:l5@F15.4,F14:side:l6,F14:side:l6@F15.1,F14:side:l6@F15.2,F14:side:l6@F15.3,F14:side:l6@F15.4,F14:side:l7,F14:side:l7@F15.1,F14:side:l7@F15.2,F14:side:l7@F15.3,F14:side:l7@F15.4,F14:side:l8,F14:side:l8@F15.1,F14:side:l8@F15.2,F14:side:l8@F15.3,F14:side:l8@F15.4,F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l65,F17:side:l66,F17:side:l67,F17:side:l68,F17:side:l69,F17:side:l70,F17:side:l71,F17:side:l72,F17:side:l76,F17:side:l77,F17:side:l78,F17:side:l79,F17:side:l80,F17:side:l81,F17:side:l82,F17:side:l83,F17:side:l84,F17:side:l85,F17:side:l86,F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]": "!edge.chamfer !edge.fillet edge.normal_plane edge.project edge.project+edit", + "Body1/F17:side:k11|F4:inner:F2:start#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1]#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]#[F14:side:l5,F14:side:l5@F15.1,F14:side:l5@F15.2,F14:side:l5@F15.3,F14:side:l5@F15.4,F14:side:l6,F14:side:l6@F15.1,F14:side:l6@F15.2,F14:side:l6@F15.3,F14:side:l6@F15.4,F14:side:l7,F14:side:l7@F15.1,F14:side:l7@F15.2,F14:side:l7@F15.3,F14:side:l7@F15.4,F14:side:l8,F14:side:l8@F15.1,F14:side:l8@F15.2,F14:side:l8@F15.3,F14:side:l8@F15.4,F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l65,F17:side:l66,F17:side:l67,F17:side:l68,F17:side:l69,F17:side:l70,F17:side:l71,F17:side:l72,F17:side:l76,F17:side:l77,F17:side:l78,F17:side:l79,F17:side:l80,F17:side:l81,F17:side:l82,F17:side:l83,F17:side:l84,F17:side:l85,F17:side:l86,F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]": "edge.chamfer edge.fillet edge.normal_plane edge.project edge.project+edit", "Body1/F17:side:k12|F17:side:l58": "edge.axis edge.normal_plane edge.project", - "Body1/F17:side:k12|F4:inner:F2:start#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1]#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]#[F14:side:l5,F14:side:l5@F15.1,F14:side:l5@F15.2,F14:side:l5@F15.3,F14:side:l5@F15.4,F14:side:l6,F14:side:l6@F15.1,F14:side:l6@F15.2,F14:side:l6@F15.3,F14:side:l6@F15.4,F14:side:l7,F14:side:l7@F15.1,F14:side:l7@F15.2,F14:side:l7@F15.3,F14:side:l7@F15.4,F14:side:l8,F14:side:l8@F15.1,F14:side:l8@F15.2,F14:side:l8@F15.3,F14:side:l8@F15.4,F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l65,F17:side:l66,F17:side:l67,F17:side:l68,F17:side:l69,F17:side:l70,F17:side:l71,F17:side:l72,F17:side:l76,F17:side:l77,F17:side:l78,F17:side:l79,F17:side:l80,F17:side:l81,F17:side:l82,F17:side:l83,F17:side:l84,F17:side:l85,F17:side:l86,F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]": "!edge.chamfer !edge.fillet edge.normal_plane edge.project", + "Body1/F17:side:k12|F4:inner:F2:start#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1]#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]#[F14:side:l5,F14:side:l5@F15.1,F14:side:l5@F15.2,F14:side:l5@F15.3,F14:side:l5@F15.4,F14:side:l6,F14:side:l6@F15.1,F14:side:l6@F15.2,F14:side:l6@F15.3,F14:side:l6@F15.4,F14:side:l7,F14:side:l7@F15.1,F14:side:l7@F15.2,F14:side:l7@F15.3,F14:side:l7@F15.4,F14:side:l8,F14:side:l8@F15.1,F14:side:l8@F15.2,F14:side:l8@F15.3,F14:side:l8@F15.4,F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l65,F17:side:l66,F17:side:l67,F17:side:l68,F17:side:l69,F17:side:l70,F17:side:l71,F17:side:l72,F17:side:l76,F17:side:l77,F17:side:l78,F17:side:l79,F17:side:l80,F17:side:l81,F17:side:l82,F17:side:l83,F17:side:l84,F17:side:l85,F17:side:l86,F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]": "edge.chamfer edge.fillet edge.normal_plane edge.project", "Body1/F17:side:k13|F17:side:k14": "edge.axis edge.normal_plane edge.project", "Body1/F17:side:k13|F17:side:l63": "edge.axis edge.normal_plane edge.project edge.project+edit", "Body1/F17:side:k13|F4:inner:F2:start#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1]#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]#[F17:side:k13,F17:side:k14,F17:side:k15,F17:side:k16,F17:side:l62,F17:side:l63,F17:side:l64]": "edge.chamfer edge.fillet edge.fillet+edit edge.normal_plane edge.project", @@ -2308,13 +2308,13 @@ "Body1/F17:side:k16|F17:side:l64": "edge.axis edge.normal_plane edge.project", "Body1/F17:side:k16|F4:inner:F2:start#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1]#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]#[F17:side:k13,F17:side:k14,F17:side:k15,F17:side:k16,F17:side:l62,F17:side:l63,F17:side:l64]": "edge.chamfer edge.fillet edge.normal_plane edge.project edge.project+edit", "Body1/F17:side:k9|F17:side:l57": "edge.axis edge.axis+edit edge.normal_plane edge.project", - "Body1/F17:side:k9|F4:inner:F2:start#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1]#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]#[F14:side:l5,F14:side:l5@F15.1,F14:side:l5@F15.2,F14:side:l5@F15.3,F14:side:l5@F15.4,F14:side:l6,F14:side:l6@F15.1,F14:side:l6@F15.2,F14:side:l6@F15.3,F14:side:l6@F15.4,F14:side:l7,F14:side:l7@F15.1,F14:side:l7@F15.2,F14:side:l7@F15.3,F14:side:l7@F15.4,F14:side:l8,F14:side:l8@F15.1,F14:side:l8@F15.2,F14:side:l8@F15.3,F14:side:l8@F15.4,F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l65,F17:side:l66,F17:side:l67,F17:side:l68,F17:side:l69,F17:side:l70,F17:side:l71,F17:side:l72,F17:side:l76,F17:side:l77,F17:side:l78,F17:side:l79,F17:side:l80,F17:side:l81,F17:side:l82,F17:side:l83,F17:side:l84,F17:side:l85,F17:side:l86,F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]": "!edge.chamfer !edge.fillet edge.normal_plane edge.project", + "Body1/F17:side:k9|F4:inner:F2:start#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1]#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]#[F14:side:l5,F14:side:l5@F15.1,F14:side:l5@F15.2,F14:side:l5@F15.3,F14:side:l5@F15.4,F14:side:l6,F14:side:l6@F15.1,F14:side:l6@F15.2,F14:side:l6@F15.3,F14:side:l6@F15.4,F14:side:l7,F14:side:l7@F15.1,F14:side:l7@F15.2,F14:side:l7@F15.3,F14:side:l7@F15.4,F14:side:l8,F14:side:l8@F15.1,F14:side:l8@F15.2,F14:side:l8@F15.3,F14:side:l8@F15.4,F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l65,F17:side:l66,F17:side:l67,F17:side:l68,F17:side:l69,F17:side:l70,F17:side:l71,F17:side:l72,F17:side:l76,F17:side:l77,F17:side:l78,F17:side:l79,F17:side:l80,F17:side:l81,F17:side:l82,F17:side:l83,F17:side:l84,F17:side:l85,F17:side:l86,F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]": "edge.chamfer edge.fillet edge.normal_plane edge.project", "Body1/F17:side:l57": "face.angle_plane !face.draft_neutral face.extrude !face.hole face.midplane face.midplane+edit face.offset_plane face.offset_plane+edit face.press_pull !face.shell face.sketch face.sketch+edit", "Body1/F17:side:l57|F17:side:l61": "edge.axis edge.chamfer edge.chamfer+edit edge.fillet edge.normal_plane edge.normal_plane+edit edge.project", - "Body1/F17:side:l57|F4:inner:F2:start#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1]#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]#[F14:side:l5,F14:side:l5@F15.1,F14:side:l5@F15.2,F14:side:l5@F15.3,F14:side:l5@F15.4,F14:side:l6,F14:side:l6@F15.1,F14:side:l6@F15.2,F14:side:l6@F15.3,F14:side:l6@F15.4,F14:side:l7,F14:side:l7@F15.1,F14:side:l7@F15.2,F14:side:l7@F15.3,F14:side:l7@F15.4,F14:side:l8,F14:side:l8@F15.1,F14:side:l8@F15.2,F14:side:l8@F15.3,F14:side:l8@F15.4,F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l65,F17:side:l66,F17:side:l67,F17:side:l68,F17:side:l69,F17:side:l70,F17:side:l71,F17:side:l72,F17:side:l76,F17:side:l77,F17:side:l78,F17:side:l79,F17:side:l80,F17:side:l81,F17:side:l82,F17:side:l83,F17:side:l84,F17:side:l85,F17:side:l86,F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]": "edge.axis !edge.chamfer !edge.fillet edge.normal_plane edge.project", + "Body1/F17:side:l57|F4:inner:F2:start#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1]#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]#[F14:side:l5,F14:side:l5@F15.1,F14:side:l5@F15.2,F14:side:l5@F15.3,F14:side:l5@F15.4,F14:side:l6,F14:side:l6@F15.1,F14:side:l6@F15.2,F14:side:l6@F15.3,F14:side:l6@F15.4,F14:side:l7,F14:side:l7@F15.1,F14:side:l7@F15.2,F14:side:l7@F15.3,F14:side:l7@F15.4,F14:side:l8,F14:side:l8@F15.1,F14:side:l8@F15.2,F14:side:l8@F15.3,F14:side:l8@F15.4,F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l65,F17:side:l66,F17:side:l67,F17:side:l68,F17:side:l69,F17:side:l70,F17:side:l71,F17:side:l72,F17:side:l76,F17:side:l77,F17:side:l78,F17:side:l79,F17:side:l80,F17:side:l81,F17:side:l82,F17:side:l83,F17:side:l84,F17:side:l85,F17:side:l86,F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project", "Body1/F17:side:l58": "face.angle_plane !face.draft_neutral face.extrude !face.hole face.midplane face.offset_plane face.press_pull face.press_pull+edit !face.shell face.sketch", "Body1/F17:side:l58|F17:side:l59": "edge.axis edge.chamfer edge.chamfer+edit edge.fillet edge.normal_plane edge.project", - "Body1/F17:side:l58|F4:inner:F2:start#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1]#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]#[F14:side:l5,F14:side:l5@F15.1,F14:side:l5@F15.2,F14:side:l5@F15.3,F14:side:l5@F15.4,F14:side:l6,F14:side:l6@F15.1,F14:side:l6@F15.2,F14:side:l6@F15.3,F14:side:l6@F15.4,F14:side:l7,F14:side:l7@F15.1,F14:side:l7@F15.2,F14:side:l7@F15.3,F14:side:l7@F15.4,F14:side:l8,F14:side:l8@F15.1,F14:side:l8@F15.2,F14:side:l8@F15.3,F14:side:l8@F15.4,F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l65,F17:side:l66,F17:side:l67,F17:side:l68,F17:side:l69,F17:side:l70,F17:side:l71,F17:side:l72,F17:side:l76,F17:side:l77,F17:side:l78,F17:side:l79,F17:side:l80,F17:side:l81,F17:side:l82,F17:side:l83,F17:side:l84,F17:side:l85,F17:side:l86,F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]": "edge.axis !edge.chamfer !edge.fillet edge.normal_plane edge.project", + "Body1/F17:side:l58|F4:inner:F2:start#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1]#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]#[F14:side:l5,F14:side:l5@F15.1,F14:side:l5@F15.2,F14:side:l5@F15.3,F14:side:l5@F15.4,F14:side:l6,F14:side:l6@F15.1,F14:side:l6@F15.2,F14:side:l6@F15.3,F14:side:l6@F15.4,F14:side:l7,F14:side:l7@F15.1,F14:side:l7@F15.2,F14:side:l7@F15.3,F14:side:l7@F15.4,F14:side:l8,F14:side:l8@F15.1,F14:side:l8@F15.2,F14:side:l8@F15.3,F14:side:l8@F15.4,F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l65,F17:side:l66,F17:side:l67,F17:side:l68,F17:side:l69,F17:side:l70,F17:side:l71,F17:side:l72,F17:side:l76,F17:side:l77,F17:side:l78,F17:side:l79,F17:side:l80,F17:side:l81,F17:side:l82,F17:side:l83,F17:side:l84,F17:side:l85,F17:side:l86,F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project", "Body1/F17:side:l59": "face.angle_plane face.angle_plane+edit !face.draft_neutral face.extrude !face.hole face.offset_plane face.press_pull !face.shell face.sketch", "Body1/F17:side:l59|F17:side:l60": "edge.axis edge.axis+edit edge.chamfer edge.fillet edge.normal_plane edge.project", "Body1/F17:side:l59|F4:inner:F2:start#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1]#[F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]#[F14:side:l5,F14:side:l5@F15.1,F14:side:l5@F15.2,F14:side:l5@F15.3,F14:side:l5@F15.4,F14:side:l6,F14:side:l6@F15.1,F14:side:l6@F15.2,F14:side:l6@F15.3,F14:side:l6@F15.4,F14:side:l7,F14:side:l7@F15.1,F14:side:l7@F15.2,F14:side:l7@F15.3,F14:side:l7@F15.4,F14:side:l8,F14:side:l8@F15.1,F14:side:l8@F15.2,F14:side:l8@F15.3,F14:side:l8@F15.4,F17:side:k10,F17:side:k11,F17:side:k12,F17:side:k9,F17:side:l57,F17:side:l58,F17:side:l59,F17:side:l60,F17:side:l61,F17:side:l65,F17:side:l66,F17:side:l67,F17:side:l68,F17:side:l69,F17:side:l70,F17:side:l71,F17:side:l72,F17:side:l76,F17:side:l77,F17:side:l78,F17:side:l79,F17:side:l80,F17:side:l81,F17:side:l82,F17:side:l83,F17:side:l84,F17:side:l85,F17:side:l86,F4:inner:F2:side:l1,F4:inner:F2:side:l2,F4:inner:F2:side:l3,F4:inner:F2:side:l4,F4:inner:F3:blend:F2:side:l1|F2:side:l2,F4:inner:F3:blend:F2:side:l1|F2:side:l4,F4:inner:F3:blend:F2:side:l2|F2:side:l3,F4:inner:F3:blend:F2:side:l3|F2:side:l4,F7:side:c1,F7:side:c1@F8.1,F7:side:c1@F8.2,F7:side:c1@F8.3]": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project", @@ -3953,12 +3953,12 @@ "A/F2:side:l1|F2:top": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project", "A/F2:side:l2#[F2:side:l1]": "face.angle_plane !face.draft_neutral face.extrude face.hole face.midplane face.offset_plane face.press_pull face.shell face.sketch", "A/F2:side:l2#[F2:side:l1]|F2:start": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project", - "A/F2:side:l2#[F2:side:l1]|F2:top": "edge.axis !edge.chamfer !edge.fillet edge.normal_plane edge.project", + "A/F2:side:l2#[F2:side:l1]|F2:top": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project", "A/F2:side:l2#[F2:side:l1]|F4:side:c1#2#[F2:side:l2,F2:start]": "edge.axis !edge.chamfer !edge.fillet edge.normal_plane edge.project", "A/F2:side:l2#[F2:side:l3]": "face.angle_plane !face.draft_neutral face.extrude face.hole face.midplane face.offset_plane face.press_pull face.shell face.sketch", "A/F2:side:l2#[F2:side:l3]|F2:side:l3": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project", "A/F2:side:l2#[F2:side:l3]|F2:start": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project", - "A/F2:side:l2#[F2:side:l3]|F2:top": "edge.axis !edge.chamfer !edge.fillet edge.normal_plane edge.project", + "A/F2:side:l2#[F2:side:l3]|F2:top": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project", "A/F2:side:l2#[F2:side:l3]|F4:side:c1#1#[F2:side:l2,F2:start]": "edge.axis !edge.chamfer !edge.fillet edge.normal_plane edge.project", "A/F2:side:l3": "face.angle_plane !face.draft_neutral face.extrude face.hole face.offset_plane face.press_pull face.shell face.sketch", "A/F2:side:l3|F2:side:l4": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project", @@ -3968,8 +3968,8 @@ "A/F2:side:l4|F2:start": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project", "A/F2:side:l4|F2:top": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project", "A/F2:start": "face.angle_plane face.draft_neutral face.extrude face.hole face.midplane face.offset_plane face.press_pull face.shell face.sketch", - "A/F2:start|F4:side:c1#1#[F2:side:l2,F2:start]": "!edge.chamfer !edge.fillet edge.normal_plane edge.project", - "A/F2:start|F4:side:c1#2#[F2:side:l2,F2:start]": "!edge.chamfer !edge.fillet edge.normal_plane edge.project", + "A/F2:start|F4:side:c1#1#[F2:side:l2,F2:start]": "edge.chamfer edge.fillet edge.normal_plane edge.project", + "A/F2:start|F4:side:c1#2#[F2:side:l2,F2:start]": "edge.chamfer edge.fillet edge.normal_plane edge.project", "A/F2:top": "face.angle_plane face.draft_neutral face.extrude face.hole face.offset_plane face.press_pull face.shell face.sketch", "A/F2:top|F4:side:c1#1#[F2:top,F4:top]": "!edge.chamfer !edge.fillet edge.normal_plane edge.project", "A/F2:top|F4:side:c1#2#[F2:top,F4:top]": "!edge.chamfer !edge.fillet edge.normal_plane edge.project", @@ -4341,7 +4341,7 @@ "Cyl (1)/F2:side:c1": "curved.axis curved.tangent_plane", "Cyl (1)/F2:side:c1|F4:face:3": "!edge.chamfer !edge.fillet edge.normal_plane edge.project", "Cyl (1)/F4:face:2": "curved.axis curved.tangent_plane", - "Cyl (1)/F4:face:2|F4:face:3": "!edge.chamfer !edge.fillet edge.normal_plane edge.project", + "Cyl (1)/F4:face:2|F4:face:3": "edge.chamfer edge.fillet edge.normal_plane edge.project", "Cyl (1)/F4:face:3": "!face.extrude face.hole !face.offset_plane !face.press_pull face.shell !face.sketch", "Cyl (1)/body": "body.copy body.mirror body.move body.pattern", "Cyl (1)/v[F2:bottom & F2:side:c1 & F4:face:2]": "vertex.point", @@ -4770,19 +4770,19 @@ "Block/F2:side:l3|F2:top~1": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project", "Block/F2:side:l3|F2:top~2": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project", "Block/F2:side:l3|F4:end": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project", - "Block/F2:side:l3|F4:side:l2#[F2:top,F4:start]": "!edge.chamfer !edge.fillet edge.normal_plane edge.project", - "Block/F2:side:l3|F4:side:l2#[F2:top]": "!edge.chamfer !edge.fillet edge.normal_plane edge.project", - "Block/F2:side:l3|F4:side:l2#[F4:end]": "!edge.chamfer !edge.fillet edge.normal_plane edge.project", - "Block/F2:side:l3|F4:side:l4": "!edge.chamfer !edge.fillet edge.normal_plane edge.project", - "Block/F2:side:l3|F4:side:l4#[F4:end]": "!edge.chamfer !edge.fillet edge.normal_plane edge.project", - "Block/F2:side:l3|F4:side:l4#[F4:start]": "!edge.chamfer !edge.fillet edge.normal_plane edge.project", + "Block/F2:side:l3|F4:side:l2#[F2:top,F4:start]": "edge.chamfer edge.fillet edge.normal_plane edge.project", + "Block/F2:side:l3|F4:side:l2#[F2:top]": "edge.chamfer edge.fillet edge.normal_plane edge.project", + "Block/F2:side:l3|F4:side:l2#[F4:end]": "edge.chamfer edge.fillet edge.normal_plane edge.project", + "Block/F2:side:l3|F4:side:l4": "edge.chamfer edge.fillet edge.normal_plane edge.project", + "Block/F2:side:l3|F4:side:l4#[F4:end]": "edge.chamfer edge.fillet edge.normal_plane edge.project", + "Block/F2:side:l3|F4:side:l4#[F4:start]": "edge.chamfer edge.fillet edge.normal_plane edge.project", "Block/F2:side:l3|F4:start": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project", "Block/F2:side:l4": "face.angle_plane !face.draft_neutral face.extrude face.hole face.midplane face.offset_plane face.press_pull face.shell face.sketch", "Block/F2:side:l4|F2:top": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project", "Block/F2:top": "face.angle_plane !face.draft_neutral face.extrude face.hole face.midplane face.offset_plane face.press_pull face.shell face.sketch", "Block/F2:top|F4:side:l1": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project", - "Block/F2:top|F4:side:l2#[F2:top,F4:start]": "edge.axis !edge.chamfer !edge.fillet edge.normal_plane edge.project", - "Block/F2:top|F4:side:l2#[F2:top]": "edge.axis !edge.chamfer !edge.fillet edge.normal_plane edge.project", + "Block/F2:top|F4:side:l2#[F2:top,F4:start]": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project", + "Block/F2:top|F4:side:l2#[F2:top]": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project", "Block/F4:end": "face.angle_plane !face.draft_neutral face.extrude face.hole face.offset_plane face.press_pull face.shell face.sketch", "Block/F4:end|F4:side:l1": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project", "Block/F4:end|F4:side:l2#[F4:end]": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",