feat(spline): correct property semantics

Merge PR #824 while keeping periodic and coplanarity geometry in cadkernel.
This commit is contained in:
Hakan Seven 2026-08-20 12:13:35 +03:00
commit dcedf945ea
11 changed files with 208 additions and 73 deletions

4
Cargo.lock generated
View file

@ -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",

View file

@ -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"] }

View file

@ -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"

View file

@ -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"

View file

@ -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)))

View file

@ -1758,7 +1758,7 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
};
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<Message> {
(
"current_control_point",
acadrust::EntityType::Spline(spline),
) => Some(spline.control_points.len()),
) => Some(
crate::entities::spline::control_vertex_count(spline),
),
_ => None,
}
})

View file

@ -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)

View file

@ -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<String>) -> Property {
Property {
label: label.into(),
@ -574,8 +584,13 @@ pub fn stepper_prop(
pub fn parse_f64(value: &str) -> Option<f64> {
let t = value.trim();
// Angle rows display via AUNITS (#297) — accept those formats back.
t.parse::<f64>().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::<f64>()
.ok()
.or_else(|| parse_length(t))
.or_else(|| parse_angle_deg(t))
}
/// Parse an angle string the panel displayed via AUNITS back to DEGREES:

View file

@ -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<PlanarCurve> {
/// 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<PlanarCurve> {
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<Vector3> = 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,

View file

@ -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<f64>, [Vec<f64>; 3])> {
let n = p.len();
@ -439,6 +463,10 @@ fn control_vertices(spline: &Spline) -> Vec<acadrust::types::Vector3> {
.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<GripDef> {
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<GripDef> {
fn properties(spline: &Spline) -> Vec<PropSection> {
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<PropSection> {
};
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<PropSection> {
},
]
} 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<PropSection> {
"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<PropSection> {
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<PropSection> {
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;
}

View file

@ -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,