feat(spline): correct property semantics
Merge PR #824 while keeping periodic and coplanarity geometry in cadkernel.
This commit is contained in:
commit
dcedf945ea
11 changed files with 208 additions and 73 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"] }
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue