//! Construction geometry from the model: references to vertices, edges, faces, planes, axes, //! points and sketch entities, resolved on each evaluation, and the axes, points and planes //! built from them (Fusion's CONSTRUCT panel). //! //! Face and edge references carry the persistent name of what was picked (see `naming`), so //! they follow the model when earlier features change; the picked point is the fallback. use std::collections::BTreeMap; use serde::{Deserialize, Serialize}; use solidcraft_geom::{Plane, Vec2, Vec3}; use solidcraft_kernel::{self as kernel, FaceSurface}; use crate::eval::{ModelBody, ModelState}; use crate::expr::{Kind, Value}; use crate::{DocError, Document, PlaneRef, Result}; /// Something picked to build construction geometry from. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] pub enum GeoRef { /// A fixed point. Point { p: Vec3 }, /// The design origin. Origin, /// A body vertex (the one nearest `at`). Vertex { at: Vec3 }, /// A body edge: the one named `name`, else the one nearest `at`. Edge { at: Vec3, #[serde(default, skip_serializing_if = "Option::is_none")] name: Option, }, /// A body face: the one named `name`, else the one at `at`. Face { at: Vec3, #[serde(default, skip_serializing_if = "Option::is_none")] name: Option, }, /// A plane (origin, construction, offset…). Plane { plane: PlaneRef }, /// World X, Y or Z axis. World { axis: String }, /// A construction axis feature, by name. Axis { name: String }, /// A construction point feature, by name. ConstructPoint { name: String }, /// A sketch point (by sketch feature id and point id). SketchPoint { sketch: u64, point: String }, /// A sketch curve (by sketch feature id and curve id). SketchCurve { sketch: u64, curve: String }, } /// How a construction axis is defined. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] pub enum AxisDef { /// The axis of a cylinder, cone or torus face (or a circular edge). ThroughFace { face: GeoRef }, /// Perpendicular to a face, through a point (projected onto the face). NormalToFace { face: GeoRef, point: GeoRef }, /// Where two planes meet. TwoPlanes { a: GeoRef, b: GeoRef }, /// Through two points. TwoPoints { a: GeoRef, b: GeoRef }, /// Along a straight edge (or sketch line). Edge { edge: GeoRef }, } /// How a construction point is defined. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] pub enum PointDef { /// At a vertex, sketch point or fixed point. At { point: GeoRef }, /// Where two edges (lines) meet, or come closest. TwoEdges { a: GeoRef, b: GeoRef }, /// Where three planes meet. ThreePlanes { a: GeoRef, b: GeoRef, c: GeoRef }, /// The centre of a circular edge, a sphere or a torus. Center { of: GeoRef }, /// Where an edge (line) crosses a plane. EdgeAndPlane { edge: GeoRef, plane: GeoRef }, /// At a fraction `t` (0…1) of the way along an edge or sketch curve. AlongPath { path: GeoRef, t: String }, } /// Resolved construction geometry, kept in the model state by feature. #[derive(Clone, Debug, PartialEq)] pub enum ConstructGeom { Plane(Plane), Axis { origin: Vec3, dir: Vec3 }, Point(Vec3), } #[derive(Clone, Debug, PartialEq)] pub struct Construct { pub feature: u64, pub name: String, pub geom: ConstructGeom, } /// Everything a reference needs to be resolved. pub(crate) struct Ctx<'a> { pub doc: &'a Document, pub vals: &'a BTreeMap, pub st: &'a ModelState, /// How deep the plane resolution that led here already is. A plane built from model /// geometry resolves its sides through the document again, so the count has to continue /// across that hop; restarting it lets a plane that refers to itself recurse forever. pub depth: usize, } fn bad(m: impl Into) -> DocError { DocError::Invalid(m.into()) } fn bodies(st: &ModelState) -> impl Iterator { st.bodies.iter().filter(|b| !b.body.is_mesh()) } fn construct<'a>(st: &'a ModelState, name: &str) -> Result<&'a ConstructGeom> { st.construct.iter().find(|c| c.name == name).map(|c| &c.geom).ok_or_else(|| DocError::Unknown(format!("construction `{name}`"))) } /// The face picked: (body, face index, point on it). fn face_of<'a>(st: &'a ModelState, at: Vec3, name: &Option) -> Result<(&'a ModelBody, usize, Vec3)> { // The named face itself: its nearest point to the old pick can lie on an edge it shares // (a corner round that a parameter moved away from the pick), where picking again by the // point finds the flat face beside it (#1255). if let Some(hit) = name.as_deref().and_then(|n| crate::name_lookup::locate_face(st, n, at)) && let Some(b) = st.bodies.get(hit.body).filter(|b| !b.body.is_mesh()) && hit.face < b.body.face_count() { return Ok((b, hit.face, hit.point)); } for b in bodies(st) { if let Some(fi) = crate::appearance::face_index_at(b, at) { return Ok((b, fi, at)); } } Err(bad(format!("no face at {at:?}"))) } /// The edge picked, as a polyline. fn edge_points(st: &ModelState, at: Vec3, name: &Option) -> Result> { let mut at = at; if let Some(n) = name { for b in bodies(st) { if let Ok((q, _)) = crate::naming::find_edge(b, n, at) { at = q; break; } } } let mut best: Option<(f64, Vec)> = None; for b in bodies(st) { let tol = (b.body.size() * 1e-3).max(1e-3); let Ok(edges) = b.body.edges(tol) else { continue }; for e in edges { let d = e.points.windows(2).map(|w| at.dist_to_segment(w[0], w[1])).fold(f64::MAX, f64::min); if best.as_ref().is_none_or(|(bd, _)| d < *bd) { best = Some((d, e.points)); } } } match best { Some((d, pts)) if pts.len() >= 2 && d <= 1e-3 + 1e-2 * pts.iter().fold(0.0, |m: f64, p| m.max(p.len())).max(1.0) => Ok(pts), _ => Err(bad(format!("no edge at {at:?}"))), } } /// A polyline that is straight: its line. fn as_line(pts: &[Vec3]) -> Option<(Vec3, Vec3)> { let (a, b) = (*pts.first()?, *pts.last()?); let d = (b - a).normalized()?; let len = (b - a).len(); pts.iter().all(|p| p.dist_to_segment(a, b) <= len * 1e-6 + 1e-9).then_some((a, d)) } /// A polyline on a circle: (centre, unit normal, radius). fn as_circle(pts: &[Vec3]) -> Option<(Vec3, Vec3, f64)> { let n = pts.len(); if n < 4 { return None; } let (a, b, c) = (*pts.first()?, *pts.get(n / 3)?, *pts.get(2 * n / 3)?); let (ab, ac) = (b - a, c - a); let nrm = ab.cross(ac); let l2 = nrm.len2(); if l2 < 1e-24 { return None; } // Circumcentre of abc. let centre = a + (nrm.cross(ab) * ac.len2() + ac.cross(nrm) * ab.len2()) * (1.0 / (2.0 * l2)); let r = (a - centre).len(); let unit = nrm.normalized()?; pts.iter() .all(|p| ((*p - centre).len() - r).abs() <= r * 1e-3 + 1e-6 && (*p - centre).dot(unit).abs() <= r * 1e-3 + 1e-6) .then_some((centre, unit, r)) } fn sketch_world(ctx: &Ctx, sketch: u64, q: Vec2) -> Result { let ss = ctx.st.sketch(sketch).ok_or_else(|| DocError::Unknown(format!("sketch {sketch}")))?; Ok(ss.plane.to_world(q)) } fn sketch_curve_points(ctx: &Ctx, sketch: u64, curve: &str) -> Result> { let ss = ctx.st.sketch(sketch).ok_or_else(|| DocError::Unknown(format!("sketch {sketch}")))?; if let Some(wire) = ss.sketch.wires.iter().find(|wire| wire.id == curve) { if wire.pts.len() < 2 { return Err(bad(format!("curve `{curve}` has no length"))); } // Evaluation has already mapped wires into this component's frame. Their coordinates // are independent of the sketch plane; applying it again would move the path twice. return Ok(wire.pts.clone()); } let ci = ss.sketch.curve_index(curve).ok_or_else(|| DocError::Unknown(format!("curve `{curve}`")))?; let pl = ss.sketch.polyline(ci); if pl.len() < 2 { return Err(bad(format!("curve `{curve}` has no length"))); } Ok(pl.into_iter().map(|q| ss.plane.to_world(q)).collect()) } /// A reference as a point. pub(crate) fn point(ctx: &Ctx, g: &GeoRef) -> Result { match g { GeoRef::Point { p } => Ok(*p), GeoRef::Origin => Ok(Vec3::ZERO), GeoRef::Vertex { at } => { let mut best: Option<(f64, Vec3)> = None; for b in bodies(ctx.st) { let tol = (b.body.size() * 1e-3).max(1e-3); let Ok(edges) = b.body.edges(tol) else { continue }; for v in edges.iter().flat_map(|e| [e.points.first().copied(), e.points.last().copied()]).flatten() { let d = v.dist(*at); if best.is_none_or(|(bd, _)| d < bd) { best = Some((d, v)); } } } best.map(|(_, v)| v).ok_or_else(|| bad("there is no vertex")) } GeoRef::ConstructPoint { name } => match construct(ctx.st, name)? { ConstructGeom::Point(p) => Ok(*p), _ => Err(bad(format!("`{name}` is not a point"))), }, GeoRef::SketchPoint { sketch, point } => { let ss = ctx.st.sketch(*sketch).ok_or_else(|| DocError::Unknown(format!("sketch {sketch}")))?; let q = ss.sketch.point_index(point).and_then(|i| ss.sketch.point(i)).ok_or_else(|| DocError::Unknown(format!("sketch point `{point}`")))?; sketch_world(ctx, *sketch, q) } // A round edge or face: its centre. GeoRef::Edge { .. } | GeoRef::Face { .. } | GeoRef::SketchCurve { .. } => centre(ctx, g), _ => Err(bad("pick a point, vertex or sketch point")), } } /// A reference as a line (origin, unit direction). pub(crate) fn line(ctx: &Ctx, g: &GeoRef) -> Result<(Vec3, Vec3)> { match g { GeoRef::Edge { at, name } => { let pts = edge_points(ctx.st, *at, name)?; as_line(&pts).or_else(|| as_circle(&pts).map(|(c, n, _)| (c, n))).ok_or_else(|| bad("the edge is neither straight nor circular")) } GeoRef::SketchCurve { sketch, curve } => { let pts = sketch_curve_points(ctx, *sketch, curve)?; as_line(&pts).ok_or_else(|| bad(format!("sketch curve `{curve}` is not a line"))) } GeoRef::World { axis } => { let d = match axis.to_ascii_uppercase().as_str() { "X" => Vec3::X, "Y" => Vec3::Y, "Z" => Vec3::Z, _ => return Err(DocError::Unknown(format!("axis `{axis}`"))), }; Ok((Vec3::ZERO, d)) } GeoRef::Axis { name } => match construct(ctx.st, name)? { ConstructGeom::Axis { origin, dir } => Ok((*origin, *dir)), _ => Err(bad(format!("`{name}` is not an axis"))), }, GeoRef::Face { at, name } => { let (b, fi, _) = face_of(ctx.st, *at, name)?; match kernel::face_surface(&b.body, fi) { Some(FaceSurface::Cylinder { origin, axis, .. }) => Ok((origin, axis)), Some(FaceSurface::Cone { apex, axis, .. }) => Ok((apex, axis)), Some(FaceSurface::Torus { center, axis, .. }) => Ok((center, axis)), _ => Err(bad("the face is not a cylinder, cone or torus")), } } _ => Err(bad("pick an edge, line or axis")), } } /// A reference as a plane. pub(crate) fn plane(ctx: &Ctx, g: &GeoRef) -> Result { match g { GeoRef::Plane { plane } => ctx.doc.resolve_plane_in(ctx.vals, plane, ctx.depth + 1, Some(ctx.st)), GeoRef::Face { at, name } => { let (b, fi, q) = face_of(ctx.st, *at, name)?; match kernel::face_surface(&b.body, fi) { Some(FaceSurface::Plane { normal, point }) => { let o = q - normal * (q - point).dot(normal); Plane::from_normal(o, normal).ok_or_else(|| bad("degenerate face")) } _ => Err(bad("the face is not planar")), } } _ => Err(bad("pick a plane or planar face")), } } /// The centre of a round edge, sketch circle, sphere or torus. fn centre(ctx: &Ctx, g: &GeoRef) -> Result { match g { GeoRef::Edge { at, name } => { let pts = edge_points(ctx.st, *at, name)?; as_circle(&pts).map(|(c, _, _)| c).ok_or_else(|| bad("the edge is not circular")) } GeoRef::SketchCurve { sketch, curve } => { let pts = sketch_curve_points(ctx, *sketch, curve)?; as_circle(&pts).map(|(c, _, _)| c).ok_or_else(|| bad(format!("sketch curve `{curve}` is not a circle or arc"))) } GeoRef::Face { at, name } => { let (b, fi, _) = face_of(ctx.st, *at, name)?; match kernel::face_surface(&b.body, fi) { Some(FaceSurface::Sphere { center, .. }) | Some(FaceSurface::Torus { center, .. }) => Ok(center), _ => Err(bad("the face is not a sphere or torus")), } } _ => Err(bad("pick a circular edge, a sphere or a torus")), } } /// Point and unit tangent at fraction `t` along a polyline. fn along(pts: &[Vec3], t: f64) -> Option<(Vec3, Vec3)> { let total: f64 = pts.windows(2).map(|w| (w[1] - w[0]).len()).sum(); if !(total > 0.0) { return None; } let mut left = t.clamp(0.0, 1.0) * total; for w in pts.windows(2) { let l = (w[1] - w[0]).len(); if l <= 0.0 { continue; } if left <= l { return Some((w[0] + (w[1] - w[0]) * (left / l), (w[1] - w[0]) * (1.0 / l))); } left -= l; } let (a, b) = (pts.get(pts.len() - 2)?, pts.last()?); Some((*b, (*b - *a).normalized()?)) } fn path_points(ctx: &Ctx, g: &GeoRef) -> Result> { match g { GeoRef::Edge { at, name } => edge_points(ctx.st, *at, name), GeoRef::SketchCurve { sketch, curve } => sketch_curve_points(ctx, *sketch, curve), _ => Err(bad("pick an edge or sketch curve as the path")), } } /// The surface's unit normal at (the point of the surface nearest) `p`, and that point. fn surface_normal(s: FaceSurface, p: Vec3) -> Option<(Vec3, Vec3)> { match s { FaceSurface::Plane { normal, point } => Some((p - normal * (p - point).dot(normal), normal)), FaceSurface::Cylinder { origin, axis, radius } => { let w = p - origin; let r = (w - axis * w.dot(axis)).normalized()?; Some((origin + axis * w.dot(axis) + r * radius, r)) } FaceSurface::Sphere { center, radius } => { let r = (p - center).normalized()?; Some((center + r * radius, r)) } FaceSurface::Cone { apex, axis, half_angle } => { let w = p - apex; let radial = (w - axis * w.dot(axis)).normalized()?; // The generator through p's side, and the normal square to it in the axial plane. let line = axis * half_angle.cos() + radial * half_angle.sin(); let n = (radial * half_angle.cos() - axis * half_angle.sin()).normalized()?; Some((apex + line * w.dot(line), n)) } FaceSurface::Torus { center, axis, major, minor } => { let w = p - center; let radial = (w - axis * w.dot(axis)).normalized()?; let tube = center + radial * major; let n = (p - tube).normalized()?; Some((tube + n * minor, n)) } } } fn face_surface_at(ctx: &Ctx, g: &GeoRef) -> Result<(FaceSurface, Vec3)> { match g { GeoRef::Face { at, name } => { let (b, fi, q) = face_of(ctx.st, *at, name)?; kernel::face_surface(&b.body, fi).map(|s| (s, q)).ok_or_else(|| bad("the face's surface is not a plane, cylinder, cone, sphere or torus")) } GeoRef::Plane { .. } => { let pl = plane(ctx, g)?; Ok((FaceSurface::Plane { normal: pl.normal(), point: pl.origin }, pl.origin)) } _ => Err(bad("pick a face")), } } /// Where two planes meet. fn plane_plane(a: &Plane, b: &Plane) -> Option<(Vec3, Vec3)> { let (n1, n2) = (a.normal(), b.normal()); let d = n1.cross(n2); let l2 = d.len2(); if l2 < 1e-18 { return None; } let (h1, h2) = (n1.dot(a.origin), n2.dot(b.origin)); let o = (n2.cross(d) * h1 + d.cross(n1) * h2) * (1.0 / l2); Some((o, d.normalized()?)) } /// Closest points of two lines: their midpoint (where they meet, if they do). fn line_line(a: (Vec3, Vec3), b: (Vec3, Vec3)) -> Option { let ((p, u), (q, v)) = (a, b); let w = p - q; let (uu, uv, vv, uw, vw) = (u.dot(u), u.dot(v), v.dot(v), u.dot(w), v.dot(w)); let den = uu * vv - uv * uv; if den.abs() < 1e-12 { return None; } let s = (uv * vw - vv * uw) / den; let t = (uu * vw - uv * uw) / den; Some(((p + u * s) + (q + v * t)) * 0.5) } pub(crate) fn resolve_axis(ctx: &Ctx, d: &AxisDef) -> Result<(Vec3, Vec3)> { match d { AxisDef::ThroughFace { face } => line(ctx, face), AxisDef::Edge { edge } => { let (o, dir) = line(ctx, edge)?; if let GeoRef::Edge { at, name } = edge && as_line(&edge_points(ctx.st, *at, name)?).is_none() { return Err(bad("the edge is not straight")); } Ok((o, dir)) } AxisDef::NormalToFace { face, point: p } => { let (s, _) = face_surface_at(ctx, face)?; let q = point(ctx, p)?; let (on, n) = surface_normal(s, q).ok_or_else(|| bad("the point is on the face's axis"))?; Ok((on, n)) } AxisDef::TwoPlanes { a, b } => plane_plane(&plane(ctx, a)?, &plane(ctx, b)?).ok_or_else(|| bad("the planes are parallel")), AxisDef::TwoPoints { a, b } => { let (p, q) = (point(ctx, a)?, point(ctx, b)?); Ok((p, (q - p).normalized().ok_or_else(|| bad("the two points coincide"))?)) } } } pub(crate) fn resolve_point(ctx: &Ctx, d: &PointDef) -> Result { match d { PointDef::At { point: p } => point(ctx, p), PointDef::Center { of } => centre(ctx, of), PointDef::TwoEdges { a, b } => line_line(line(ctx, a)?, line(ctx, b)?).ok_or_else(|| bad("the edges are parallel")), PointDef::ThreePlanes { a, b, c } => { let (pa, pb, pc) = (plane(ctx, a)?, plane(ctx, b)?, plane(ctx, c)?); let (o, dir) = plane_plane(&pa, &pb).ok_or_else(|| bad("two of the planes are parallel"))?; line_plane(o, dir, &pc).ok_or_else(|| bad("the planes don't meet in a point")) } PointDef::EdgeAndPlane { edge, plane: p } => { let (o, dir) = line(ctx, edge)?; line_plane(o, dir, &plane(ctx, p)?).ok_or_else(|| bad("the edge is parallel to the plane")) } PointDef::AlongPath { path, t } => { let t = Document::eval_in(ctx.vals, t, Kind::Unitless)?; along(&path_points(ctx, path)?, t).map(|(p, _)| p).ok_or_else(|| bad("the path has no length")) } } } fn line_plane(o: Vec3, d: Vec3, p: &Plane) -> Option { let n = p.normal(); let den = d.dot(n); if den.abs() < 1e-12 { return None; } Some(o + d * ((p.origin - o).dot(n) / den)) } /// Planes built from model geometry (the `PlaneRef` kinds that need the model). pub(crate) fn resolve_geo_plane(ctx: &Ctx, p: &PlaneRef) -> Result { let degenerate = || bad("degenerate plane"); match p { PlaneRef::Midplane { a, b } => { let (pa, pb) = (plane(ctx, a)?, plane(ctx, b)?); let (na, nb) = (pa.normal(), pb.normal()); if na.cross(nb).len() < 1e-9 { // Parallel: halfway between, facing as the first. let half = nb * ((pb.origin - pa.origin).dot(nb) * 0.5); return Plane::new(pa.origin + half, pa.x, pa.y).ok_or_else(degenerate); } // Meeting at an angle: the plane through their line that halves the angle. let (o, dir) = plane_plane(&pa, &pb).ok_or_else(degenerate)?; let n = (na - nb).normalized().ok_or_else(degenerate)?; Plane::new(o, dir, n.cross(dir)).ok_or_else(degenerate) } PlaneRef::TwoEdges { a, b } => { let ((o1, d1), (o2, d2)) = (line(ctx, a)?, line(ctx, b)?); let n = match d1.cross(d2).normalized() { Some(n) if (o2 - o1).dot(n).abs() <= 1e-6 * (1.0 + (o2 - o1).len()) => n, Some(_) => return Err(bad("the edges are not in one plane")), None => d1.cross(o2 - o1).normalized().ok_or_else(|| bad("the edges are on one line"))?, }; Plane::new(o1, d1, n.cross(d1)).ok_or_else(degenerate) } PlaneRef::ThreePoints { a, b, c } => { let (pa, pb, pc) = (point(ctx, a)?, point(ctx, b)?, point(ctx, c)?); if (pb - pa).cross(pc - pa).len() < 1e-9 { return Err(bad("the three points are on one line")); } Plane::new(pa, pb - pa, pc - pa).ok_or_else(degenerate) } PlaneRef::Tangent { face, at } => { let (s, _) = face_surface_at(ctx, face)?; let (on, n) = surface_normal(s, *at).ok_or_else(|| bad("the point is on the face's axis"))?; Plane::from_normal(on, n).ok_or_else(degenerate) } PlaneRef::Perpendicular { plane: base, line: l } => { let pl = plane(ctx, base)?; let (o, d) = line(ctx, l)?; let n = d .cross(pl.normal()) .normalized() .ok_or_else(|| bad("the line is square to the plane: every plane through it is perpendicular; pick a line in or along the plane"))?; Plane::new(o, d, n.cross(d)).ok_or_else(degenerate) } PlaneRef::AlongPath { path, t } => { let t = Document::eval_in(ctx.vals, t, Kind::Unitless)?; let (p, tan) = along(&path_points(ctx, path)?, t).ok_or_else(|| bad("the path has no length"))?; Plane::from_normal(p, tan).ok_or_else(degenerate) } _ => ctx.doc.resolve_plane_in(ctx.vals, p, ctx.depth + 1, Some(ctx.st)), } } impl GeoRef { /// Map the reference's world points (component frames). pub fn map_points(&mut self, pt: &mut dyn FnMut(&mut Vec3), plane: &mut dyn FnMut(&mut PlaneRef)) { match self { GeoRef::Point { p } => pt(p), GeoRef::Vertex { at } | GeoRef::Edge { at, .. } | GeoRef::Face { at, .. } => pt(at), GeoRef::Plane { plane: p } => plane(p), _ => {} } } /// Picked faces and edges get their persistent names (and only those still missing one). pub fn name_picks(&mut self, st: &ModelState) { match self { GeoRef::Face { at, name: name @ None } => { *name = crate::naming::face_names_at(st, &[*at]).into_iter().next().filter(|n| !n.is_empty()); } GeoRef::Edge { at, name: name @ None } => { for b in bodies(st) { let tol = (b.body.size() * 1e-3).max(1e-3); if b.body.nearest_edge(*at, tol).ok().flatten().is_some_and(|(_, d)| d <= tol * 10.0) { *name = crate::naming::edge_names_at(b, &[*at]).into_iter().next().filter(|n| !n.is_empty()); break; } } } GeoRef::Plane { plane } => plane.name_picks(st), _ => {} } } } impl AxisDef { pub fn refs(&self) -> Vec<&GeoRef> { match self { AxisDef::ThroughFace { face } => vec![face], AxisDef::Edge { edge } => vec![edge], AxisDef::NormalToFace { face, point } => vec![face, point], AxisDef::TwoPlanes { a, b } | AxisDef::TwoPoints { a, b } => vec![a, b], } } pub fn refs_mut(&mut self) -> Vec<&mut GeoRef> { match self { AxisDef::ThroughFace { face } => vec![face], AxisDef::Edge { edge } => vec![edge], AxisDef::NormalToFace { face, point } => vec![face, point], AxisDef::TwoPlanes { a, b } | AxisDef::TwoPoints { a, b } => vec![a, b], } } } impl PointDef { pub fn refs(&self) -> Vec<&GeoRef> { match self { PointDef::At { point } => vec![point], PointDef::Center { of } => vec![of], PointDef::TwoEdges { a, b } => vec![a, b], PointDef::ThreePlanes { a, b, c } => vec![a, b, c], PointDef::EdgeAndPlane { edge, plane } => vec![edge, plane], PointDef::AlongPath { path, .. } => vec![path], } } pub fn refs_mut(&mut self) -> Vec<&mut GeoRef> { match self { PointDef::At { point } => vec![point], PointDef::Center { of } => vec![of], PointDef::TwoEdges { a, b } => vec![a, b], PointDef::ThreePlanes { a, b, c } => vec![a, b, c], PointDef::EdgeAndPlane { edge, plane } => vec![edge, plane], PointDef::AlongPath { path, .. } => vec![path], } } } impl PlaneRef { /// The references of a plane built from model geometry. pub fn refs_mut(&mut self) -> Vec<&mut GeoRef> { match self { PlaneRef::Midplane { a, b } | PlaneRef::TwoEdges { a, b } => vec![a.as_mut(), b.as_mut()], PlaneRef::Perpendicular { plane, line } => vec![plane.as_mut(), line.as_mut()], PlaneRef::ThreePoints { a, b, c } => vec![a.as_mut(), b.as_mut(), c.as_mut()], PlaneRef::Tangent { face, .. } => vec![face.as_mut()], PlaneRef::AlongPath { path, .. } => vec![path.as_mut()], PlaneRef::Offset { base, .. } | PlaneRef::AtAngle { base, .. } => base.refs_mut(), _ => Vec::new(), } } pub fn name_picks(&mut self, st: &ModelState) { for r in self.refs_mut() { r.name_picks(st); } } /// Does resolving it need the model (edges, faces, construction axes and points)? pub fn needs_model(&self) -> bool { let mut k = self.clone(); !k.refs_mut().is_empty() } }