fix: closed spline gap, hatch spline boundary, and spline commit grouping

- Spline: if flags.closed/periodic and B-spline endpoints don't meet,
  add an explicit closing Wire segment to eliminate the visual gap.
- Hatch BoundaryEdge::Spline: evaluate the B-spline at 16 uniform
  parameter samples (de Boor via truck BSplineCurve) instead of
  plotting raw control points as a polyline; falls back to control-
  point polyline for degenerate knot/degree inputs.
- Ellipse arc key_vertices and LWPolyline midpoint grips committed
  separately; this commit wraps the spline-related fixes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Sonnet 4.6 committed 2026-04-27 02:26:26 +03:00
1 parent 6fd740e38f
commit 0bb4209e2c
2 files changed
+59 -13

No files matched your search

+26 -7
View File
@@ -2,7 +2,7 @@ use acadrust::entities::Spline;
use glam::Vec3;
use truck_modeling::{
base::{BoundedCurve, ParametricCurve},
builder, BSplineCurve, Curve, Edge, KnotVec, Point3,
builder, BSplineCurve, Curve, Edge, KnotVec, Point3, Wire,
};
use crate::command::EntityTransform;
@@ -34,12 +34,7 @@ fn to_truck(spl: &Spline) -> TruckEntity {
let (t0, t1) = bspline.range_tuple();
let p_start = bspline.subs(t0);
let p_end = bspline.subs(t1);
let v_start = builder::vertex(p_start);
let v_end = builder::vertex(p_end);
let edge = Edge::new(&v_start, &v_end, Curve::BSplineCurve(bspline));
// Expose fit_points (preferred) or control_points as key_vertices so that
// Endpoint/Midpoint snap can reach spline construction points.
let snap_source = if !spl.fit_points.is_empty() {
&spl.fit_points
} else {
@@ -50,8 +45,32 @@ fn to_truck(spl: &Spline) -> TruckEntity {
.map(|p| [p.x as f32, p.y as f32, p.z as f32])
.collect();
let is_closed = spl.flags.closed || spl.flags.periodic;
let gap = {
let dx = (p_end.x - p_start.x) as f32;
let dy = (p_end.y - p_start.y) as f32;
let dz = (p_end.z - p_start.z) as f32;
(dx * dx + dy * dy + dz * dz).sqrt()
};
let object = if is_closed && gap > 1e-6 {
// The B-spline doesn't self-close — add an explicit closing segment.
let v_start = builder::vertex(p_start);
let v_end = builder::vertex(p_end);
let v_close = builder::vertex(p_start);
let main_edge = Edge::new(&v_start, &v_end, Curve::BSplineCurve(bspline));
let close_edge = builder::line(&v_end, &v_close);
let wire: Wire = [main_edge, close_edge].into_iter().collect();
TruckObject::Contour(wire)
} else {
let v_start = builder::vertex(p_start);
let v_end = builder::vertex(p_end);
let edge = Edge::new(&v_start, &v_end, Curve::BSplineCurve(bspline));
TruckObject::Curve(edge)
};
TruckEntity {
object: TruckObject::Curve(edge),
object,
snap_pts: vec![],
tangent_geoms: vec![],
key_vertices,
+33 -6
View File
@@ -37,6 +37,10 @@ pub use wire_model::WireModel;
use crate::command::EntityTransform;
use acadrust::entities::{BoundaryEdge, BoundaryPath, Hatch as DxfHatch, PolylineEdge, Solid as DxfSolid};
use truck_modeling::{
base::{BoundedCurve, ParametricCurve},
BSplineCurve as TruckBSpline, KnotVec, Point3,
};
use acadrust::entities::{Block, BlockEnd, Insert as DxfInsert};
use acadrust::objects::ObjectType;
use acadrust::types::Vector2;
@@ -1668,12 +1672,35 @@ impl Scene {
}
}
BoundaryEdge::Spline(spline) => {
for cp in &spline.control_points {
boundary.push([cp.x as f32, cp.y as f32]);
}
if boundary.len() > 1 {
if let Some(&first) = boundary.first() {
boundary.push(first);
// Evaluate the B-spline curve into smooth boundary points.
// Fall back to control-point polyline for degenerate inputs.
let degree = spline.degree as usize;
let cps: Vec<Point3> = spline.control_points
.iter()
.map(|p| Point3::new(p.x, p.y, 0.0))
.collect();
let knot_vec = if !spline.knots.is_empty() {
KnotVec::from(spline.knots.clone())
} else if cps.len() >= 2 {
KnotVec::uniform_knot(degree, cps.len() - 1)
} else {
KnotVec::from(vec![])
};
let ok = cps.len() >= 2
&& degree >= 1
&& knot_vec.len() == cps.len() + degree + 1;
if ok {
let bspl = TruckBSpline::new(knot_vec, cps);
let (t0, t1) = bspl.range_tuple();
let segs = 16u32;
for i in 0..=segs {
let t = t0 + (t1 - t0) * (i as f64 / segs as f64);
let p = bspl.subs(t);
boundary.push([p.x as f32, p.y as f32]);
}
} else {
for cp in &spline.control_points {
boundary.push([cp.x as f32, cp.y as f32]);
}
}
}