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 <noreply@anthropic.com>
This commit is contained in:
bherbruck authored and GitHub committed 2026-10-14 00:28:09 -04:00
1 parent 63095fedb3
commit ce902be66c
5 files changed
+537 -133

No files matched your search

@@ -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<Vec<String>> = 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<String> = 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}),
+493 -105
View File
@@ -76,26 +76,73 @@ fn rebuild_face_dropping(f: &mt::Face, subst: &HashMap<mt::EdgeID, mt::Edge>, 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<mt::EdgeID> = 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<Vec<mt::Edge>> {
let mut paths: Vec<(mt::Vertex, Vec<mt::Edge>, Vec<usize>)> = 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<mt::Vertex>,
cb: Option<mt::Vertex>,
/// Points of the end's section on a curved end face, from A to B.
cap: Option<Vec<Vec3>>,
/// 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<Vec<Vec<Vec3>>>,
/// 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<Vec<Vec3>> = 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<mt::EdgeID> = 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<usize> =
(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<mt::EdgeID> = 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<mt::Surface> = 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<Vec3>> = 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<Vec3> {
@@ -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<mt::Edge>, Option<mt::Vertex>)> = Vec::new();
#[allow(clippy::type_complexity)]
let mut corners: Vec<(mt::Vertex, mt::Vertex, Vec<mt::Edge>, Option<mt::Vertex>, Vec<mt::Vertex>, Vec<mt::Edge>)> = 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<mt::Vertex> = 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<mt::Edge> = c0.iter().chain(x0).cloned().collect();
let link1: Vec<mt::Edge> = 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<mt::EdgeID, mt::Edge> = HashMap::new();
let mut dropped: Vec<mt::EdgeID> = 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<Body> {
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<Body> {
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<Vec<mt::EdgeID>> {
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<Vec<mt::EdgeID>> {
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<Vec3> = 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<Vec3> {
use mt::{BoundedCurve, ParametricCurve};
let c = e.curve();
@@ -1537,19 +1840,29 @@ pub fn chamfer(body: &Body, edges: &[Vec3], distance: f64) -> Result<Body> {
// 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<Body> {
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<mt::EdgeID> = 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] {
+4 -3
View File
@@ -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());
+3
View File
@@ -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
+25 -25
View File
@@ -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",