From 851edd530f933541b4c20fb7d6e93307aff1271d Mon Sep 17 00:00:00 2001 From: Royce Date: Mon, 12 Oct 2026 09:38:50 +0800 Subject: [PATCH] fix(cad): set the closed bit on DXF SPLINE entities so closed curves import into CAM (#1299) A subpath that geometrically closes (e.g. an ellipse outline) was always emitted with SPLINE group 70 = 8 (planar only). Autodesk's DXF reference defines bit 0 of group 70 as 'closed', and CAM software such as VCarve relies on it to treat the cut as a pocket or profile rather than asking the operator to join endpoints. Now `self.e.int(70, if sub.closed { 8 | 1 } else { 8 })`. Test: extend lines_are_lwpolylines_curves_splines_and_fills_hatches to assert the existing closed ellipse's SPLINE group 70 is '9' (1 closed + 8 planar). --- crates/cad/src/scene.rs | 6 ++++-- crates/cad/src/tests.rs | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/cad/src/scene.rs b/crates/cad/src/scene.rs index abd2d48d..a8a9aa1d 100644 --- a/crates/cad/src/scene.rs +++ b/crates/cad/src/scene.rs @@ -652,8 +652,10 @@ impl<'a> Scene<'a> { let h = self.handle(); self.e.entity("SPLINE", h, fixed::MODEL_RECORD, &layer, style, "AcDbSpline"); self.e.normal(); - // Planar. - self.e.int(70, 8); + // Planar (bit 3) and, if the subpath is closed, also closed (bit 0). A geometrically + // closed cut needs the closed flag set so CAM/CAM tools (e.g. VCarve) treat it as a + // pocket/profile rather than asking the operator to join endpoints. (#1299) + self.e.int(70, if sub.closed { 8 | 1 } else { 8 }); self.e.int(71, 3); self.e.int(72, knots.len() as i64); self.e.int(73, ctrl.len() as i64); diff --git a/crates/cad/src/tests.rs b/crates/cad/src/tests.rs index 5d6c50e7..87023aa3 100644 --- a/crates/cad/src/tests.rs +++ b/crates/cad/src/tests.rs @@ -155,6 +155,7 @@ fn lines_are_lwpolylines_curves_splines_and_fills_hatches() { // The ellipse: four cubics through clamped knots. let spline = &e[3]; assert_eq!((get(spline, 71), get(spline, 73)), (Some("3"), Some("13"))); + assert_eq!(get(spline, 70), Some("9"), "closed planar spline (#1299): 1 = closed, 8 = planar"); assert_eq!(all(spline, 40), [0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 3.0, 3.0, 3.0, 4.0, 4.0, 4.0, 4.0]); // R14 has lightweight polylines but no lineweights: the stroke is a polyline width. let r14 = export(&d, &DxfOptions { version: DxfVersion::R14, ..opts(&d) }).unwrap();