mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-04 02:55:54 +00:00
doc: sweeps along linked and planar curves follow the exact curves
A sweep path through 3D sketch curves sampled every planar sketch curve in it into a 0.01 mm polyline, and took every linked 3D curve as the points its link stored, so SMLib got a many-cornered polyline (and refused a smooth one). Now: - a planar sketch curve in such a path is its exact segments (PathPiece::Segs of PathSeg::of_sketch over exact_segs); - a linked curve whose source has an exact curve keeps it: Wire::exact holds the record curves (WireCurve: schema 4 curve + interval) end to end along the wire, set with the wire when the link is resolved: an included body edge its record edge (kernel::brep_record), an included sketch curve leaving the plane its segments (kernel::sketch_record_curve). Wire::map carries them with a placement. The sweep follows them through kernel::curve_path (line, arc, ellipse as rational quadratic spans, polyline, B-spline as Bézier spans). - curves known only as points (projected onto a face, intersection and iso curves: the link resolves them on the mesh) stay points. Kernel, for the sheet and blend ports: planar_face_record (a profile face as a schema 4 sheet record with exact pcurves), smlib_sweeps::chain_curves (a segment chain as SMLib curves), body_from_record (any record into SMLib, checked), sketch_record_curve; record curves from design files are checked in curve_path. Format: pending/wire-exact-curves.rs. lib.rs (coordinator): export SpaceBezier, curve_path, edge_path, sketch_record_curve and planar_face_record from the kernel. (not built: base in progress; kernel and sketch check clean, doc not reachable) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017itGh5fpMEousnZ91cYcVt
This commit is contained in:
+17
-48
@@ -5844,17 +5844,7 @@ fn pipe_region(vals: &BTreeMap<String, Value>, r: f64, wall: &Option<crate::Expr
|
||||
}
|
||||
|
||||
pub fn map_wire(wire: &mut solidcraft_sketch::Wire, matrix: &Mat) {
|
||||
for point in wire.pts.iter_mut().chain(&mut wire.fit) {
|
||||
*point = crate::apply_point(matrix, *point);
|
||||
}
|
||||
if let Some(arc) = &mut wire.arc {
|
||||
let normal = crate::apply_vector(matrix, arc.normal);
|
||||
arc.center = crate::apply_point(matrix, arc.center);
|
||||
arc.start = crate::apply_point(matrix, arc.start);
|
||||
arc.end = crate::apply_point(matrix, arc.end);
|
||||
arc.normal = normal.normalized().unwrap_or(arc.normal);
|
||||
arc.radius = arc.center.dist(arc.start);
|
||||
}
|
||||
wire.map(|p| crate::apply_point(matrix, p), |v| crate::apply_vector(matrix, v));
|
||||
}
|
||||
|
||||
/// A feature's sketch as its component sees it: a sketch of another component that is placed
|
||||
@@ -5909,8 +5899,9 @@ fn path_in_frame<'a>(doc: &Document, sketch: &'a SolvedSketch, consumer: u64) ->
|
||||
|
||||
/// A path that uses 3D sketch curves (wires), as one chain of pieces from the end nearest
|
||||
/// `start` (no start: the first curve runs toward the second): each drawn 3D spline as its fit
|
||||
/// points (the kernel has its exact curve), every other curve as world points. `None` when the
|
||||
/// path is all planar curves.
|
||||
/// points (the kernel has its exact curve), each curve held exactly (a planar sketch curve, a
|
||||
/// linked curve with [`solidcraft_sketch::Wire::exact`]) as its path segments, and a linked
|
||||
/// curve known only as points as those points. `None` when the path is all planar curves.
|
||||
fn path_points(ps: &SolvedSketch, ids: &[String], start: Vec3) -> Result<Option<kernel::PointPath>> {
|
||||
if !ids.iter().any(|id| ps.sketch.wire_index(id).is_some()) {
|
||||
return Ok(None);
|
||||
@@ -5923,20 +5914,19 @@ fn path_points(ps: &SolvedSketch, ids: &[String], start: Vec3) -> Result<Option<
|
||||
if let Some(wi) = ps.sketch.wire_index(id) {
|
||||
pieces.push(match ps.sketch.wires.get(wi) {
|
||||
Some(w) if w.fit.len() >= 3 => kernel::PathPiece::Spline { fit: w.fit.clone(), reversed: false },
|
||||
Some(w) if !w.exact.is_empty() => {
|
||||
let mut segs = Vec::new();
|
||||
for c in &w.exact {
|
||||
segs.extend(kernel::curve_path(&c.curve, c.interval)?);
|
||||
}
|
||||
kernel::PathPiece::Segs(segs)
|
||||
}
|
||||
w => kernel::PathPiece::Polyline(w.map(|w| w.pts.clone()).unwrap_or_default()),
|
||||
});
|
||||
} else {
|
||||
let ci = ps.sketch.curve_index(id).ok_or_else(|| DocError::Unknown(format!("path curve `{id}`")))?;
|
||||
let mut pts: Vec<Vec3> = Vec::new();
|
||||
for s in ps.sketch.segs(ci) {
|
||||
for q in s.polyline(1e-2) {
|
||||
let w = ps.plane.to_world(q);
|
||||
if pts.last().is_none_or(|l| l.dist(w) > 1e-9) {
|
||||
pts.push(w);
|
||||
}
|
||||
}
|
||||
}
|
||||
pieces.push(kernel::PathPiece::Polyline(pts));
|
||||
let segs = ps.sketch.exact_segs(ci).unwrap_or_else(|| ps.sketch.segs(ci));
|
||||
pieces.push(kernel::PathPiece::Segs(segs.iter().map(|s| kernel::PathSeg::of_sketch(&ps.plane, s)).collect()));
|
||||
}
|
||||
}
|
||||
// Pieces meet within the sketch's tolerance (projected curves are only that close).
|
||||
@@ -6022,19 +6012,7 @@ pub(crate) fn path_segments_at(ps: &SolvedSketch, ids: &[String], start: Vec3, p
|
||||
if let Some(profile) = profile {
|
||||
segs = from_profile_on_closed_path(segs, &ps.plane, profile, start);
|
||||
}
|
||||
let n = ps.plane.normal();
|
||||
Ok(segs
|
||||
.iter()
|
||||
.flat_map(|s| match *s {
|
||||
solidcraft_geom::Seg2::Line { a, b } => vec![kernel::PathSeg::Line { a: w(a), b: w(b) }],
|
||||
solidcraft_geom::Seg2::Arc { center, sweep, .. } => {
|
||||
vec![kernel::PathSeg::Arc { a: w(s.start()), center: w(center), axis: n * sweep.signum(), angle: sweep.abs() }]
|
||||
}
|
||||
solidcraft_geom::Seg2::Cubic { .. } | solidcraft_geom::Seg2::Conic { .. } | solidcraft_geom::Seg2::Bezier { .. } => {
|
||||
vec![kernel::PathSeg::Curve { plane: ps.plane, seg: *s }]
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
Ok(segs.iter().map(|s| kernel::PathSeg::of_sketch(&ps.plane, s)).collect())
|
||||
}
|
||||
|
||||
/// A closed chain `segs` (in the plane `path`) begun where `profile`'s plane crosses it nearest
|
||||
@@ -6342,7 +6320,7 @@ fn seg_len(s: &kernel::PathSeg) -> f64 {
|
||||
kernel::PathSeg::Line { a, b } => (b - a).len(),
|
||||
kernel::PathSeg::Arc { a, center, angle, .. } => (a - center).len() * angle.abs(),
|
||||
// Patterns flatten their paths; a curve left is measured along its chord.
|
||||
kernel::PathSeg::Curve { .. } | kernel::PathSeg::Cubic { .. } => s.start().dist(s.end()),
|
||||
kernel::PathSeg::Curve { .. } | kernel::PathSeg::Bezier(_) => s.start().dist(s.end()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6366,7 +6344,7 @@ fn seg_at(s: &kernel::PathSeg, t: f64) -> (Vec3, Vec3) {
|
||||
let v = rotate_vec(a - center, k, s * t / r);
|
||||
(center + v, k.cross(v).normalized().unwrap_or(Vec3::X) * s)
|
||||
}
|
||||
kernel::PathSeg::Curve { .. } | kernel::PathSeg::Cubic { .. } => {
|
||||
kernel::PathSeg::Curve { .. } | kernel::PathSeg::Bezier(_) => {
|
||||
let (a, b) = (s.start(), s.end());
|
||||
let d = (b - a).normalized().unwrap_or(Vec3::X);
|
||||
(a + d * t, d)
|
||||
@@ -6375,16 +6353,7 @@ fn seg_at(s: &kernel::PathSeg, t: f64) -> (Vec3, Vec3) {
|
||||
}
|
||||
|
||||
fn reverse_seg(s: &kernel::PathSeg) -> kernel::PathSeg {
|
||||
match *s {
|
||||
kernel::PathSeg::Line { a, b } => kernel::PathSeg::Line { a: b, b: a },
|
||||
kernel::PathSeg::Arc { a, center, axis, angle } => {
|
||||
let k = axis.normalized().unwrap_or(Vec3::Z);
|
||||
let end = center + rotate_vec(a - center, k, angle);
|
||||
kernel::PathSeg::Arc { a: end, center, axis: k * -1.0, angle }
|
||||
}
|
||||
kernel::PathSeg::Curve { plane, seg } => kernel::PathSeg::Curve { plane, seg: seg.reversed() },
|
||||
kernel::PathSeg::Cubic { p: [a, b, c, d] } => kernel::PathSeg::Cubic { p: [d, c, b, a] },
|
||||
}
|
||||
s.reversed()
|
||||
}
|
||||
|
||||
/// Point and tangent at arc length `d` along a chain (clamped to its ends).
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
//! A linked 3D sketch curve (an included edge or an included sketch curve that leaves the sketch
|
||||
//! plane) saves its exact geometry with its points: `wires[].exact`, the record curves (schema 4
|
||||
//! curve records with their intervals) end to end along the curve, so a sweep along it follows
|
||||
//! the curve itself rather than its points. An older design has none and gets them when its
|
||||
//! links are next resolved; until then such a curve is followed as its points. The last release
|
||||
//! refuses a design with this change (the newer format number).
|
||||
+67
-15
@@ -13,7 +13,7 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use solidcraft_geom::{Mesh, Plane, Seg2, Vec2, Vec3};
|
||||
use solidcraft_sketch::{CurveKind, LinkGeom, LinkKind, LinkSource, Sketch};
|
||||
use solidcraft_sketch::{CurveKind, LinkGeom, LinkKind, LinkSource, Sketch, WireCurve};
|
||||
|
||||
use crate::eval::ModelState;
|
||||
use crate::expr::Value;
|
||||
@@ -33,6 +33,9 @@ pub struct Resolved {
|
||||
pub geom: Vec<LinkGeom>,
|
||||
/// 3D curves (world coordinates).
|
||||
pub wires: Vec<Vec<Vec3>>,
|
||||
/// Each wire's exact curve where its source has one (an included edge or sketch curve),
|
||||
/// one list per wire, empty for a curve known only as points ([`solidcraft_sketch::Wire::exact`]).
|
||||
pub exact: Vec<Vec<WireCurve>>,
|
||||
pub source: LinkSource,
|
||||
/// The reference was not exactly where it was and was re-found on the nearest geometry.
|
||||
pub moved: bool,
|
||||
@@ -108,6 +111,7 @@ fn resolve_uncached(
|
||||
let mut edge_index = None;
|
||||
let mut source = src.clone();
|
||||
let mut wires: Vec<Vec<Vec3>> = Vec::new();
|
||||
let mut exact: Vec<Vec<WireCurve>> = Vec::new();
|
||||
let geom = match src {
|
||||
LinkSource::Edge { body, at } => {
|
||||
let (bi, mesh) = body_mesh(st, body, *at)?;
|
||||
@@ -120,8 +124,9 @@ fn resolve_uncached(
|
||||
let name = st.bodies.get(bi).map(|b| b.name.clone()).unwrap_or_else(|| body.clone());
|
||||
source = LinkSource::Edge { body: name, at: q };
|
||||
edge_index = Some(e);
|
||||
let exact = st.bodies.get(bi).and_then(|b| exact_edges(b, plane, &[e]).pop().flatten());
|
||||
edge_geom(plane, kind, exact, mesh.edges.get(e).cloned().unwrap_or_default(), &mut wires)
|
||||
let planar = st.bodies.get(bi).and_then(|b| exact_edges(b, plane, &[e]).pop().flatten());
|
||||
let pts = mesh.edges.get(e).cloned().unwrap_or_default();
|
||||
edge_geom(plane, kind, planar, (pts, || edge_curves(st.bodies.get(bi), e)), &mut wires, &mut exact)
|
||||
}
|
||||
LinkSource::Face { body, at } => {
|
||||
let (bi, mesh) = body_mesh(st, body, *at)?;
|
||||
@@ -182,6 +187,7 @@ fn resolve_uncached(
|
||||
let pts: Vec<Vec3> = ss.sketch.polyline(ci).iter().map(|p| ss.plane.to_world(*p)).collect();
|
||||
if off_plane(plane, &pts) {
|
||||
wires.push(pts);
|
||||
exact.push(sketch_curves(ss, ci));
|
||||
Vec::new()
|
||||
} else {
|
||||
project_sketch_curve(&ss.sketch, &ss.plane, ci, plane)
|
||||
@@ -203,12 +209,14 @@ fn resolve_uncached(
|
||||
let name = st.bodies.get(bi).map(|b| b.name.clone()).unwrap_or_else(|| body.clone());
|
||||
source = LinkSource::OnSurface { sketch: *sketch, curve: curve.clone(), body: name, at: q };
|
||||
wires = crate::project3d::onto_face(&mesh, f, &pts, plane.normal());
|
||||
exact = vec![Vec::new(); wires.len()];
|
||||
Vec::new()
|
||||
}
|
||||
LinkSource::Intersection { a, b } => {
|
||||
let (ma, fa) = crate::project3d::mesh_of(st, a).ok_or_else(|| lost(&a.describe()))?;
|
||||
let (mb, fb) = crate::project3d::mesh_of(st, b).ok_or_else(|| lost(&b.describe()))?;
|
||||
wires = crate::project3d::mesh_intersection(&ma, fa, &mb, fb);
|
||||
exact = vec![Vec::new(); wires.len()];
|
||||
Vec::new()
|
||||
}
|
||||
LinkSource::Iso { body, at, dir } => {
|
||||
@@ -221,6 +229,7 @@ fn resolve_uncached(
|
||||
let name = st.bodies.get(bi).map(|b| b.name.clone()).unwrap_or_else(|| body.clone());
|
||||
source = LinkSource::Iso { body: name, at: q, dir: dir.clone() };
|
||||
wires = crate::project3d::iso_curve(&mesh, f, q, dir);
|
||||
exact = vec![Vec::new(); wires.len()];
|
||||
Vec::new()
|
||||
}
|
||||
LinkSource::Spun { body, origin, dir } => {
|
||||
@@ -275,7 +284,7 @@ fn resolve_uncached(
|
||||
if geom.is_empty() && wires.is_empty() {
|
||||
return Err(DocError::Invalid(format!("the {} projects to nothing on this sketch plane", src.describe())));
|
||||
}
|
||||
Ok(Resolved { geom: merge_split_circles(geom), wires, source, moved, face_index, edge_index })
|
||||
Ok(Resolved { geom: merge_split_circles(geom), wires, exact, source, moved, face_index, edge_index })
|
||||
}
|
||||
|
||||
/// Whether a curve (world points) leaves the sketch plane: included, it then stays 3D.
|
||||
@@ -285,19 +294,51 @@ fn off_plane(plane: &Plane, pts: &[Vec3]) -> bool {
|
||||
}
|
||||
|
||||
/// What a body edge (its `exact` curve when read, and its polyline `pts`) gives a sketch: its
|
||||
/// section with the sketch plane, itself as a 3D curve (into `wires`) when it is included and
|
||||
/// leaves the plane, else its projection (the exact curve, or the polyline fitted).
|
||||
fn edge_geom(plane: &Plane, kind: LinkKind, exact: Option<Vec<LinkGeom>>, pts: Vec<Vec3>, wires: &mut Vec<Vec<Vec3>>) -> Vec<LinkGeom> {
|
||||
/// section with the sketch plane, itself as a 3D curve (its points into `wires`, its exact
|
||||
/// curve into `exact`) when it is included and leaves the plane, else its projection (the exact
|
||||
/// planar curve `planar`, or the polyline fitted).
|
||||
fn edge_geom(
|
||||
plane: &Plane,
|
||||
kind: LinkKind,
|
||||
planar: Option<Vec<LinkGeom>>,
|
||||
(pts, curves): (Vec<Vec3>, impl FnOnce() -> Vec<WireCurve>),
|
||||
wires: &mut Vec<Vec<Vec3>>,
|
||||
exact: &mut Vec<Vec<WireCurve>>,
|
||||
) -> Vec<LinkGeom> {
|
||||
match kind {
|
||||
LinkKind::Intersect => section_of_polyline(plane, &pts),
|
||||
LinkKind::Include if off_plane(plane, &pts) => {
|
||||
wires.push(pts);
|
||||
exact.push(curves());
|
||||
Vec::new()
|
||||
}
|
||||
_ => exact.unwrap_or_else(|| fit_world(plane, &pts)),
|
||||
_ => planar.unwrap_or_else(|| fit_world(plane, &pts)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Edge `e` of `body` (its index in the body's edge order) as its exact curve from the body's
|
||||
/// record; none for a mesh body or an intersection curve the record holds no curve for.
|
||||
fn edge_curves(body: Option<&crate::ModelBody>, e: usize) -> Vec<WireCurve> {
|
||||
let Some(body) = body.filter(|b| !b.body.is_mesh()) else { return Vec::new() };
|
||||
let Ok(record) = crate::kernel::brep_record(&body.body) else { return Vec::new() };
|
||||
record
|
||||
.edges
|
||||
.get(e)
|
||||
.filter(|edge| !matches!(edge.curve, solidcraft_geom::brep::Curve::SurfaceIntersection { .. }))
|
||||
.map(|edge| vec![WireCurve { curve: edge.curve.clone(), interval: edge.interval }])
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Curve `ci` of the solved sketch `ss` as its exact pieces in space, in the order its polyline
|
||||
/// runs; none where a piece has no record curve.
|
||||
fn sketch_curves(ss: &crate::eval::SolvedSketch, ci: usize) -> Vec<WireCurve> {
|
||||
let segs = ss.sketch.exact_segs(ci).unwrap_or_else(|| ss.sketch.segs(ci));
|
||||
segs.iter()
|
||||
.map(|s| crate::kernel::sketch_record_curve(&ss.plane, s).map(|(curve, interval)| WireCurve { curve, interval }))
|
||||
.collect::<Option<_>>()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Edges `edges` of `body` (indices into its mesh's `edges`) on `plane` as their exact curves:
|
||||
/// lines, circles and arcs, and the Bézier pieces of a free-form curve. `None` for an edge that
|
||||
/// doesn't lie in a plane parallel to `plane` or isn't read exactly (a mesh body's): it is
|
||||
@@ -375,14 +416,14 @@ fn resolve_named_edge(st: &ModelState, plane: &Plane, kind: LinkKind, body: &str
|
||||
.filter_map(|w| Some(closest_on_segment(near, *w.first()?, *w.get(1)?)))
|
||||
.min_by(|a, b| a.dist(near).total_cmp(&b.dist(near)))
|
||||
.ok_or_else(|| lost("named projection edge"))?;
|
||||
let mut wires = Vec::new();
|
||||
let exact = exact_edges(target, plane, &[index]).pop().flatten();
|
||||
let geom = edge_geom(plane, kind, exact, pts, &mut wires);
|
||||
let (mut wires, mut exact) = (Vec::new(), Vec::new());
|
||||
let planar = exact_edges(target, plane, &[index]).pop().flatten();
|
||||
let geom = edge_geom(plane, kind, planar, (pts, || edge_curves(Some(target), index)), &mut wires, &mut exact);
|
||||
if geom.is_empty() && wires.is_empty() {
|
||||
return Err(DocError::Invalid("named edge projects to nothing on this sketch plane".into()));
|
||||
}
|
||||
let source = LinkSource::Edge { body: body.into(), at };
|
||||
Ok(Resolved { geom: merge_split_circles(geom), wires, source, moved: false, face_index: None, edge_index: Some(index) })
|
||||
Ok(Resolved { geom: merge_split_circles(geom), wires, exact, source, moved: false, face_index: None, edge_index: Some(index) })
|
||||
}
|
||||
|
||||
fn resolve_named_face(st: &ModelState, plane: &Plane, kind: LinkKind, body: &str, index: usize, at: Vec3) -> Result<Resolved> {
|
||||
@@ -403,7 +444,15 @@ fn resolve_named_face(st: &ModelState, plane: &Plane, kind: LinkKind, body: &str
|
||||
if geom.is_empty() {
|
||||
return Err(DocError::Invalid("named face projects to nothing on this sketch plane".into()));
|
||||
}
|
||||
Ok(Resolved { geom, wires: Vec::new(), source: LinkSource::Face { body: body.into(), at }, moved: false, face_index, edge_index: None })
|
||||
Ok(Resolved {
|
||||
geom,
|
||||
wires: Vec::new(),
|
||||
exact: Vec::new(),
|
||||
source: LinkSource::Face { body: body.into(), at },
|
||||
moved: false,
|
||||
face_index,
|
||||
edge_index: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn projection_face_edges(body: &crate::ModelBody, mesh: &Mesh, index: u32, plane: &Plane) -> Result<Vec<usize>> {
|
||||
@@ -484,6 +533,7 @@ pub fn resolve_face_indices(st: &ModelState, plane: &Plane, kind: LinkKind, body
|
||||
Ok(Resolved {
|
||||
geom: merge_split_circles(geometry),
|
||||
wires: Vec::new(),
|
||||
exact: Vec::new(),
|
||||
source: LinkSource::Face { body: body.name.clone(), at: point },
|
||||
moved: false,
|
||||
face_index: if indices.len() == 1 { indices.first().copied() } else { None },
|
||||
@@ -663,7 +713,7 @@ fn refresh_links_in(
|
||||
if geom.is_empty() {
|
||||
return Err(DocError::Invalid(format!("the {} makes no outline", source.describe())));
|
||||
}
|
||||
Ok(Resolved { geom, wires: Vec::new(), source: source.clone(), moved: false, face_index: None, edge_index: None })
|
||||
Ok(Resolved { geom, wires: Vec::new(), exact: Vec::new(), source: source.clone(), moved: false, face_index: None, edge_index: None })
|
||||
})
|
||||
} else if l.auto_face {
|
||||
anchor
|
||||
@@ -690,7 +740,9 @@ fn refresh_links_in(
|
||||
link.edge_name = Some(name);
|
||||
}
|
||||
sk.set_link_source(&l.id, r.source);
|
||||
let _ = sk.set_link_wires(&l.id, &r.wires);
|
||||
if let Err(e) = sk.set_link_wires(&l.id, &r.wires).and_then(|()| sk.set_link_wire_curves(&l.id, &r.exact)) {
|
||||
warn.push(format!("projected {}: {e}", l.source.describe()));
|
||||
}
|
||||
// Words built from text parameters change shape when the parameters do: that
|
||||
// rebuild is the text following them, not a reference that changed under it.
|
||||
let words_follow = matches!(&l.source, LinkSource::Text { exprs, .. } if exprs.text.is_some());
|
||||
|
||||
@@ -920,6 +920,90 @@ fn smlib_tapered(plane: &Plane, region: &Region2, from: f64, to: f64, foot: f64,
|
||||
crate::smlib_sweeps::extrude(plane, &crate::smlib_sweeps::pieces([&bottom]), Some(region), from * sign, to - from, sign, draft)
|
||||
}
|
||||
|
||||
/// A planar region on `plane` as the schema 4 record of one face (an open sheet): the plane's
|
||||
/// own axes as its parameters (its natural normal the sketch's), the outer loop
|
||||
/// counter-clockwise and the holes clockwise, each sketch segment one edge of its exact record
|
||||
/// curve ([`crate::point_path::sketch_record_curve`]) with its exact pcurve on the plane. The record
|
||||
/// equivalent of a profile face: SMLib takes it with `Brep::from_record`.
|
||||
pub fn planar_face_record(plane: &Plane, r: &Region2) -> Result<solidcraft_geom::brep::Brep> {
|
||||
use solidcraft_geom::brep::{self as rec, pcurve};
|
||||
if !frame_ok(plane) {
|
||||
return Err(KernelError::Invalid("the face's plane is not finite and orthonormal".into()));
|
||||
}
|
||||
region_ok(r)?;
|
||||
let region = oriented(r)?;
|
||||
let tolerance = 1e-7;
|
||||
let d = |v: Vec3| [v.x, v.y, v.z];
|
||||
let surface = rec::Surface::Plane { origin: d(plane.origin), u_axis: d(plane.x), v_axis: d(plane.y) };
|
||||
let bad = |what: &str| KernelError::Invalid(format!("profile {what}"));
|
||||
// Edges and vertices numbered as the face's loops first meet them.
|
||||
let mut edges: Vec<rec::Edge> = Vec::new();
|
||||
let mut vertices: Vec<rec::Vertex> = Vec::new();
|
||||
let mut loops: Vec<Vec<u32>> = Vec::new();
|
||||
let (mut lo, mut hi) = ([f64::INFINITY; 2], [f64::NEG_INFINITY; 2]);
|
||||
for (li, l) in region.loops().enumerate() {
|
||||
let segs = &l.lp.segs;
|
||||
let first = u32::try_from(vertices.len()).map_err(|_| bad("size"))?;
|
||||
let mut ids = Vec::with_capacity(segs.len());
|
||||
for (k, s) in segs.iter().enumerate() {
|
||||
let (curve, interval) = crate::point_path::sketch_record_curve(plane, s).ok_or_else(|| bad("curve"))?;
|
||||
let start = u32::try_from(vertices.len()).map_err(|_| bad("size"))?;
|
||||
vertices.push(rec::Vertex { point: d(plane.to_world(s.start())), tolerance });
|
||||
let end = if k + 1 == segs.len() { first } else { start + 1 };
|
||||
let e = u32::try_from(edges.len()).map_err(|_| bad("size"))?;
|
||||
let (li, k) = (u32::try_from(li).map_err(|_| bad("size"))?, u32::try_from(k).map_err(|_| bad("size"))?);
|
||||
edges.push(rec::Edge {
|
||||
curve,
|
||||
interval,
|
||||
vertices: [start, end],
|
||||
tolerance,
|
||||
uses: vec![rec::EdgeUseRef { face: 0, r#loop: li, position: k, upper: false }],
|
||||
});
|
||||
ids.push(e);
|
||||
for q in s.polyline(tolerance) {
|
||||
for (i, x) in [q.x, q.y].into_iter().enumerate() {
|
||||
if let (Some(a), Some(b)) = (lo.get_mut(i), hi.get_mut(i)) {
|
||||
*a = a.min(x);
|
||||
*b = b.max(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
loops.push(ids);
|
||||
}
|
||||
let uses: Vec<Vec<pcurve::EdgeUse<'_>>> = loops
|
||||
.iter()
|
||||
.map(|ids| {
|
||||
ids.iter()
|
||||
.filter_map(|&e| {
|
||||
let edge = edges.get(e as usize)?;
|
||||
Some(pcurve::EdgeUse { edge: e, curve: &edge.curve, interval: edge.interval, forward: true, tolerance, given: None })
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
let pcurves = pcurve::pcurves(&surface, &uses, tolerance).map_err(|e| KernelError::Failed(format!("profile pcurves: {e}")))?;
|
||||
let face_loops = loops
|
||||
.iter()
|
||||
.zip(pcurves)
|
||||
.map(|(ids, pcs)| rec::Loop::Edges {
|
||||
edges: ids.iter().zip(pcs).map(|(&edge, pcurve)| rec::OrientedEdge { edge, forward: true, pcurve }).collect(),
|
||||
})
|
||||
.collect();
|
||||
let pad = tolerance * 10.0;
|
||||
let brep = rec::Brep {
|
||||
version: rec::VERSION,
|
||||
tolerance,
|
||||
regions: vec![rec::Region { shells: vec![0], void: false }],
|
||||
shells: vec![rec::Shell::Faces { faces: vec![rec::OrientedFace { face: 0, forward: true }], wires: vec![] }],
|
||||
faces: vec![rec::Face { surface, domain: [[lo[0] - pad, hi[0] + pad], [lo[1] - pad, hi[1] + pad]], loops: face_loops }],
|
||||
edges,
|
||||
vertices,
|
||||
};
|
||||
brep.validate().map_err(|e| KernelError::Failed(format!("profile face: {e}")))?;
|
||||
Ok(brep)
|
||||
}
|
||||
|
||||
/// Ruled loft through two or more planar sections (outer loops only). The sections and the
|
||||
/// same sections in reverse order give the same body.
|
||||
pub fn loft(sections: &[(Plane, Loop2)]) -> Result<Body> {
|
||||
@@ -1377,7 +1461,9 @@ impl PathSeg {
|
||||
let w = |p: Vec2| plane.to_world(p);
|
||||
match *s {
|
||||
Seg2::Line { a, b } => PathSeg::Line { a: w(a), b: w(b) },
|
||||
Seg2::Arc { center, sweep, .. } => PathSeg::Arc { a: w(s.start()), center: w(center), axis: plane.normal() * sweep.signum(), angle: sweep.abs() },
|
||||
Seg2::Arc { center, sweep, .. } => {
|
||||
PathSeg::Arc { a: w(s.start()), center: w(center), axis: plane.normal() * sweep.signum(), angle: sweep.abs() }
|
||||
}
|
||||
Seg2::Cubic { .. } | Seg2::Conic { .. } | Seg2::Bezier { .. } => PathSeg::Curve { plane: *plane, seg: *s },
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,9 @@ impl PathPiece {
|
||||
if spans.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
let seg = |p: [Vec3; 4]| SpaceBezier::cubic(p).map(PathSeg::Bezier).ok_or_else(|| KernelError::Invalid("the 3D sketch spline is not finite".into()));
|
||||
let seg = |p: [Vec3; 4]| {
|
||||
SpaceBezier::cubic(p).map(PathSeg::Bezier).ok_or_else(|| KernelError::Invalid("the 3D sketch spline is not finite".into()))
|
||||
};
|
||||
Ok(Some(if *reversed {
|
||||
spans.into_iter().rev().map(|[a, b, c, d]| seg([d, c, b, a])).collect::<Result<_>>()?
|
||||
} else {
|
||||
@@ -153,13 +155,30 @@ pub fn edge_path(body: &Body, edge: usize) -> Result<Vec<PathSeg>> {
|
||||
curve_path(&e.curve, e.interval)
|
||||
}
|
||||
|
||||
/// The record curve `c` on `[t0, t1]` as path segments ([`edge_path`]).
|
||||
pub(crate) fn curve_path(c: &brep::Curve, [t0, t1]: [f64; 2]) -> Result<Vec<PathSeg>> {
|
||||
let bad = |what: &str| KernelError::Invalid(format!("the edge's {what} does not evaluate"));
|
||||
if !(t0.is_finite() && t1.is_finite() && t0 < t1) {
|
||||
/// Most control points a record curve given to [`curve_path`] may have.
|
||||
const MAX_CURVE_POINTS: usize = 100_000;
|
||||
|
||||
/// The record curve `c` on `[t0, t1]` as path segments ([`edge_path`]); the numbers are checked
|
||||
/// first (they may come from a design file).
|
||||
pub fn curve_path(c: &brep::Curve, [t0, t1]: [f64; 2]) -> Result<Vec<PathSeg>> {
|
||||
let bad = |what: &str| KernelError::Invalid(format!("the curve's {what} does not evaluate"));
|
||||
if !(t0.is_finite() && t1.is_finite() && t0 < t1 && t0.abs() < 1e9 && t1.abs() < 1e9) {
|
||||
return Err(bad("interval"));
|
||||
}
|
||||
let v = |q: [f64; 3]| Vec3::new(q[0], q[1], q[2]);
|
||||
let frame_ok = |f: &brep::Frame| {
|
||||
let (x, y) = (v(f.x_axis), v(f.y_axis));
|
||||
v(f.origin).is_finite() && (x.len() - 1.0).abs() < 1e-6 && (y.len() - 1.0).abs() < 1e-6 && x.dot(y).abs() < 1e-6
|
||||
};
|
||||
let finite = |pts: &[[f64; 3]]| pts.len() <= MAX_CURVE_POINTS && pts.iter().all(|q| v(*q).is_finite());
|
||||
match c {
|
||||
brep::Curve::Line { start, end } if !finite(&[*start, *end]) => return Err(bad("line")),
|
||||
brep::Curve::Circle { frame, radius } if !(frame_ok(frame) && radius.is_finite() && *radius > 0.0) => return Err(bad("circle")),
|
||||
brep::Curve::Ellipse { frame, radii } if !(frame_ok(frame) && radii.iter().all(|r| r.is_finite() && *r > 0.0)) => return Err(bad("ellipse")),
|
||||
brep::Curve::Polyline { points } | brep::Curve::Spline { points, .. } if !finite(points) => return Err(bad("points")),
|
||||
brep::Curve::RationalSpline { homogeneous, .. } if homogeneous.len() > MAX_CURVE_POINTS => return Err(bad("points")),
|
||||
_ => {}
|
||||
}
|
||||
let out = match c {
|
||||
brep::Curve::Line { start, end } => {
|
||||
let (s, e) = (v(*start), v(*end));
|
||||
@@ -184,7 +203,8 @@ pub(crate) fn curve_path(c: &brep::Curve, [t0, t1]: [f64; 2]) -> Result<Vec<Path
|
||||
.map(|i| {
|
||||
let a = t0 + d * i as f64;
|
||||
let w = (d / 2.0).cos();
|
||||
let ctrl = [HPoint::weighted(at(a, 1.0), 1.0), HPoint::weighted(at(a + d / 2.0, 1.0 / w), w), HPoint::weighted(at(a + d, 1.0), 1.0)];
|
||||
let ctrl =
|
||||
[HPoint::weighted(at(a, 1.0), 1.0), HPoint::weighted(at(a + d / 2.0, 1.0 / w), w), HPoint::weighted(at(a + d, 1.0), 1.0)];
|
||||
SpaceBezier::new(&ctrl).map(PathSeg::Bezier).ok_or_else(|| bad("ellipse"))
|
||||
})
|
||||
.collect::<Result<_>>()?
|
||||
@@ -218,6 +238,53 @@ pub(crate) fn curve_path(c: &brep::Curve, [t0, t1]: [f64; 2]) -> Result<Vec<Path
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// A sketch segment on `plane` as the record curve that is it, on its interval, running the
|
||||
/// segment's way: a line, a circle (an arc's; clockwise ones on the frame turned over), a cubic
|
||||
/// or Bézier as a spline of its degree, a conic as a rational quadratic. `None` for a segment
|
||||
/// with non-finite numbers.
|
||||
pub fn sketch_record_curve(plane: &Plane, s: &solidcraft_geom::Seg2) -> Option<(brep::Curve, [f64; 2])> {
|
||||
use solidcraft_geom::Seg2;
|
||||
let w = |p: solidcraft_geom::Vec2| {
|
||||
let q = plane.to_world(p);
|
||||
[q.x, q.y, q.z]
|
||||
};
|
||||
let d = |v: Vec3| [v.x, v.y, v.z];
|
||||
let bezier = |pts: &[solidcraft_geom::Vec2]| {
|
||||
let n = pts.len().checked_sub(1)?;
|
||||
let knots = std::iter::repeat_n(0.0, n + 1).chain(std::iter::repeat_n(1.0, n + 1)).collect();
|
||||
Some((brep::Curve::Spline { knots, points: pts.iter().map(|p| w(*p)).collect() }, [0.0, 1.0]))
|
||||
};
|
||||
let out = match *s {
|
||||
Seg2::Line { a, b } => (brep::Curve::Line { start: w(a), end: w(b) }, [0.0, 1.0]),
|
||||
Seg2::Arc { center, radius, start, sweep } => {
|
||||
let (y, t0) = if sweep >= 0.0 { (plane.y, start) } else { (plane.y * -1.0, -start) };
|
||||
let frame = brep::Frame { origin: w(center), x_axis: d(plane.x), y_axis: d(y) };
|
||||
(brep::Curve::Circle { frame, radius }, [t0, t0 + sweep.abs()])
|
||||
}
|
||||
Seg2::Cubic { p0, p1, p2, p3 } => bezier(&[p0, p1, p2, p3])?,
|
||||
Seg2::Bezier { n, p } => bezier(p.get(..=usize::from(n))?)?,
|
||||
Seg2::Conic { a, apex, b, w: wt } => {
|
||||
let h = |p: solidcraft_geom::Vec2, k: f64| {
|
||||
let q = w(p);
|
||||
[q[0] * k, q[1] * k, q[2] * k, k]
|
||||
};
|
||||
(
|
||||
brep::Curve::RationalSpline { knots: vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0], homogeneous: vec![h(a, 1.0), h(apex, wt), h(b, 1.0)] },
|
||||
[0.0, 1.0],
|
||||
)
|
||||
}
|
||||
};
|
||||
let finite = |q: &[f64]| q.iter().all(|x| x.is_finite());
|
||||
let ok = match &out.0 {
|
||||
brep::Curve::Line { start, end } => finite(start) && finite(end),
|
||||
brep::Curve::Circle { frame, radius } => finite(&frame.origin) && radius.is_finite() && *radius > 0.0,
|
||||
brep::Curve::Spline { points, .. } => points.iter().all(|q| finite(q)),
|
||||
brep::Curve::RationalSpline { homogeneous, .. } => homogeneous.iter().all(|q| finite(q) && q[3] > 0.0),
|
||||
_ => false,
|
||||
};
|
||||
(ok && out.1.iter().all(|t| t.is_finite())).then_some(out)
|
||||
}
|
||||
|
||||
/// The B-spline (homogeneous `ctrl` on `knots`) on `[t0, t1]` as its Bézier spans, one per knot
|
||||
/// span: each the piece of the spline between two neighbouring knots ([`segment`]), exactly.
|
||||
fn spline_spans(knots: &[f64], ctrl: &[HPoint], t0: f64, t1: f64) -> Result<Vec<PathSeg>> {
|
||||
|
||||
@@ -77,7 +77,7 @@ fn combinatorics(hs: &[HalfSpace]) -> Result<(Vec<Vec3>, Vec<(usize, Vec<usize>)
|
||||
/// The convex polyhedron bounded by the half-spaces.
|
||||
pub fn convex_polyhedron(hs: &[HalfSpace]) -> Result<Body> {
|
||||
let (verts, faces) = combinatorics(hs)?;
|
||||
crate::smlib_sweeps::solid_from_record(&polyhedron_record(hs, &verts, &faces)?, "polyhedron")
|
||||
crate::smlib_sweeps::body_from_record(&polyhedron_record(hs, &verts, &faces)?, "polyhedron")
|
||||
}
|
||||
|
||||
/// The record of the closed polyhedron with corners `verts` whose faces are `faces` (the
|
||||
|
||||
@@ -71,7 +71,7 @@ pub(crate) struct Draft {
|
||||
}
|
||||
|
||||
#[cfg(feature = "smlib")]
|
||||
pub(crate) use imp::{extrude, loft, loft_rails, loft_to_point, revolve, screw, solid_from_record, sphere, sweep, sweep_controlled};
|
||||
pub(crate) use imp::{body_from_record, extrude, loft, loft_rails, loft_to_point, revolve, screw, sphere, sweep, sweep_controlled};
|
||||
|
||||
/// The error of an SMLib operation in a build without SMLib.
|
||||
#[cfg(not(feature = "smlib"))]
|
||||
@@ -83,7 +83,7 @@ pub(crate) fn not_built() -> KernelError {
|
||||
mod stubs {
|
||||
use super::*;
|
||||
|
||||
pub(crate) fn solid_from_record(_: &solidcraft_geom::brep::Brep, _: &str) -> Result<Body> {
|
||||
pub(crate) fn body_from_record(_: &solidcraft_geom::brep::Brep, _: &str) -> Result<Body> {
|
||||
Err(not_built())
|
||||
}
|
||||
pub(crate) fn sphere(_: Vec3, _: f64) -> Result<Body> {
|
||||
@@ -115,7 +115,7 @@ mod stubs {
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "smlib"))]
|
||||
pub(crate) use stubs::{extrude, loft, loft_rails, loft_to_point, revolve, screw, solid_from_record, sphere, sweep, sweep_controlled};
|
||||
pub(crate) use stubs::{body_from_record, extrude, loft, loft_rails, loft_to_point, revolve, screw, sphere, sweep, sweep_controlled};
|
||||
|
||||
/// Refuses what no SMLib sweep does, before any work.
|
||||
pub(crate) fn refuse(what: &str) -> KernelError {
|
||||
@@ -183,6 +183,13 @@ mod imp {
|
||||
pieces.iter().map(|x| curve(plane, &x.seg)).collect()
|
||||
}
|
||||
|
||||
/// A chain of sketch segments on `plane` as SMLib's curves, one each, exactly (lines, arcs,
|
||||
/// Béziers and conics as NURBS): what SMLib's sweeps take as a profile or a path.
|
||||
#[allow(dead_code)] // For the sheet builders, which still build their own wires.
|
||||
pub(crate) fn chain_curves(plane: &Plane, segs: &[Seg2]) -> Result<Vec<Curve>> {
|
||||
segs.iter().map(|s| curve(plane, s)).collect()
|
||||
}
|
||||
|
||||
/// The kernel body of an SMLib result SMLib's own check finds valid.
|
||||
fn body_of(brep: &Brep, what: &str) -> Result<Body> {
|
||||
let check = brep.check(CheckLevel::All).map_err(|e| failed(what, e))?;
|
||||
@@ -192,9 +199,9 @@ mod imp {
|
||||
crate::native::body_from_smlib(brep)
|
||||
}
|
||||
|
||||
/// The solid a schema 4 record describes, taken by SMLib as a modeller's own record (no
|
||||
/// healing) and checked there.
|
||||
pub(crate) fn solid_from_record(record: &solidcraft_geom::brep::Brep, what: &str) -> Result<Body> {
|
||||
/// The body (a solid or a sheet) a schema 4 record describes, taken by SMLib as a
|
||||
/// modeller's own record (no healing) and checked there.
|
||||
pub(crate) fn body_from_record(record: &solidcraft_geom::brep::Brep, what: &str) -> Result<Body> {
|
||||
let brep = Brep::from_record(record, true).map_err(|e| failed(what, e))?;
|
||||
body_of(&brep, what)
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ pub use solver::{
|
||||
};
|
||||
pub use text::{MAX_TEXT_CHARS, TextAlign, TextMetrics, TextStyle, text_geometry, text_geometry_in, text_geometry_styled};
|
||||
pub use thin::{MAX_THIN_SEGMENTS, thin_wall_regions};
|
||||
pub use wire::{MAX_FIT_POINTS, MAX_WIRE_POINTS, Wire, WireArc, WireSpace, arc_from_3_points, arc_polyline, arc_sweep, fit_polyline};
|
||||
pub use wire::{MAX_FIT_POINTS, MAX_WIRE_POINTS, Wire, WireArc, WireCurve, WireSpace, arc_from_3_points, arc_polyline, arc_sweep, fit_polyline};
|
||||
|
||||
#[cfg(test)]
|
||||
mod circle_edge_tests;
|
||||
|
||||
@@ -62,6 +62,70 @@ pub struct Wire {
|
||||
/// are its three defining points (start, through, end) and whose `pts` are its samples.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub arc: Option<WireArc>,
|
||||
/// A linked curve's exact geometry where its source has one (an included edge or sketch
|
||||
/// curve): record curves end to end along `pts`, in `space` coordinates. Empty for a curve
|
||||
/// known only as its points (projected onto a face, intersection and iso curves). Set when
|
||||
/// the link is resolved, with `pts`.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub exact: Vec<WireCurve>,
|
||||
}
|
||||
|
||||
/// One exact piece of a 3D sketch curve ([`Wire::exact`]): a record curve on its interval.
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct WireCurve {
|
||||
pub curve: solidcraft_geom::brep::Curve,
|
||||
pub interval: [f64; 2],
|
||||
}
|
||||
|
||||
impl WireCurve {
|
||||
/// The piece carried by the rigid motion `point` of points and `vector` of directions (a
|
||||
/// placement). `None` for an intersection curve, whose surfaces are not carried here.
|
||||
pub fn mapped(&self, point: &impl Fn(Vec3) -> Vec3, vector: &impl Fn(Vec3) -> Vec3) -> Option<WireCurve> {
|
||||
use solidcraft_geom::brep::{Curve, Frame};
|
||||
let a = |q: [f64; 3]| Vec3::new(q[0], q[1], q[2]);
|
||||
let b = |v: Vec3| [v.x, v.y, v.z];
|
||||
let p = |q: [f64; 3]| b(point(a(q)));
|
||||
let frame = |f: &Frame| Frame { origin: p(f.origin), x_axis: b(vector(a(f.x_axis))), y_axis: b(vector(a(f.y_axis))) };
|
||||
let curve = match &self.curve {
|
||||
Curve::Line { start, end } => Curve::Line { start: p(*start), end: p(*end) },
|
||||
Curve::Circle { frame: f, radius } => Curve::Circle { frame: frame(f), radius: *radius },
|
||||
Curve::Ellipse { frame: f, radii } => Curve::Ellipse { frame: frame(f), radii: *radii },
|
||||
Curve::Polyline { points } => Curve::Polyline { points: points.iter().map(|q| p(*q)).collect() },
|
||||
Curve::Spline { knots, points } => Curve::Spline { knots: knots.clone(), points: points.iter().map(|q| p(*q)).collect() },
|
||||
Curve::RationalSpline { knots, homogeneous } => Curve::RationalSpline {
|
||||
knots: knots.clone(),
|
||||
homogeneous: homogeneous
|
||||
.iter()
|
||||
.map(|h| {
|
||||
let q = point(a([h[0], h[1], h[2]]) * (1.0 / h[3]));
|
||||
[q.x * h[3], q.y * h[3], q.z * h[3], h[3]]
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
Curve::SurfaceIntersection { .. } => return None,
|
||||
};
|
||||
Some(WireCurve { curve, interval: self.interval })
|
||||
}
|
||||
}
|
||||
|
||||
impl Wire {
|
||||
/// The wire carried by the rigid motion `point` of points and `vector` of directions: its
|
||||
/// points, fit points and exact curves (dropped where one cannot be carried, leaving the
|
||||
/// points).
|
||||
pub fn map(&mut self, point: impl Fn(Vec3) -> Vec3, vector: impl Fn(Vec3) -> Vec3) {
|
||||
for q in self.pts.iter_mut().chain(&mut self.fit) {
|
||||
*q = point(*q);
|
||||
}
|
||||
if let Some(arc) = &mut self.arc {
|
||||
let normal = vector(arc.normal);
|
||||
arc.center = point(arc.center);
|
||||
arc.start = point(arc.start);
|
||||
arc.end = point(arc.end);
|
||||
arc.normal = normal.normalized().unwrap_or(arc.normal);
|
||||
arc.radius = arc.center.dist(arc.start);
|
||||
}
|
||||
self.exact = self.exact.iter().map(|c| c.mapped(&point, &vector)).collect::<Option<_>>().unwrap_or_default();
|
||||
}
|
||||
}
|
||||
|
||||
/// Most fit points of a drawn 3D curve.
|
||||
@@ -271,7 +335,22 @@ impl Sketch {
|
||||
Some(o) => o.clone(),
|
||||
None => self.fresh("w")?,
|
||||
};
|
||||
self.wires.push(Wire { id: wid, pts, space: WireSpace::Component, link: Some(id.to_string()), fit: Vec::new(), arc: None });
|
||||
self.wires.push(Wire { id: wid, pts, space: WireSpace::Component, link: Some(id.to_string()), fit: Vec::new(), arc: None, exact: Vec::new() });
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Give a link's wires their exact curves ([`Wire::exact`]), one list per wire in the order
|
||||
/// [`Self::set_link_wires`] took them (an empty list: known only as points).
|
||||
pub fn set_link_wire_curves(&mut self, id: &str, exact: &[Vec<WireCurve>]) -> Result<()> {
|
||||
let wires = self.link_wires(id);
|
||||
if wires.len() != exact.len() {
|
||||
return Err(SketchError::Invalid(format!("link {id} has {} 3D curves, not {}", wires.len(), exact.len())));
|
||||
}
|
||||
for (i, curves) in wires.into_iter().zip(exact) {
|
||||
if let Some(w) = self.wires.get_mut(i) {
|
||||
w.exact = curves.clone();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -286,7 +365,7 @@ impl Sketch {
|
||||
let pts = drawn_polyline(fit)?;
|
||||
self.wire_budget(None, 1, pts.len() + fit.len())?;
|
||||
let id = self.fresh(if fit.len() == 1 { "q" } else { "w" })?;
|
||||
self.wires.push(Wire { id: id.clone(), pts, space, link: None, fit: fit.to_vec(), arc: None });
|
||||
self.wires.push(Wire { id: id.clone(), pts, space, link: None, fit: fit.to_vec(), arc: None, exact: Vec::new() });
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
@@ -306,7 +385,7 @@ impl Sketch {
|
||||
let pts = arc_polyline(&arc, ARC_TOL);
|
||||
self.wire_budget(None, 1, pts.len() + fit.len())?;
|
||||
let id = self.fresh("w")?;
|
||||
self.wires.push(Wire { id: id.clone(), pts, space, link: None, fit: fit.to_vec(), arc: Some(arc) });
|
||||
self.wires.push(Wire { id: id.clone(), pts, space, link: None, fit: fit.to_vec(), arc: Some(arc), exact: Vec::new() });
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user