diff --git a/Cargo.lock b/Cargo.lock index 829cec057..69d6628e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -72,7 +72,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] name = "acadrust" version = "0.4.1" -source = "git+https://github.com/HakanSeven12/cadcodec.git?rev=931c4ab#931c4ab0c590b755e280bed318a35f41c57b139f" +source = "git+https://github.com/HakanSeven12/cadcodec.git?rev=0975677#0975677029f2b472759db00e9692421a5831ad00" dependencies = [ "ahash 0.8.12", "anyhow", @@ -878,7 +878,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "cadkernel" version = "0.1.0" -source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=ff950ce#ff950ce4146c9fb94a5478e6704ada01264dbf9f" +source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=2af87ad#2af87adf3e3234fd9e8df1bfe01faf7e2b6333d1" dependencies = [ "acadrust", "cavalier_contours", diff --git a/Cargo.toml b/Cargo.toml index 41d691137..bea72a9ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,8 +27,8 @@ glam = { version = "0.33", features = ["bytemuck"] } rfd = "0.17" clap = { version = "4", features = ["derive"] } env_logger = "0.11" -acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "931c4ab", features = ["serde"] } -cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "ff950ce", features = ["acis", "offset"] } +acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "0975677", features = ["serde"] } +cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "2af87ad", features = ["acis", "offset"] } dwg-thumbnailer = { path = "crates/dwg-thumbnailer" } flate2 = "1" image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] } diff --git a/crates/ocs_plugin_api/Cargo.toml b/crates/ocs_plugin_api/Cargo.toml index a2f1a505c..5a540bdc4 100644 --- a/crates/ocs_plugin_api/Cargo.toml +++ b/crates/ocs_plugin_api/Cargo.toml @@ -14,7 +14,7 @@ serde = { version = "1", features = ["derive"] } # Pulled in only by the `host` feature, which adds the `acadrust`-typed # `HostApi` runtime surface. The default crate stays dependency-free so engine # crates and external tooling can depend on the manifest/ribbon contract cheaply. -acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "931c4ab", optional = true, features = ["serde"] } +acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "0975677", optional = true, features = ["serde"] } # Runtime IPC and serialization (host feature only). interprocess = { version = "2", optional = true } @@ -37,7 +37,7 @@ serde_json = "1" serde = { version = "1", features = ["derive"] } cargo-lock = "11" # acadrust is scanned at build time to generate the embedded type registry. -acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "931c4ab", features = ["serde"] } +acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "0975677", features = ["serde"] } [dev-dependencies] serde_json = "1" diff --git a/crates/ocs_web_worker/Cargo.toml b/crates/ocs_web_worker/Cargo.toml index b33a568bd..a38821d6d 100644 --- a/crates/ocs_web_worker/Cargo.toml +++ b/crates/ocs_web_worker/Cargo.toml @@ -8,7 +8,7 @@ publish = false crate-type = ["cdylib"] [dependencies] -acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "931c4ab", features = ["serde"] } +acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "0975677", features = ["serde"] } bincode = "1.3" serde = { version = "1", features = ["derive"] } console_error_panic_hook = "0.1" diff --git a/src/app/properties.rs b/src/app/properties.rs index 9576564a4..e4dc29f93 100644 --- a/src/app/properties.rs +++ b/src/app/properties.rs @@ -121,13 +121,13 @@ impl OpenCADStudio { acadrust::EntityType::LwPolyline(polyline) => Some(polyline.vertices.len()), acadrust::EntityType::Polyline2D(polyline) => Some(polyline.vertices.len()), acadrust::EntityType::Leader(leader) => Some(leader.vertices.len()), - acadrust::EntityType::Spline(spline) => Some( - if crate::entities::spline::uses_fit_method(spline) { + acadrust::EntityType::Spline(spline) => { + Some(if crate::entities::spline::shows_fit_points(spline) { spline.fit_points.len() } else { - spline.control_points.len() - }, - ), + crate::entities::spline::control_vertex_count(spline) + }) + } _ => None, }); vertex_count.map_or(prop_vertex, |count| prop_vertex.min(count.saturating_sub(1))) diff --git a/src/app/update/command.rs b/src/app/update/command.rs index 77949dd9c..635931afc 100644 --- a/src/app/update/command.rs +++ b/src/app/update/command.rs @@ -1758,7 +1758,7 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task { }; match field { "spline_method" => { - value == if crate::entities::spline::uses_fit_method(spline) { + value == if crate::entities::spline::shows_fit_points(spline) { "Fit" } else { "Control Vertices" @@ -2193,7 +2193,9 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task { ( "current_control_point", acadrust::EntityType::Spline(spline), - ) => Some(spline.control_points.len()), + ) => Some( + crate::entities::spline::control_vertex_count(spline), + ), _ => None, } }) diff --git a/src/app/view/mod.rs b/src/app/view/mod.rs index 5acbd8cff..fcd26fbe1 100644 --- a/src/app/view/mod.rs +++ b/src/app/view/mod.rs @@ -423,12 +423,8 @@ impl OpenCADStudio { .and_then(|h| { let indexed = match tab.scene.document.get_entity(h) { Some(acadrust::EntityType::LwPolyline(_)) - | Some(acadrust::EntityType::Polyline2D(_)) => true, - Some(acadrust::EntityType::Spline(spline)) => { - !crate::entities::spline::uses_fit_method(spline) - || !spline.cv_frame_visible - || crate::entities::curve::spline_curve(spline).is_none() - } + | Some(acadrust::EntityType::Polyline2D(_)) + | Some(acadrust::EntityType::Spline(_)) => true, _ => false, }; indexed.then_some(tab.properties.prop_vertex) diff --git a/src/entities/common.rs b/src/entities/common.rs index 4542591ca..f67bc2baf 100644 --- a/src/entities/common.rs +++ b/src/entities/common.rs @@ -535,6 +535,16 @@ pub fn edit_prop(label: &str, field: &'static str, value: f64) -> Property { } } +/// Editable dimensionless value independent of drawing units. +pub fn edit_scalar_prop(label: &str, field: &'static str, value: f64) -> Property { + let value = if value == 0.0 { 0.0 } else { value }; + Property { + label: label.into(), + field, + value: PropValue::EditText(value.to_string()), + } +} + pub fn ro_prop(label: &str, field: &'static str, value: impl Into) -> Property { Property { label: label.into(), @@ -574,8 +584,13 @@ pub fn stepper_prop( pub fn parse_f64(value: &str) -> Option { let t = value.trim(); - // Angle rows display via AUNITS (#297) — accept those formats back. - t.parse::().ok().or_else(|| parse_angle_deg(t)) + // Length rows display via LUNITS and angle rows via AUNITS. Accept both + // representations back so a value shown by Properties can always be + // committed unchanged. + t.parse::() + .ok() + .or_else(|| parse_length(t)) + .or_else(|| parse_angle_deg(t)) } /// Parse an angle string the panel displayed via AUNITS back to DEGREES: diff --git a/src/entities/curve.rs b/src/entities/curve.rs index d30077733..f477a83b8 100644 --- a/src/entities/curve.rs +++ b/src/entities/curve.rs @@ -32,13 +32,13 @@ use acadrust::entities::{ Arc as ArcEnt, Circle as CircleEnt, Ellipse as EllipseEnt, LwPolyline as LwPolylineEnt, Polyline2D, Spline as SplineEnt, }; +use acadrust::types::Vector3; +use acadrust::EntityType; use cadkernel::geom2d::{ characteristic_points, Arc, Circle, Curve, Ellipse, EllipseArc, Line, Polyline, PolylineVertex, Ray, SnapKind, Transform, XLine, }; -use cadkernel::space::{PlanarCurve, Plane, Vec3}; -use acadrust::types::Vector3; -use acadrust::EntityType; +use cadkernel::space::{are_coplanar, coplanarity_tolerance, PlanarCurve, Plane, Vec3}; use crate::modules::draw::modify::spline_ops::spline_to_nurbs_on; use crate::scene::model::wire_model::SnapHint; @@ -287,27 +287,61 @@ pub fn polyline2d_curve(polyline: &Polyline2D) -> Option { /// plane the normal describes. A spline that genuinely wanders in space gets /// `None`, which is honest: flattening it to XY would move it. pub fn spline_curve(spline: &SplineEnt) -> Option { - let source = if crate::entities::spline::uses_fit_method(spline) { + let fit_method = crate::entities::spline::uses_fit_method(spline); + let source = if fit_method { &spline.fit_points } else { &spline.control_points }; let points: Vec = source.to_vec(); let first = points.first()?; + if !spline_is_planar(spline) { + return None; + } let normal = normalized(spline.normal); let elevation = Vec3::from(xyz(*first)).dot(Vec3::from(xyz(normal))); let plane = ocs_plane(normal, elevation); - let tolerance = PLANARITY_TOLERANCE * scale_of(&points); + let point_arrays: Vec<[f64; 3]> = points.iter().copied().map(xyz).collect(); + let tolerance = coplanarity_tolerance(&point_arrays); if !points.iter().all(|p| plane.contains(xyz(*p), tolerance)) { return None; } + if fit_method && !spline.flags.periodic { + let plane_normal = Vec3::from(plane.normal()?); + for tangent in [spline.begin_tangent, spline.end_tangent] { + let tangent = Vec3::from(xyz(tangent)); + if tangent.length_squared() > 1e-18 + && tangent.dot(plane_normal).abs() + > PLANARITY_TOLERANCE * tangent.length().max(1.0) + { + return None; + } + } + } Some(PlanarCurve::new( plane, Curve::Nurbs(spline_to_nurbs_on(spline, &plane)?), )) } +/// Whether the defining points and active tangents share a plane. +pub fn spline_is_planar(spline: &SplineEnt) -> bool { + let fit_method = crate::entities::spline::uses_fit_method(spline); + let points = if fit_method { + &spline.fit_points + } else { + &spline.control_points + }; + let points: Vec<[f64; 3]> = points.iter().copied().map(xyz).collect(); + let directions: Vec<[f64; 3]> = if fit_method && !spline.flags.periodic { + vec![xyz(spline.begin_tangent), xyz(spline.end_tangent)] + } else { + Vec::new() + }; + are_coplanar(&points, &directions) +} + /// The entity's curve in world XY coordinates. /// /// The editing commands — TRIM, EXTEND, FILLET, OFFSET — work in plan view, diff --git a/src/entities/spline.rs b/src/entities/spline.rs index 9a4f5d035..5c143ea47 100644 --- a/src/entities/spline.rs +++ b/src/entities/spline.rs @@ -1,21 +1,26 @@ use acadrust::entities::Spline; -use crate::t; use cadkernel::geom2d::Curve as KernelCurve; -use cadkernel::space::NurbsCurve3; +use cadkernel::space::{NurbsCurve3, Parameterization}; use crate::command::EntityTransform; use crate::entities::common::{ - dropdown_grip, edit_prop as edit, parse_f64, ro_prop as ro, round_grip, square_grip, + dropdown_grip, edit_prop as edit, edit_scalar_prop as edit_scalar, format_length, + parse_f64, ro_prop as ro, round_grip, square_grip, }; use crate::entities::traits::RenderConvertible; use crate::scene::convert::acad_to_render::{RenderEntity, RenderObject}; use crate::scene::model::object::{GripApply, GripDef, PropSection, PropValue, Property}; +use crate::t; pub(crate) fn uses_fit_method(spline: &Spline) -> bool { let degree = spline.degree.max(1) as usize; !spline.fit_points.is_empty() && spline.control_points.len() <= degree } +pub(crate) fn shows_fit_points(spline: &Spline) -> bool { + uses_fit_method(spline) && (!spline.cv_frame_visible || spline.flags.periodic) +} + fn to_render(spl: &Spline) -> RenderEntity { let n = spl.control_points.len(); if n < 2 { @@ -36,7 +41,8 @@ fn to_render(spl: &Spline) -> RenderEntity { // A fit spline through points in space is not a planar curve, // so the kernel has nothing to say about it and the solve // here remains the only description of its shape. - None if spl.flags.closed || spl.flags.periodic => { + None if spl.flags.periodic => periodic_fit_spline_polyline(spl), + None if spl.flags.closed => { catmull_rom_polyline(&spl.fit_points, true) } None => fit_spline_polyline(spl), @@ -155,7 +161,9 @@ pub(crate) fn measurement_polyline(spl: &Spline) -> Vec<[f64; 3]> { if spl.fit_points.len() < 2 { return spl.fit_points.iter().map(|p| [p.x, p.y, p.z]).collect(); } - return if spl.flags.closed || spl.flags.periodic { + return if spl.flags.periodic { + periodic_fit_spline_polyline(spl) + } else if spl.flags.closed { catmull_rom_polyline(&spl.fit_points, true) } else { fit_spline_polyline(spl) @@ -322,6 +330,22 @@ fn fit_spline_polyline(spl: &Spline) -> Vec<[f64; 3]> { out } +fn periodic_fit_spline_polyline(spl: &Spline) -> Vec<[f64; 3]> { + let points: Vec<[f64; 3]> = spl + .fit_points + .iter() + .map(|point| [point.x, point.y, point.z]) + .collect(); + let parameterization = match spl.knot_parameterization { + 2 => Parameterization::Uniform, + 1 => Parameterization::Centripetal, + _ => Parameterization::Chord, + }; + NurbsCurve3::interpolate_periodic(&points, parameterization) + .map(|curve| curve.tessellate_angle(cadkernel::tessellation::DEFAULT_ANGLE)) + .unwrap_or_else(|| catmull_rom_polyline(&spl.fit_points, true)) +} + /// Slopes used by spatial fit interpolation. fn fit_spline_slopes(spl: &Spline, p: &[[f64; 3]]) -> Option<(Vec, [Vec; 3])> { let n = p.len(); @@ -439,6 +463,10 @@ fn control_vertices(spline: &Spline) -> Vec { .collect() } +pub(crate) fn control_vertex_count(spline: &Spline) -> usize { + control_vertices(spline).len() +} + fn choice_prop( label: &str, field: &'static str, @@ -504,10 +532,6 @@ fn convert_to_fit_method(spline: &mut Spline) -> bool { uses_fit_method(spline) } -fn is_planar(spline: &Spline) -> bool { - crate::entities::curve::spline_curve(spline).is_some() -} - fn tangent_is_set(tangent: &acadrust::types::Vector3) -> bool { tangent.x * tangent.x + tangent.y * tangent.y + tangent.z * tangent.z > 1e-18 } @@ -557,6 +581,7 @@ fn grips(spline: &Spline) -> Vec { let fit_method = uses_fit_method(spline); let derived_control = control_vertices(spline); let show_control_vertices = (spline.cv_frame_visible || !fit_method) + && !(fit_method && spline.flags.periodic) && derived_control.len() >= 2; let source = if show_control_vertices { derived_control @@ -588,9 +613,11 @@ fn grips(spline: &Spline) -> Vec { fn properties(spline: &Spline) -> Vec { let fit_method = uses_fit_method(spline); - let method = if fit_method { "Fit" } else { "Control Vertices" }; + let show_fit = shows_fit_points(spline); + let method = if show_fit { "Fit" } else { "Control Vertices" }; let closed = spline.flags.closed || spline.flags.periodic; - let can_show_control = control_vertices(spline).len() >= 2; + let can_show_control = control_vertices(spline).len() >= 2 + && !(fit_method && spline.flags.periodic); let yes_no = |b: bool| if b { "Yes" } else { "No" }; let knot_param = match spline.knot_parameterization { 0 => "Chord", @@ -601,7 +628,7 @@ fn properties(spline: &Spline) -> Vec { }; let current = crate::scene::view::dispatch::prop_current_vertex(); let (effective_begin_tangent, effective_end_tangent) = effective_fit_tangents(spline); - let mut data_points = if fit_method { + let mut data_points = if show_fit { let count = spline.fit_points.len(); let index = current.min(count.saturating_sub(1)); let point = spline.fit_points.get(index); @@ -648,9 +675,10 @@ fn properties(spline: &Spline) -> Vec { }, ] } else { - let count = spline.control_points.len(); + let controls = control_vertices(spline); + let count = controls.len(); let index = current.min(count.saturating_sub(1)); - let point = spline.control_points.get(index); + let point = controls.get(index); let weight = spline.weights.get(index).copied().unwrap_or(1.0); vec![ ro( @@ -679,7 +707,7 @@ fn properties(spline: &Spline) -> Vec { "ctrl_pt_z", point.map(|value| value.z).unwrap_or(0.0), ), - edit(t!("Weight").as_ref(), "weight", weight), + edit_scalar(t!("Weight").as_ref(), "weight", weight), ] }; data_points.push(if can_show_control { @@ -723,48 +751,51 @@ fn properties(spline: &Spline) -> Vec { ro( t!("Planar").as_ref(), "planar", - crate::i18n::translate(yes_no(is_planar(spline))).into_owned(), + crate::i18n::translate(yes_no(crate::entities::curve::spline_is_planar(spline))) + .into_owned(), ), ]; - if fit_method { + if show_fit && !spline.flags.periodic { misc.extend([ - edit( + edit_scalar( t!("Start tangent vector X").as_ref(), "start_tan_x", effective_begin_tangent.x, ), - edit( + edit_scalar( t!("Start tangent vector Y").as_ref(), "start_tan_y", effective_begin_tangent.y, ), - edit( + edit_scalar( t!("Start tangent vector Z").as_ref(), "start_tan_z", effective_begin_tangent.z, ), - edit( + edit_scalar( t!("End tangent vector X").as_ref(), "end_tan_x", effective_end_tangent.x, ), - edit( + edit_scalar( t!("End tangent vector Y").as_ref(), "end_tan_y", effective_end_tangent.y, ), - edit( + edit_scalar( t!("End tangent vector Z").as_ref(), "end_tan_z", effective_end_tangent.z, ), - edit( - t!("Fit tolerance").as_ref(), - "fit_tolerance", - spline.fit_tolerance, - ), ]); } + if show_fit { + misc.push(ro( + t!("Fit tolerance").as_ref(), + "fit_tolerance", + format_length(spline.fit_tolerance), + )); + } vec![ PropSection { @@ -781,8 +812,14 @@ fn properties(spline: &Spline) -> Vec { fn apply_geom_prop(spline: &mut Spline, field: &str, value: &str) { match field { "spline_method" => { - if value == "Control Vertices" { - convert_to_control_method(spline); + if value == "Fit" { + if convert_to_fit_method(spline) { + spline.cv_frame_visible = false; + } + } else if value == "Control Vertices" { + if !spline.flags.periodic && control_vertices(spline).len() >= 2 { + spline.cv_frame_visible = true; + } } return; } @@ -796,11 +833,40 @@ fn apply_geom_prop(spline: &mut Spline, field: &str, value: &str) { return; } "cv_frame" => { - spline.cv_frame_visible = value == "Show" && control_vertices(spline).len() >= 2; + spline.cv_frame_visible = value == "Show" + && !(uses_fit_method(spline) && spline.flags.periodic) + && control_vertices(spline).len() >= 2; return; } _ => {} } + if uses_fit_method(spline) + && spline.flags.periodic + && matches!( + field, + "ctrl_pt_x" + | "ctrl_pt_y" + | "ctrl_pt_z" + | "weight" + | "start_tan_x" + | "start_tan_y" + | "start_tan_z" + | "end_tan_x" + | "end_tan_y" + | "end_tan_z" + ) + { + return; + } + if field == "fit_tolerance" { + return; + } + if matches!(field, "ctrl_pt_x" | "ctrl_pt_y" | "ctrl_pt_z" | "weight") + && uses_fit_method(spline) + && !convert_to_control_method(spline) + { + return; + } let (effective_begin_tangent, effective_end_tangent) = effective_fit_tangents(spline); let Some(v) = parse_f64(value) else { return }; let control_index = crate::scene::view::dispatch::prop_current_vertex() @@ -850,7 +916,6 @@ fn apply_geom_prop(spline: &mut Spline, field: &str, value: &str) { fp.z = v; } } - "fit_tolerance" if v >= 0.0 => spline.fit_tolerance = v, "start_tan_x" | "start_tan_y" | "start_tan_z" => { if !tangent_is_set(&spline.begin_tangent) { spline.begin_tangent = effective_begin_tangent; @@ -875,7 +940,7 @@ fn apply_geom_prop(spline: &mut Spline, field: &str, value: &str) { } _ => {} } - spline.flags.planar = is_planar(spline); + spline.flags.planar = crate::entities::curve::spline_is_planar(spline); } fn apply_grip(spline: &mut Spline, grip_id: usize, apply: GripApply) { @@ -884,12 +949,12 @@ fn apply_grip(spline: &mut Spline, grip_id: usize, apply: GripApply) { } // First CV edit makes the displayed kernel controls authoritative. let fit_method = uses_fit_method(spline); - if spline.cv_frame_visible && fit_method { + if spline.cv_frame_visible && fit_method && !spline.flags.periodic { if !convert_to_control_method(spline) { return; } } - let target = if spline.cv_frame_visible || !fit_method { + let target = if (spline.cv_frame_visible && !spline.flags.periodic) || !fit_method { spline.control_points.get_mut(grip_id) } else { spline.fit_points.get_mut(grip_id) @@ -911,6 +976,21 @@ fn apply_grip(spline: &mut Spline, grip_id: usize, apply: GripApply) { } fn apply_transform(spline: &mut Spline, t: &EntityTransform) { + if let EntityTransform::Mirror { + p1, + p2, + working_normal, + } = t + { + let transform = crate::scene::view::transform::reflection_about_working_line( + *p1, + *p2, + *working_normal, + ); + acadrust::Entity::apply_transform(spline, &transform); + spline.flags.planar = crate::entities::curve::spline_is_planar(spline); + return; + } crate::scene::view::transform::apply_standard_entity_transform(spline, t, |entity, p1, p2| { for cp in &mut entity.control_points { crate::scene::view::transform::reflect_xy_point(&mut cp.x, &mut cp.y, p1, p2); @@ -919,6 +999,7 @@ fn apply_transform(spline: &mut Spline, t: &EntityTransform) { crate::scene::view::transform::reflect_xy_point(&mut fp.x, &mut fp.y, p1, p2); } }); + spline.flags.planar = crate::entities::curve::spline_is_planar(spline); } impl RenderConvertible for Spline { @@ -938,8 +1019,9 @@ impl crate::entities::traits::Grippable for Spline { use crate::scene::model::object::{GripMenuAction, GripMenuItem}; if grip_id == SPLINE_MODE_GRIP_ID { let fit_method = uses_fit_method(self); - let can_show_control = control_vertices(self).len() >= 2; - return if self.cv_frame_visible || !fit_method { + let can_show_control = control_vertices(self).len() >= 2 + && !(fit_method && self.flags.periodic); + return if !shows_fit_points(self) { vec![ (can_show_control && fit_method).then_some(GripMenuItem { label: "Fit", @@ -987,7 +1069,10 @@ impl crate::entities::traits::Grippable for Spline { return; } } - A::ShowControlVertices if control_vertices(self).len() >= 2 => { + A::ShowControlVertices + if !(uses_fit_method(self) && self.flags.periodic) + && control_vertices(self).len() >= 2 => + { self.cv_frame_visible = true; return; } diff --git a/src/modules/draw/modify/spline_ops.rs b/src/modules/draw/modify/spline_ops.rs index f3c98f2be..c40bdaac0 100644 --- a/src/modules/draw/modify/spline_ops.rs +++ b/src/modules/draw/modify/spline_ops.rs @@ -10,10 +10,10 @@ // fine until it is measured. use acadrust::entities::Spline; -use cadkernel::geom2d::{NurbsCurve, Parameterization}; -use cadkernel::space::Plane; use acadrust::types::Vector3; use acadrust::Handle; +use cadkernel::geom2d::{NurbsCurve, Parameterization}; +use cadkernel::space::Plane; /// The drawing plane a spline sits on. /// @@ -79,7 +79,15 @@ fn spline_to_nurbs_with( // No usable control polygon, so this is a fit-point spline. let mut fit: Vec<[f64; 2]> = spl.fit_points.iter().map(&point).collect(); - if spl.flags.closed || spl.flags.periodic { + let parameterization = match spl.knot_parameterization { + 2 => Parameterization::Uniform, + 1 => Parameterization::Centripetal, + _ => Parameterization::Chord, + }; + if spl.flags.periodic { + return NurbsCurve::interpolate_periodic(&fit, parameterization); + } + if spl.flags.closed { // The interpolation is a clamped solve and does not model a wrap, so // a closed spline came back as an open curve that never returned to // its start — and a TRIM against it then cut nothing along the seam. @@ -98,11 +106,6 @@ fn spline_to_nurbs_with( }; let start_tangent = tangent(&spl.begin_tangent); let end_tangent = tangent(&spl.end_tangent); - let parameterization = match spl.knot_parameterization { - 2 => Parameterization::Uniform, - 1 => Parameterization::Centripetal, - _ => Parameterization::Chord, - }; NurbsCurve::interpolate( &fit, start_tangent,