Feature fields: a type for each role (Expr, BodyName, FacePick…) (#1273)
Thanks @jijinbei.
This commit is contained in:
1 parent
3333f8a62b
commit
44968dbfe6
59 files changed
+1505
-1043
No files matched your search
+96
-92
@@ -951,15 +951,15 @@ fn placement_digest(doc: &Document, f: &Feature) -> u64 {
|
||||
_ => None,
|
||||
};
|
||||
if let Some(path) = path
|
||||
&& doc.sketch(path).is_ok_and(|sketch| !sketch.wires.is_empty())
|
||||
&& doc.sketch(path.0).is_ok_and(|sketch| !sketch.wires.is_empty())
|
||||
{
|
||||
let mut hash = std::collections::hash_map::DefaultHasher::new();
|
||||
let owner = doc.feature(path).map(|feature| feature.component).unwrap_or(0);
|
||||
let owner = doc.feature(path.0).map(|feature| feature.component).unwrap_or(0);
|
||||
for frame in [doc.component_transform(f.component), doc.component_transform(owner)] {
|
||||
frame.iter().flatten().for_each(|value| value.to_bits().hash(&mut hash));
|
||||
}
|
||||
if let FeatureKind::Sweep { sketch, .. } | FeatureKind::SurfaceSweep { sketch, .. } = &f.kind {
|
||||
let owner = doc.feature(*sketch).map(|feature| feature.component).unwrap_or(0);
|
||||
let owner = doc.feature(sketch.0).map(|feature| feature.component).unwrap_or(0);
|
||||
doc.component_transform(owner).iter().flatten().for_each(|value| value.to_bits().hash(&mut hash));
|
||||
}
|
||||
return hash.finish();
|
||||
@@ -1951,7 +1951,7 @@ fn face_level_at(st: &ModelState, plane: &Plane, h: f64, targets: &[String]) ->
|
||||
|
||||
/// The body a face-based feature applies to: by name, or the one whose surface is nearest the
|
||||
/// first point.
|
||||
fn body_at(st: &ModelState, body: &Option<String>, points: &[Vec3]) -> Result<usize> {
|
||||
fn body_at(st: &ModelState, body: &Option<crate::BodyName>, points: &[Vec3]) -> Result<usize> {
|
||||
if let Some(n) = body {
|
||||
return st.bodies.iter().position(|b| &b.name == n).ok_or_else(|| DocError::Unknown(format!("body `{n}`")));
|
||||
}
|
||||
@@ -2085,7 +2085,7 @@ fn edge_dir(e: &kernel::EdgeInfo) -> Vec3 {
|
||||
}
|
||||
|
||||
/// References for edges picked at `pts` on the body a blend would go on (the state before it).
|
||||
pub fn edge_refs(st: &ModelState, body: &Option<String>, pts: &[Vec3]) -> Vec<EdgeRef> {
|
||||
pub fn edge_refs(st: &ModelState, body: &Option<crate::BodyName>, pts: &[Vec3]) -> Vec<EdgeRef> {
|
||||
let Ok(ti) = blend_target(st, body, pts) else { return Vec::new() };
|
||||
let Some(mb) = st.bodies.get(ti) else { return Vec::new() };
|
||||
let size = mb.body.size();
|
||||
@@ -2103,7 +2103,7 @@ pub fn edge_refs(st: &ModelState, body: &Option<String>, pts: &[Vec3]) -> Vec<Ed
|
||||
}
|
||||
|
||||
/// Persistent names of the edges picked at `pts` on the body a blend would go on.
|
||||
pub fn edge_names_for(st: &ModelState, body: &Option<String>, pts: &[Vec3]) -> Vec<String> {
|
||||
pub fn edge_names_for(st: &ModelState, body: &Option<crate::BodyName>, pts: &[Vec3]) -> Vec<String> {
|
||||
match blend_target(st, body, pts).ok().and_then(|i| st.bodies.get(i)) {
|
||||
Some(b) => crate::naming::edge_names_at(b, pts),
|
||||
None => Vec::new(),
|
||||
@@ -2190,7 +2190,7 @@ fn resolve_edges(b: &Body, pts: &[Vec3], refs: &[EdgeRef], warning: &mut Option<
|
||||
}
|
||||
|
||||
/// The body a fillet/chamfer applies to: by name, or the one closest to the first edge point.
|
||||
fn blend_target(state: &ModelState, body: &Option<String>, edges: &[Vec3]) -> Result<usize> {
|
||||
fn blend_target(state: &ModelState, body: &Option<crate::BodyName>, edges: &[Vec3]) -> Result<usize> {
|
||||
if let Some(n) = body {
|
||||
return state.bodies.iter().position(|b| &b.name == n).ok_or_else(|| DocError::Unknown(format!("body `{n}`")));
|
||||
}
|
||||
@@ -2246,10 +2246,10 @@ pub(crate) fn revolve_axis(ss: &SolvedSketch, axis: &AxisRef) -> Result<(Vec2, V
|
||||
AxisRef::Line { origin, dir } => {
|
||||
let pl = &ss.plane;
|
||||
let d = dir.normalized().ok_or_else(|| DocError::Invalid("revolve axis has no direction".into()))?;
|
||||
if d.dot(pl.normal()).abs() > 1e-9 || pl.height(*origin).abs() > 1e-6 {
|
||||
if d.dot(pl.normal()).abs() > 1e-9 || pl.height(origin.0).abs() > 1e-6 {
|
||||
return Err(DocError::Invalid("the revolve axis does not lie in the sketch plane".into()));
|
||||
}
|
||||
Ok((pl.to_local(*origin), Vec2::new(d.dot(pl.x), d.dot(pl.y))))
|
||||
Ok((pl.to_local(origin.0), Vec2::new(d.dot(pl.x), d.dot(pl.y))))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2303,7 +2303,8 @@ fn hole_spots(st: &ModelState, f: &Feature) -> Result<(Vec<Vec3>, Vec3)> {
|
||||
let FeatureKind::Hole { position, direction, points, .. } = &f.kind else { return Err(DocError::Invalid("not a hole".into())) };
|
||||
let (spots, dir) = match points {
|
||||
Some(sp) => {
|
||||
let ss = st.sketch(sp.sketch).ok_or_else(|| DocError::Unknown(format!("sketch {} (it must come earlier in the timeline)", sp.sketch)))?;
|
||||
let ss =
|
||||
st.sketch(sp.sketch.0).ok_or_else(|| DocError::Unknown(format!("sketch {} (it must come earlier in the timeline)", sp.sketch)))?;
|
||||
let mut v = Vec::new();
|
||||
for id in &sp.ids {
|
||||
let p = ss.sketch.points.iter().find(|p| &p.id == id).ok_or_else(|| DocError::Unknown(format!("sketch point `{id}`")))?;
|
||||
@@ -2315,7 +2316,7 @@ fn hole_spots(st: &ModelState, f: &Feature) -> Result<(Vec<Vec3>, Vec3)> {
|
||||
}
|
||||
None => {
|
||||
let dir = direction.normalized().ok_or_else(|| DocError::Invalid("hole direction".into()))?;
|
||||
let at = hole_on_surface(st, *position, dir);
|
||||
let at = hole_on_surface(st, position.0, dir);
|
||||
let dir = hole_dir_into_face(st, at, dir);
|
||||
return Ok((vec![hole_on_surface(st, at, dir)], dir));
|
||||
}
|
||||
@@ -2328,7 +2329,7 @@ fn hole_spots(st: &ModelState, f: &Feature) -> Result<(Vec<Vec3>, Vec3)> {
|
||||
|
||||
/// A hole's depth: down to the To Object (where the hole's axis meets the face there, else the
|
||||
/// point's depth along the axis), else as given.
|
||||
fn hole_depth_to(st: &ModelState, at: &Vec3, dir: Vec3, to: &Option<Vec3>, depth: &Option<String>) -> Result<Option<String>> {
|
||||
fn hole_depth_to(st: &ModelState, at: &Vec3, dir: Vec3, to: &Option<Vec3>, depth: &Option<crate::Expr>) -> Result<Option<crate::Expr>> {
|
||||
let Some(t) = to else { return Ok(depth.clone()) };
|
||||
let mut d = (*t - *at).dot(dir);
|
||||
// The face at the To point, met along the axis.
|
||||
@@ -2345,7 +2346,7 @@ fn hole_depth_to(st: &ModelState, at: &Vec3, dir: Vec3, to: &Option<Vec3>, depth
|
||||
if !(d > 1e-6) {
|
||||
return Err(DocError::Invalid("the hole's To object is not ahead of the hole".into()));
|
||||
}
|
||||
Ok(Some(format!("{d} mm")))
|
||||
Ok(Some(format!("{d} mm").into()))
|
||||
}
|
||||
|
||||
/// A hole's spots as given (not yet moved onto the surface) and its direction.
|
||||
@@ -2353,7 +2354,7 @@ fn hole_spots_raw(st: &ModelState, f: &Feature) -> Result<(Vec<Vec3>, Vec3)> {
|
||||
let FeatureKind::Hole { position, direction, points, .. } = &f.kind else { return Err(DocError::Invalid("not a hole".into())) };
|
||||
match points {
|
||||
Some(sp) => {
|
||||
let ss = st.sketch(sp.sketch).ok_or_else(|| DocError::Unknown(format!("sketch {}", sp.sketch)))?;
|
||||
let ss = st.sketch(sp.sketch.0).ok_or_else(|| DocError::Unknown(format!("sketch {}", sp.sketch)))?;
|
||||
let v: Vec<Vec3> =
|
||||
sp.ids.iter().filter_map(|id| ss.sketch.points.iter().find(|p| &p.id == id)).map(|p| ss.plane.to_world(p.pos)).take(10_000).collect();
|
||||
let dir = sketch_hole_dir(st, &v, ss.plane.normal());
|
||||
@@ -2362,7 +2363,7 @@ fn hole_spots_raw(st: &ModelState, f: &Feature) -> Result<(Vec<Vec3>, Vec3)> {
|
||||
}
|
||||
None => {
|
||||
let dir = direction.normalized().ok_or_else(|| DocError::Invalid("hole direction".into()))?;
|
||||
Ok((vec![*position], hole_dir_into_face(st, hole_on_surface(st, *position, dir), dir)))
|
||||
Ok((vec![position.0], hole_dir_into_face(st, hole_on_surface(st, position.0, dir), dir)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2377,7 +2378,7 @@ fn hole_feature_tools(vals: &BTreeMap<String, Value>, st: &ModelState, f: &Featu
|
||||
}
|
||||
let mut tools = Vec::new();
|
||||
for at in spots {
|
||||
let depth = hole_depth_to(st, &at, dir, to, depth)?;
|
||||
let depth = hole_depth_to(st, &at, dir, &to.map(Vec3::from), depth)?;
|
||||
tools.extend(hole_tools_and_prisms(vals, st, &at, dir, diameter, &depth, hole)?);
|
||||
}
|
||||
Ok(tools)
|
||||
@@ -2454,7 +2455,7 @@ fn hole_tools(
|
||||
position: &Vec3,
|
||||
dir: Vec3,
|
||||
diameter: &str,
|
||||
depth: &Option<String>,
|
||||
depth: &Option<crate::Expr>,
|
||||
hole: &HoleKind,
|
||||
) -> Result<Vec<Traced>> {
|
||||
Ok(hole_tools_and_prisms(vals, st, position, dir, diameter, depth, hole)?.into_iter().map(|(tool, _)| tool).collect())
|
||||
@@ -2471,7 +2472,7 @@ fn hole_tools_and_prisms(
|
||||
position: &Vec3,
|
||||
dir: Vec3,
|
||||
diameter: &str,
|
||||
depth: &Option<String>,
|
||||
depth: &Option<crate::Expr>,
|
||||
hole: &HoleKind,
|
||||
) -> Result<Vec<(Traced, Option<more::PrimitivePrism>)>> {
|
||||
let r = val(vals, diameter, Kind::Length)? / 2.0;
|
||||
@@ -2746,7 +2747,7 @@ fn feature_tools_with_constructor(
|
||||
FeatureKind::Extrude { sketch, profiles, extent, operation, targets, thin } => {
|
||||
let ss = sketch_in_feature_frame(
|
||||
doc,
|
||||
st.sketch(*sketch).ok_or_else(|| DocError::Unknown(format!("sketch {sketch} (it must come earlier in the timeline)")))?,
|
||||
st.sketch(sketch.0).ok_or_else(|| DocError::Unknown(format!("sketch {sketch} (it must come earlier in the timeline)")))?,
|
||||
f.component,
|
||||
)?;
|
||||
let regions = match thin {
|
||||
@@ -2762,14 +2763,14 @@ fn feature_tools_with_constructor(
|
||||
// The face names follow `face_points`: To, then From.
|
||||
let name = |k: usize| f.face_names.get(k).map(String::as_str);
|
||||
let (to_name, from_name) = if extent.to.is_some() { (name(0), name(1)) } else { (None, name(0)) };
|
||||
let to_face = extent.to.and_then(|p| object_face(&tst, to_name, p));
|
||||
let to_face = extent.to.and_then(|p| object_face(&tst, to_name, p.0));
|
||||
let centre = {
|
||||
let pts: Vec<Vec2> = regions.iter().flat_map(|r| r.outer.polyline(1e-2)).collect();
|
||||
let c = pts.iter().fold(Vec2::default(), |a, p| a + *p) * (1.0 / pts.len().max(1) as f64);
|
||||
ss.plane.to_world(c)
|
||||
};
|
||||
let from = match extent.from {
|
||||
Some(p) => height_at(&tst, object_face(&tst, from_name, p), &ss.plane, centre, p),
|
||||
Some(p) => height_at(&tst, object_face(&tst, from_name, p.0), &ss.plane, centre, p.0),
|
||||
None => 0.0,
|
||||
};
|
||||
let d = match extent.to {
|
||||
@@ -2778,7 +2779,7 @@ fn feature_tools_with_constructor(
|
||||
Some(e) => val(vals, e, Kind::Length)?,
|
||||
None => 0.0,
|
||||
};
|
||||
let h = height_at(&tst, to_face, &ss.plane, centre, p);
|
||||
let h = height_at(&tst, to_face, &ss.plane, centre, p.0);
|
||||
let start = from + extent.start_offset.as_deref().map(|e| val(vals, e, Kind::Length)).transpose()?.unwrap_or(0.0);
|
||||
// Past the face by the offset, in the direction travelled.
|
||||
let to = h + if h >= start { off } else { -off };
|
||||
@@ -2787,7 +2788,7 @@ fn feature_tools_with_constructor(
|
||||
}
|
||||
to - start
|
||||
}
|
||||
None if extent.through_all => through_all_distance(st, &ss.plane, targets)?,
|
||||
None if extent.through_all => through_all_distance(st, &ss.plane, &crate::plain(targets))?,
|
||||
None => val(vals, &extent.distance, Kind::Length)?,
|
||||
};
|
||||
if let Some(tp) = &extent.taper {
|
||||
@@ -2805,7 +2806,7 @@ fn feature_tools_with_constructor(
|
||||
// How far a tool grows past its profile at either end, at most.
|
||||
let mut growth: f64 = 0.0;
|
||||
// A face of the targets in the profile's plane (the same for every profile).
|
||||
let on_face = matches!(operation, Operation::Join | Operation::Cut) && face_level_at(st, &ss.plane, from, targets);
|
||||
let on_face = matches!(operation, Operation::Join | Operation::Cut) && face_level_at(st, &ss.plane, from, &crate::plain(targets));
|
||||
let tools: Vec<Traced> = regions
|
||||
.iter()
|
||||
.map(|r| {
|
||||
@@ -2814,7 +2815,7 @@ fn feature_tools_with_constructor(
|
||||
// and the boolean finds no result there. The sides keep their slope, so
|
||||
// the profile stays the section at the start.
|
||||
let (lo, hi) = if sign < 0.0 { (from - len, from) } else { (from, from + len) };
|
||||
let (l2, h2) = extend_for_coplanar(st, &ss.plane, r, lo, hi, *operation, targets);
|
||||
let (l2, h2) = extend_for_coplanar(st, &ss.plane, r, lo, hi, *operation, &crate::plain(targets));
|
||||
let (a, b) = if sign < 0.0 { (from - h2, from - l2) } else { (l2 - from, h2 - from) };
|
||||
// A start that can't run on (the profile hangs over the face's edge):
|
||||
// the sides stand straight for a hair first, so the boolean can push the
|
||||
@@ -2882,7 +2883,7 @@ fn feature_tools_with_constructor(
|
||||
if exact.iter().all(|tool| {
|
||||
st.bodies
|
||||
.iter()
|
||||
.filter(|b| targets.is_empty() || targets.contains(&b.name))
|
||||
.filter(|b| targets.is_empty() || targets.iter().any(|t| *t == b.name))
|
||||
.any(|b| kernel::can_join_touching_faces(&b.body, &tool.body))
|
||||
}) {
|
||||
// Preserve a whole touching face: extending into a chamfer replaces
|
||||
@@ -2893,11 +2894,12 @@ fn feature_tools_with_constructor(
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let (l2, h2) = extend_for_coplanar(st, &ss.plane, r, lo + off, hi + off, *operation, targets);
|
||||
let (l2, h2) = extend_for_coplanar(st, &ss.plane, r, lo + off, hi + off, *operation, &crate::plain(targets));
|
||||
let probe = |p: Vec2| (1..4).map(|k| ss.plane.to_world(p) + n * (lo + off + (hi - lo) * k as f64 / 4.0)).collect::<Vec<_>>();
|
||||
let original = r;
|
||||
let r = grow_profile_for_coplanar(st, r, *operation, targets, &probe);
|
||||
let r = fill_void_holes(st, &r, *operation, targets, &ss.plane, (l2, h2));
|
||||
let plain_targets = crate::plain(targets);
|
||||
let r = grow_profile_for_coplanar(st, r, *operation, &plain_targets, &probe);
|
||||
let r = fill_void_holes(st, &r, *operation, &plain_targets, &ss.plane, (l2, h2));
|
||||
untrimmed &= l2 == lo + off && h2 == hi + off && &r == original;
|
||||
tools.extend(centered_extrude(&ss.plane, &r, l2, h2, Some(&ss))?);
|
||||
footprints.push(r);
|
||||
@@ -2973,7 +2975,7 @@ fn feature_tools_with_constructor(
|
||||
}
|
||||
}
|
||||
// Certificates set face sources only: the faces, and their lineage, stay.
|
||||
let body = body.with_extrude_certificates(f.id, *sketch, &curves)?.with_extrude_cap_certificates(f.id, *sketch, &caps)?;
|
||||
let body = body.with_extrude_certificates(f.id, sketch.0, &curves)?.with_extrude_cap_certificates(f.id, sketch.0, &caps)?;
|
||||
Ok(Traced { body, lineage })
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
@@ -2983,7 +2985,7 @@ fn feature_tools_with_constructor(
|
||||
FeatureKind::Revolve { sketch, profiles, axis, angle, angle2, symmetric, convention, to, .. } => {
|
||||
let ss = sketch_in_feature_frame(
|
||||
doc,
|
||||
st.sketch(*sketch).ok_or_else(|| DocError::Unknown(format!("sketch {sketch} (it must come earlier in the timeline)")))?,
|
||||
st.sketch(sketch.0).ok_or_else(|| DocError::Unknown(format!("sketch {sketch} (it must come earlier in the timeline)")))?,
|
||||
f.component,
|
||||
)?;
|
||||
let regions = solidcraft_sketch::merge_regions(&select_profiles(&ss, profiles)?);
|
||||
@@ -3002,7 +3004,7 @@ fn feature_tools_with_constructor(
|
||||
(ss.plane.to_world(o), ss.plane.dir_to_world(d)),
|
||||
angles,
|
||||
*symmetric,
|
||||
*to,
|
||||
to.map(Vec3::from),
|
||||
*convention,
|
||||
)?;
|
||||
let (start, ang) = (extent.start, extent.sweep);
|
||||
@@ -3017,7 +3019,7 @@ fn feature_tools_with_constructor(
|
||||
(ss.plane.to_world(o), ss.plane.dir_to_world(d)),
|
||||
angles,
|
||||
*symmetric,
|
||||
*to,
|
||||
to.map(Vec3::from),
|
||||
*convention,
|
||||
)
|
||||
} else {
|
||||
@@ -3052,7 +3054,7 @@ fn feature_tools_with_constructor(
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
// To a curved face: turn past the whole target body, then cut back to its surface
|
||||
// (the piece that starts at the profile).
|
||||
let curved = curved_face(&tst, to.and_then(|p| object_face(&tst, f.face_names.first().map(String::as_str), p)), None);
|
||||
let curved = curved_face(&tst, to.and_then(|p| object_face(&tst, f.face_names.first().map(String::as_str), p.0)), None);
|
||||
let curved_body = curved.and_then(|bi| tst.bodies.get(bi));
|
||||
// Each region's body: its sides named by the sketch curves, its caps start and end.
|
||||
let tool = |r: &Region2, body: Body, built: Option<&Body>| crate::lineage::ProfileRoles::swept(Some(&ss), Some(r)).tool(body, built);
|
||||
@@ -3114,11 +3116,11 @@ fn feature_tools_with_constructor(
|
||||
// A profile lofted to a sketch point (an apex).
|
||||
let [a, b] = §ions[..] else { return Err(DocError::Invalid("not supported yet: a point section in a loft of more than two".into())) };
|
||||
let (prof, pt) = if a.point.is_some() { (b, a) } else { (a, b) };
|
||||
let ss = st.sketch(prof.sketch).ok_or_else(|| DocError::Unknown(format!("sketch {}", prof.sketch)))?;
|
||||
let ss = st.sketch(prof.sketch.0).ok_or_else(|| DocError::Unknown(format!("sketch {}", prof.sketch)))?;
|
||||
let regions = solidcraft_sketch::merge_regions(&select_profiles(ss, &prof.profiles)?);
|
||||
let [r] = ®ions[..] else { return Err(DocError::Invalid("each loft section must be one profile".into())) };
|
||||
refuse_loft_holes(r)?;
|
||||
let ps = st.sketch(pt.sketch).ok_or_else(|| DocError::Unknown(format!("sketch {}", pt.sketch)))?;
|
||||
let ps = st.sketch(pt.sketch.0).ok_or_else(|| DocError::Unknown(format!("sketch {}", pt.sketch)))?;
|
||||
let id = pt.point.as_deref().unwrap_or_default();
|
||||
let q = ps.sketch.points.iter().find(|p| p.id == id).ok_or_else(|| DocError::Unknown(format!("point `{id}` in {}", ps.name)))?;
|
||||
let body = kernel::loft_to_point(&ss.plane, &r.outer, ps.plane.to_world(q.pos))?;
|
||||
@@ -3169,7 +3171,7 @@ fn feature_tools_with_constructor(
|
||||
let mut sketches = Vec::new();
|
||||
for sec in sections {
|
||||
let ss = st
|
||||
.sketch(sec.sketch)
|
||||
.sketch(sec.sketch.0)
|
||||
.ok_or_else(|| DocError::Unknown(format!("sketch {} (it must come earlier in the timeline)", sec.sketch)))?;
|
||||
let regions = solidcraft_sketch::merge_regions(&select_profiles(ss, &sec.profiles)?);
|
||||
let [r] = ®ions[..] else { return Err(DocError::Invalid("each loft section must be one profile".into())) };
|
||||
@@ -3185,9 +3187,9 @@ fn feature_tools_with_constructor(
|
||||
}
|
||||
FeatureKind::Sweep { sketch, profiles, path_sketch, path, orientation, twist, scale, .. } => {
|
||||
let ctl = sweep_control(vals, orientation, twist, scale)?;
|
||||
let ss = st.sketch(*sketch).ok_or_else(|| DocError::Unknown(format!("sketch {sketch} (it must come earlier in the timeline)")))?;
|
||||
let ss = st.sketch(sketch.0).ok_or_else(|| DocError::Unknown(format!("sketch {sketch} (it must come earlier in the timeline)")))?;
|
||||
let ps =
|
||||
st.sketch(*path_sketch).ok_or_else(|| DocError::Unknown(format!("sketch {path_sketch} (it must come earlier in the timeline)")))?;
|
||||
st.sketch(path_sketch.0).ok_or_else(|| DocError::Unknown(format!("sketch {path_sketch} (it must come earlier in the timeline)")))?;
|
||||
let profile;
|
||||
let ss = if !ps.sketch.wires.is_empty() {
|
||||
profile = profile_in_frame(doc, ss, f.component)?;
|
||||
@@ -3209,7 +3211,7 @@ fn feature_tools_with_constructor(
|
||||
}
|
||||
FeatureKind::Pipe { path_sketch, path, diameter, wall, .. } => {
|
||||
let ps =
|
||||
st.sketch(*path_sketch).ok_or_else(|| DocError::Unknown(format!("sketch {path_sketch} (it must come earlier in the timeline)")))?;
|
||||
st.sketch(path_sketch.0).ok_or_else(|| DocError::Unknown(format!("sketch {path_sketch} (it must come earlier in the timeline)")))?;
|
||||
let mapped = path_in_frame(doc, ps, f.component)?;
|
||||
let ps = mapped.as_ref();
|
||||
let r = val(vals, diameter, Kind::Length)? / 2.0;
|
||||
@@ -3239,7 +3241,7 @@ fn feature_tools_with_constructor(
|
||||
}
|
||||
FeatureKind::Cylinder { base, axis, radius, height, .. } => Ok(vec![crate::lineage::cylinder_tool(kernel::cylinder(
|
||||
point(vals, base)?,
|
||||
*axis,
|
||||
axis.0,
|
||||
val(vals, radius, Kind::Length)?,
|
||||
val(vals, height, Kind::Length)?,
|
||||
)?)]),
|
||||
@@ -3258,13 +3260,13 @@ fn feature_tools_with_constructor(
|
||||
pub(crate) fn sweep_control(
|
||||
vals: &BTreeMap<String, Value>,
|
||||
orientation: &crate::SweepOrientation,
|
||||
twist: &Option<String>,
|
||||
scale: &Option<String>,
|
||||
twist: &Option<crate::Expr>,
|
||||
scale: &Option<crate::Expr>,
|
||||
) -> Result<kernel::SweepControl> {
|
||||
let orientation = match *orientation {
|
||||
crate::SweepOrientation::Perpendicular => kernel::SweepOrientation::Perpendicular,
|
||||
crate::SweepOrientation::Parallel => kernel::SweepOrientation::Parallel,
|
||||
crate::SweepOrientation::Direction(d) => kernel::SweepOrientation::Direction(d),
|
||||
crate::SweepOrientation::Direction(d) => kernel::SweepOrientation::Direction(d.0),
|
||||
};
|
||||
let twist = match twist {
|
||||
Some(t) => val(vals, t, Kind::Angle)?,
|
||||
@@ -3280,7 +3282,7 @@ pub(crate) fn sweep_control(
|
||||
}
|
||||
|
||||
/// A pipe's section: a circle of radius `r`, hollow when it has a wall.
|
||||
fn pipe_region(vals: &BTreeMap<String, Value>, r: f64, wall: &Option<String>) -> Result<Region2> {
|
||||
fn pipe_region(vals: &BTreeMap<String, Value>, r: f64, wall: &Option<crate::Expr>) -> Result<Region2> {
|
||||
let holes = match wall {
|
||||
Some(w) => {
|
||||
let wt = val(vals, w, Kind::Length)?;
|
||||
@@ -3485,15 +3487,15 @@ pub(crate) fn extent_sides(vals: &BTreeMap<String, Value>, extent: &crate::Exten
|
||||
/// Operation and targets of a feature that makes tools.
|
||||
fn feature_op(f: &Feature) -> (Operation, Vec<String>) {
|
||||
match &f.kind {
|
||||
FeatureKind::Extrude { operation, targets, .. } | FeatureKind::Revolve { operation, targets, .. } => (*operation, targets.clone()),
|
||||
FeatureKind::Extrude { operation, targets, .. } | FeatureKind::Revolve { operation, targets, .. } => (*operation, crate::plain(targets)),
|
||||
FeatureKind::Box { operation, .. }
|
||||
| FeatureKind::Cylinder { operation, .. }
|
||||
| FeatureKind::Sphere { operation, .. }
|
||||
| FeatureKind::Torus { operation, .. } => (*operation, Vec::new()),
|
||||
FeatureKind::Hole { .. } => (Operation::Cut, Vec::new()),
|
||||
FeatureKind::Pipe { operation, targets, .. } => (*operation, targets.clone()),
|
||||
FeatureKind::Loft { operation, targets, .. } | FeatureKind::Sweep { operation, targets, .. } => (*operation, targets.clone()),
|
||||
FeatureKind::Coil { operation, targets, .. } => (*operation, targets.clone()),
|
||||
FeatureKind::Pipe { operation, targets, .. } => (*operation, crate::plain(targets)),
|
||||
FeatureKind::Loft { operation, targets, .. } | FeatureKind::Sweep { operation, targets, .. } => (*operation, crate::plain(targets)),
|
||||
FeatureKind::Coil { operation, targets, .. } => (*operation, crate::plain(targets)),
|
||||
_ => (Operation::NewBody, Vec::new()),
|
||||
}
|
||||
}
|
||||
@@ -3583,7 +3585,7 @@ fn pattern_transforms(doc: &Document, vals: &BTreeMap<String, Value>, st: &Model
|
||||
crate::PatternKind::Path { path_sketch, path, count: c, spacing, extent, flip, orient } => {
|
||||
let n = count(c)?;
|
||||
let d = val(vals, spacing, Kind::Length)?;
|
||||
let ps = st.sketch(*path_sketch).ok_or_else(|| DocError::Unknown(format!("path sketch {path_sketch}")))?;
|
||||
let ps = st.sketch(path_sketch.0).ok_or_else(|| DocError::Unknown(format!("path sketch {path_sketch}")))?;
|
||||
let mapped = path_in_frame(doc, ps, component)?;
|
||||
let ps = mapped.as_ref();
|
||||
let mut segs = path_segments(ps, path, Vec3::new(f64::NAN, f64::NAN, f64::NAN))?;
|
||||
@@ -3625,7 +3627,7 @@ fn pattern_transforms(doc: &Document, vals: &BTreeMap<String, Value>, st: &Model
|
||||
0.0
|
||||
};
|
||||
for i in 1..n {
|
||||
out.push(rotation(*origin, *axis, step * i as f64));
|
||||
out.push(rotation(origin.0, axis.0, step * i as f64));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3722,7 +3724,7 @@ fn replay(
|
||||
let r = val(vals, radius, Kind::Length)?;
|
||||
// The edges the feature blended in this evaluation (found by name), falling back to
|
||||
// its picked points.
|
||||
let found = st.blend_edges.get(&src.id).cloned().unwrap_or_else(|| edges.clone());
|
||||
let found = st.blend_edges.get(&src.id).cloned().unwrap_or_else(|| crate::plain(edges));
|
||||
for m in mats {
|
||||
let moved: Vec<Vec3> = found.iter().map(|p| crate::apply_point(m, *p)).collect();
|
||||
let ti = blend_target(st, &None, &moved)?;
|
||||
@@ -3757,7 +3759,7 @@ fn replay(
|
||||
let d = crate::apply_vector(m, dir).normalized().ok_or_else(|| DocError::Invalid("hole direction".into()))?;
|
||||
for p in &spots {
|
||||
let at = hole_on_surface(st, crate::apply_point(m, *p), d);
|
||||
let to = to.map(|t| crate::apply_point(m, t));
|
||||
let to = to.map(|t| crate::apply_point(m, t.0));
|
||||
let depth = hole_depth_to(st, &at, d, &to, depth)?;
|
||||
// Each copy's faces are named as the hole's, with the instance.
|
||||
copies.extend(
|
||||
@@ -4085,8 +4087,8 @@ fn chamfer_with(
|
||||
b: &kernel::Body,
|
||||
edges: &[Vec3],
|
||||
d: f64,
|
||||
distance2: &Option<String>,
|
||||
angle: &Option<String>,
|
||||
distance2: &Option<crate::Expr>,
|
||||
angle: &Option<crate::Expr>,
|
||||
flip: bool,
|
||||
) -> Result<kernel::Body> {
|
||||
let side = match (distance2, angle) {
|
||||
@@ -4102,7 +4104,9 @@ fn chamfer_with(
|
||||
|
||||
fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st: &mut ModelState, warning: &mut Option<String>) -> Result<()> {
|
||||
match &f.kind {
|
||||
FeatureKind::BodyTransfer { bodies, from, to, transform } => crate::body_transfer::evaluate(doc, st, bodies, *from, *to, *transform),
|
||||
FeatureKind::BodyTransfer { bodies, from, to, transform } => {
|
||||
crate::body_transfer::evaluate(doc, st, &crate::plain(bodies), *from, *to, *transform)
|
||||
}
|
||||
FeatureKind::SheetBase { .. }
|
||||
| FeatureKind::SheetContour { .. }
|
||||
| FeatureKind::SheetFlange { .. }
|
||||
@@ -4189,12 +4193,12 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
// Cuts through sheet metal become cut-outs of its flat pattern.
|
||||
if matches!(operation, Operation::Cut | Operation::Intersect) && matches!(f.kind, FeatureKind::Extrude { .. }) && !st.sheets.is_empty() {
|
||||
let bodies: Vec<Body> = tools.iter().map(|t| t.body.clone()).collect();
|
||||
let handled = more::sheet_cut(f, st, &bodies, targets, constructor.as_ref(), warning)?;
|
||||
let handled = more::sheet_cut(f, st, &bodies, &crate::plain(targets), constructor.as_ref(), warning)?;
|
||||
if !handled.is_empty() {
|
||||
return apply_beside_sheet_cut(st, f, tools, *operation, targets, &handled);
|
||||
return apply_beside_sheet_cut(st, f, tools, *operation, &crate::plain(targets), &handled);
|
||||
}
|
||||
}
|
||||
apply_traced(st, f, tools, *operation, targets)
|
||||
apply_traced(st, f, tools, *operation, &crate::plain(targets))
|
||||
}
|
||||
FeatureKind::Hole { thread, diameter, .. } => {
|
||||
let (spots, dir) = hole_spots(st, f)?;
|
||||
@@ -4222,14 +4226,14 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
Some(e) => Some(val(vals, e, Kind::Length)?),
|
||||
None => None,
|
||||
};
|
||||
let t = thread_on(st, f.id, *face, designation.as_deref(), l)?;
|
||||
let t = thread_on(st, f.id, face.0, designation.as_deref(), l)?;
|
||||
if !*modeled {
|
||||
st.threads.push(t);
|
||||
return Ok(());
|
||||
}
|
||||
let i = st.bodies.iter().position(|b| b.name == t.body).ok_or_else(|| DocError::Invalid("threaded body".into()))?;
|
||||
let Some(mb) = st.bodies.get(i).cloned() else { return Err(DocError::Invalid("body".into())) };
|
||||
let cyl = kernel::cylinder_face_at(&mb.body, *face).ok_or_else(|| DocError::Invalid("no cylindrical face there to thread".into()))?;
|
||||
let cyl = kernel::cylinder_face_at(&mb.body, face.0).ok_or_else(|| DocError::Invalid("no cylindrical face there to thread".into()))?;
|
||||
let tol = t.pitch * 1e-3;
|
||||
let groove = thread_groove(&t, cyl.radius, ((t.start - cyl.start).abs() < tol, (t.end - cyl.end).abs() < tol))?;
|
||||
// The B-rep boolean can't take helical walls in reasonable time: the body is cut
|
||||
@@ -4247,9 +4251,9 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
}
|
||||
FeatureKind::Shell { faces, thickness, body, direction, tangent_chain } => {
|
||||
let t = val(vals, thickness, Kind::Length)?;
|
||||
let i = body_at(st, body, faces)?;
|
||||
let i = body_at(st, body, &crate::plain(faces))?;
|
||||
let Some(mb) = st.bodies.get(i).cloned() else { return Err(DocError::Invalid("body".into())) };
|
||||
let open = if *tangent_chain { smooth_chain(&mb, faces) } else { faces.clone() };
|
||||
let open = if *tangent_chain { smooth_chain(&mb, &crate::plain(faces)) } else { crate::plain(faces) };
|
||||
let input = mb.traced();
|
||||
// The shell, and the operands of the operation that made it.
|
||||
let (nb, operands) = match direction.as_str() {
|
||||
@@ -4286,9 +4290,9 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
FeatureKind::Draft { faces, angle, neutral, pull, body } => {
|
||||
let a = val(vals, angle, Kind::Angle)?;
|
||||
let pl = doc.resolve_plane_in(vals, neutral, 0, Some(st))?;
|
||||
let i = body_at(st, body, faces)?;
|
||||
let i = body_at(st, body, &crate::plain(faces))?;
|
||||
let Some(mb) = st.bodies.get(i).cloned() else { return Err(DocError::Invalid("body".into())) };
|
||||
let nb = Traced::step(kernel::draft(&mb.body, faces, &pl, *pull, a)?, &[&mb.traced()]);
|
||||
let nb = Traced::step(kernel::draft(&mb.body, &crate::plain(faces), &pl, pull.0, a)?, &[&mb.traced()]);
|
||||
if let Some(slot) = st.bodies.get_mut(i) {
|
||||
*slot = ModelBody::traced_new(mb.name, nb, mb.feature);
|
||||
}
|
||||
@@ -4332,7 +4336,7 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
let mut made = Vec::new();
|
||||
for p in cells {
|
||||
// The cell: inside the tools that hold the point, outside the others.
|
||||
let inside: Vec<bool> = tb.iter().map(|b| b.mesh().contains(*p)).collect();
|
||||
let inside: Vec<bool> = tb.iter().map(|b| b.mesh().contains(p.0)).collect();
|
||||
let mut acc: Option<Traced> = None;
|
||||
for (b, _) in tb.iter().zip(&inside).filter(|(_, i)| **i) {
|
||||
acc = match acc {
|
||||
@@ -4350,16 +4354,16 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
made.push(cell);
|
||||
}
|
||||
if *remove_tools {
|
||||
st.bodies.retain(|b| !tools.contains(&b.name));
|
||||
st.bodies.retain(|b| !tools.iter().any(|t| *t == b.name));
|
||||
}
|
||||
apply_traced(st, f, made, *operation, &[])
|
||||
}
|
||||
FeatureKind::SplitFace { faces, body, plane, tool, sketch } => {
|
||||
let i = body_at(st, body, faces)?;
|
||||
let i = body_at(st, body, &crate::plain(faces))?;
|
||||
let Some(mb) = st.bodies.get(i).cloned() else { return Err(DocError::Invalid("body".into())) };
|
||||
let mut picked: Vec<usize> = Vec::new();
|
||||
for p in faces {
|
||||
let fi = crate::appearance::face_index_at(&mb, *p)
|
||||
let fi = crate::appearance::face_index_at(&mb, p.0)
|
||||
.ok_or_else(|| DocError::Invalid(format!("Split Face: no face of {} at {p:?}", mb.name)))?;
|
||||
if !picked.contains(&fi) {
|
||||
picked.push(fi);
|
||||
@@ -4368,12 +4372,12 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
let nb = match (tool, sketch) {
|
||||
(Some(t), _) => {
|
||||
let tb = st.body(&t.body).ok_or_else(|| DocError::Unknown(format!("body `{}`", t.body)))?;
|
||||
let face = crate::appearance::face_index_at(tb, t.point)
|
||||
let face = crate::appearance::face_index_at(tb, t.point.0)
|
||||
.ok_or_else(|| DocError::Invalid(format!("no face of `{}` at the tool point", t.body)))?;
|
||||
kernel::split_faces(&mb.body, &picked, &kernel::SplitTool::Face { body: &tb.body, face })?
|
||||
}
|
||||
(None, Some(sid)) => {
|
||||
let ss = st.sketch(*sid).ok_or_else(|| DocError::Unknown(format!("sketch {sid}")))?;
|
||||
let ss = st.sketch(sid.0).ok_or_else(|| DocError::Unknown(format!("sketch {sid}")))?;
|
||||
// Each curve's exact segments (lines and arcs split by their own plane and cylinder).
|
||||
let curves: Vec<Vec<solidcraft_geom::Seg2>> = (0..ss.sketch.curves.len())
|
||||
.filter(|ci| ss.sketch.curves.get(*ci).is_some_and(|c| !c.construction))
|
||||
@@ -4403,7 +4407,7 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
// A face of a body (its surface extended).
|
||||
Some(t) => {
|
||||
let tb = st.body(&t.body).ok_or_else(|| DocError::Unknown(format!("body `{}`", t.body)))?;
|
||||
let face = crate::appearance::face_index_at(tb, t.point)
|
||||
let face = crate::appearance::face_index_at(tb, t.point.0)
|
||||
.ok_or_else(|| DocError::Invalid(format!("no face of `{}` at the tool point", t.body)))?;
|
||||
kernel::split_body(&mb.body, &kernel::SplitTool::Face { body: &tb.body, face })?
|
||||
}
|
||||
@@ -4432,7 +4436,7 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
FeatureKind::Pattern { features, pattern, bodies } => {
|
||||
let mats = pattern_transforms(doc, vals, st, f.component, pattern)?;
|
||||
if bodies.is_empty() {
|
||||
return replay(doc, vals, f, st, features, &mats, warning);
|
||||
return replay(doc, vals, f, st, &crate::plain(features), &mats, warning);
|
||||
}
|
||||
// Bodies: each copy is a new body named after its source.
|
||||
let mut output = 0;
|
||||
@@ -4450,7 +4454,7 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
FeatureKind::Mirror { features, plane, bodies, combine } => {
|
||||
let pl = doc.resolve_plane_in(vals, plane, 0, Some(st))?;
|
||||
if bodies.is_empty() {
|
||||
return replay(doc, vals, f, st, features, &[mirror_matrix(&pl)], warning);
|
||||
return replay(doc, vals, f, st, &crate::plain(features), &[mirror_matrix(&pl)], warning);
|
||||
}
|
||||
let m = mirror_matrix(&pl);
|
||||
let mut output = 0;
|
||||
@@ -4485,13 +4489,13 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
}
|
||||
FeatureKind::Fillet { edges, radius, body, .. } | FeatureKind::Chamfer { edges, distance: radius, body, .. } => {
|
||||
let r = val(vals, radius, Kind::Length)?;
|
||||
let ti = blend_target(st, body, edges)?;
|
||||
let ti = blend_target(st, body, &crate::plain(edges))?;
|
||||
let Some(mb) = st.bodies.get(ti) else { return Err(DocError::Invalid("body".into())) };
|
||||
let edges = if f.edge_names.iter().any(|n| !n.is_empty()) {
|
||||
// By name first; a name that is gone falls back to the point, with a warning.
|
||||
let (mut out, mut notes) = (Vec::new(), Vec::new());
|
||||
for (i, p) in edges.iter().enumerate() {
|
||||
match f.edge_names.get(i).filter(|n| !n.is_empty()).map(|n| (n, crate::naming::locate_edge(mb, n, *p))) {
|
||||
match f.edge_names.get(i).filter(|n| !n.is_empty()).map(|n| (n, crate::naming::locate_edge(mb, n, p.0))) {
|
||||
Some((n, Ok(hit))) => {
|
||||
if let Some(note) = hit.note(n) {
|
||||
notes.push(note);
|
||||
@@ -4506,9 +4510,9 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
// The name is gone. A pick on the axis of a round must not fall
|
||||
// through to the tangency seam (#775). A sharp edge that moved
|
||||
// still resolves from the point, which an edit relies on.
|
||||
if pick_lies_on_a_round_axis(&mb.body, *p) {
|
||||
if pick_lies_on_a_round_axis(&mb.body, p.0) {
|
||||
notes.push(skipped);
|
||||
} else if let Ok(q) = resolve_edges(&mb.body, &[*p], &refs, &mut w) {
|
||||
} else if let Ok(q) = resolve_edges(&mb.body, &[p.0], &refs, &mut w) {
|
||||
out.extend(q);
|
||||
notes.push(match f.edge_names.get(i) {
|
||||
Some(n) if !n.is_empty() => format!("edge `{n}` no longer exists; using the edge at the picked point"),
|
||||
@@ -4529,7 +4533,7 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
}
|
||||
out
|
||||
} else {
|
||||
resolve_edges(&mb.body, edges, &f.edge_refs, warning)?
|
||||
resolve_edges(&mb.body, &crate::plain(edges), &f.edge_refs, warning)?
|
||||
};
|
||||
st.blend_edges.insert(f.id, edges.clone());
|
||||
let input = mb.traced();
|
||||
@@ -4543,7 +4547,7 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
for (t, e) in mid {
|
||||
pts.push((*t, val(vals, e, Kind::Length)?));
|
||||
}
|
||||
one_by_one(input, &edges, |b, p| kernel::fillet_variable_points(b, p, *start, &pts, *smooth))?
|
||||
one_by_one(input, &edges, |b, p| kernel::fillet_variable_points(b, p, start.0, &pts, *smooth))?
|
||||
}
|
||||
FeatureKind::Fillet { style: crate::FilletStyle::Spline { start, knots, radii }, .. } => {
|
||||
let mut values = vec![r];
|
||||
@@ -4551,7 +4555,7 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
values.push(val(vals, e, Kind::Length)?);
|
||||
}
|
||||
let law = crate::FilletStyle::spline(knots, values);
|
||||
one_by_one(input, &edges, |b, p| kernel::fillet_variable_spline(b, p, *start, &law))?
|
||||
one_by_one(input, &edges, |b, p| kernel::fillet_variable_spline(b, p, start.0, &law))?
|
||||
}
|
||||
FeatureKind::Fillet { fit: crate::FilletFit::Taper, .. } => {
|
||||
let (b, tapers) = kernel::fillet_tapered(&mb.body, &edges, r)?;
|
||||
@@ -4598,9 +4602,9 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
}
|
||||
FeatureKind::Cylinder { base, axis, radius, height, operation } => {
|
||||
let (base, radius, height) = (point(vals, base)?, val(vals, radius, Kind::Length)?, val(vals, height, Kind::Length)?);
|
||||
let b = kernel::cylinder(base, *axis, radius, height)?;
|
||||
let b = kernel::cylinder(base, axis.0, radius, height)?;
|
||||
let b = if matches!(operation, Operation::NewBody | Operation::Join) { b.with_cylinder_certificate(f.id)? } else { b };
|
||||
let prism = more::PrimitivePrism::Cylinder { base, axis: *axis, radius, height };
|
||||
let prism = more::PrimitivePrism::Cylinder { base, axis: axis.0, radius, height };
|
||||
let handled = more::sheet_primitive_cut(f, st, &b, prism, *operation, false, warning)?;
|
||||
let tools = vec![crate::lineage::cylinder_tool(b)];
|
||||
if !handled.is_empty() {
|
||||
@@ -4628,9 +4632,9 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
return Err(DocError::Unknown(format!("body `{target}`")));
|
||||
}
|
||||
let op = if *operation == Operation::NewBody { Operation::Join } else { *operation };
|
||||
apply_traced(st, f, tb, op, std::slice::from_ref(target))?;
|
||||
apply_traced(st, f, tb, op, std::slice::from_ref(&target.0))?;
|
||||
if !keep_tools {
|
||||
st.bodies.retain(|b| !tools.contains(&b.name));
|
||||
st.bodies.retain(|b| !tools.iter().any(|t| *t == b.name));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -4664,20 +4668,20 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
}
|
||||
FeatureKind::OffsetFace { faces, distance, body } => {
|
||||
let d = val(vals, distance, Kind::Length)?;
|
||||
let i = body_at(st, body, faces)?;
|
||||
let i = body_at(st, body, &crate::plain(faces))?;
|
||||
let Some(mb) = st.bodies.get(i).cloned() else { return Err(DocError::Invalid("body".into())) };
|
||||
let nb = Traced::step(kernel::offset_faces(&mb.body, faces, d)?, &[&mb.traced()]);
|
||||
let nb = Traced::step(kernel::offset_faces(&mb.body, &crate::plain(faces), d)?, &[&mb.traced()]);
|
||||
if let Some(slot) = st.bodies.get_mut(i) {
|
||||
*slot = ModelBody::traced_new(mb.name, nb, mb.feature);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
FeatureKind::DeleteFace { faces, body, heal } => {
|
||||
let i = body_at(st, body, faces)?;
|
||||
let i = body_at(st, body, &crate::plain(faces))?;
|
||||
let Some(mb) = st.bodies.get(i).cloned() else { return Err(DocError::Invalid("body".into())) };
|
||||
let mut picked: Vec<usize> = Vec::new();
|
||||
for p in faces {
|
||||
let fi = crate::appearance::face_index_at(&mb, *p)
|
||||
let fi = crate::appearance::face_index_at(&mb, p.0)
|
||||
.ok_or_else(|| DocError::Invalid(format!("Delete Face: no face of {} at {p:?}", mb.name)))?;
|
||||
if !picked.contains(&fi) {
|
||||
picked.push(fi);
|
||||
@@ -4694,7 +4698,7 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
FeatureKind::BoundingSolid { bodies, margin } => {
|
||||
let m = val(vals, margin, Kind::Length)?;
|
||||
let mut bb = solidcraft_geom::Aabb3::EMPTY;
|
||||
for b in st.bodies.iter().filter(|b| bodies.is_empty() || bodies.contains(&b.name)) {
|
||||
for b in st.bodies.iter().filter(|b| bodies.is_empty() || bodies.iter().any(|t| *t == b.name)) {
|
||||
bb = bb.union(&b.mesh().bounds());
|
||||
}
|
||||
if bb.is_empty() {
|
||||
@@ -4713,7 +4717,7 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
Some(a) => val(vals, a, Kind::Angle)?,
|
||||
None => 0.0,
|
||||
};
|
||||
let axis = rotate_axis.unwrap_or(Vec3::Z);
|
||||
let axis = rotate_axis.map(Vec3::from).unwrap_or(Vec3::Z);
|
||||
// With `copy` set, the source body is kept in place and the transform lands on a new
|
||||
// body pushed beside it instead of replacing it.
|
||||
let mut output = 0;
|
||||
@@ -5215,7 +5219,7 @@ mod chain_tests {
|
||||
.unwrap();
|
||||
doc.add_feature(
|
||||
FeatureKind::Fillet {
|
||||
edges: vec![Vec3::new(20.0, 0.0, 20.0)],
|
||||
edges: vec![crate::Position(Vec3::new(20.0, 0.0, 20.0))],
|
||||
radius: "4".into(),
|
||||
body: None,
|
||||
style: crate::FilletStyle::Constant,
|
||||
|
||||
Reference in new issue
Block a user