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]]
|
[[package]]
|
||||||
name = "acadrust"
|
name = "acadrust"
|
||||||
version = "0.4.1"
|
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 = [
|
dependencies = [
|
||||||
"ahash 0.8.12",
|
"ahash 0.8.12",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
|
@ -878,7 +878,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cadkernel"
|
name = "cadkernel"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"acadrust",
|
"acadrust",
|
||||||
"cavalier_contours",
|
"cavalier_contours",
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,8 @@ glam = { version = "0.33", features = ["bytemuck"] }
|
||||||
rfd = "0.17"
|
rfd = "0.17"
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "931c4ab", features = ["serde"] }
|
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "0975677", features = ["serde"] }
|
||||||
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "ff950ce", features = ["acis", "offset"] }
|
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "2af87ad", features = ["acis", "offset"] }
|
||||||
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
|
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
|
||||||
flate2 = "1"
|
flate2 = "1"
|
||||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }
|
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
|
# Pulled in only by the `host` feature, which adds the `acadrust`-typed
|
||||||
# `HostApi` runtime surface. The default crate stays dependency-free so engine
|
# `HostApi` runtime surface. The default crate stays dependency-free so engine
|
||||||
# crates and external tooling can depend on the manifest/ribbon contract cheaply.
|
# 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).
|
# Runtime IPC and serialization (host feature only).
|
||||||
interprocess = { version = "2", optional = true }
|
interprocess = { version = "2", optional = true }
|
||||||
|
|
@ -37,7 +37,7 @@ serde_json = "1"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
cargo-lock = "11"
|
cargo-lock = "11"
|
||||||
# acadrust is scanned at build time to generate the embedded type registry.
|
# 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]
|
[dev-dependencies]
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ publish = false
|
||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[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"
|
bincode = "1.3"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
console_error_panic_hook = "0.1"
|
console_error_panic_hook = "0.1"
|
||||||
|
|
|
||||||
|
|
@ -121,13 +121,13 @@ impl OpenCADStudio {
|
||||||
acadrust::EntityType::LwPolyline(polyline) => Some(polyline.vertices.len()),
|
acadrust::EntityType::LwPolyline(polyline) => Some(polyline.vertices.len()),
|
||||||
acadrust::EntityType::Polyline2D(polyline) => Some(polyline.vertices.len()),
|
acadrust::EntityType::Polyline2D(polyline) => Some(polyline.vertices.len()),
|
||||||
acadrust::EntityType::Leader(leader) => Some(leader.vertices.len()),
|
acadrust::EntityType::Leader(leader) => Some(leader.vertices.len()),
|
||||||
acadrust::EntityType::Spline(spline) => Some(
|
acadrust::EntityType::Spline(spline) => {
|
||||||
if crate::entities::spline::uses_fit_method(spline) {
|
Some(if crate::entities::spline::shows_fit_points(spline) {
|
||||||
spline.fit_points.len()
|
spline.fit_points.len()
|
||||||
} else {
|
} else {
|
||||||
spline.control_points.len()
|
crate::entities::spline::control_vertex_count(spline)
|
||||||
},
|
})
|
||||||
),
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
});
|
});
|
||||||
vertex_count.map_or(prop_vertex, |count| prop_vertex.min(count.saturating_sub(1)))
|
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 {
|
match field {
|
||||||
"spline_method" => {
|
"spline_method" => {
|
||||||
value == if crate::entities::spline::uses_fit_method(spline) {
|
value == if crate::entities::spline::shows_fit_points(spline) {
|
||||||
"Fit"
|
"Fit"
|
||||||
} else {
|
} else {
|
||||||
"Control Vertices"
|
"Control Vertices"
|
||||||
|
|
@ -2193,7 +2193,9 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||||
(
|
(
|
||||||
"current_control_point",
|
"current_control_point",
|
||||||
acadrust::EntityType::Spline(spline),
|
acadrust::EntityType::Spline(spline),
|
||||||
) => Some(spline.control_points.len()),
|
) => Some(
|
||||||
|
crate::entities::spline::control_vertex_count(spline),
|
||||||
|
),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -423,12 +423,8 @@ impl OpenCADStudio {
|
||||||
.and_then(|h| {
|
.and_then(|h| {
|
||||||
let indexed = match tab.scene.document.get_entity(h) {
|
let indexed = match tab.scene.document.get_entity(h) {
|
||||||
Some(acadrust::EntityType::LwPolyline(_))
|
Some(acadrust::EntityType::LwPolyline(_))
|
||||||
| Some(acadrust::EntityType::Polyline2D(_)) => true,
|
| Some(acadrust::EntityType::Polyline2D(_))
|
||||||
Some(acadrust::EntityType::Spline(spline)) => {
|
| Some(acadrust::EntityType::Spline(_)) => true,
|
||||||
!crate::entities::spline::uses_fit_method(spline)
|
|
||||||
|| !spline.cv_frame_visible
|
|
||||||
|| crate::entities::curve::spline_curve(spline).is_none()
|
|
||||||
}
|
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
indexed.then_some(tab.properties.prop_vertex)
|
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 {
|
pub fn ro_prop(label: &str, field: &'static str, value: impl Into<String>) -> Property {
|
||||||
Property {
|
Property {
|
||||||
label: label.into(),
|
label: label.into(),
|
||||||
|
|
@ -574,8 +584,13 @@ pub fn stepper_prop(
|
||||||
|
|
||||||
pub fn parse_f64(value: &str) -> Option<f64> {
|
pub fn parse_f64(value: &str) -> Option<f64> {
|
||||||
let t = value.trim();
|
let t = value.trim();
|
||||||
// Angle rows display via AUNITS (#297) — accept those formats back.
|
// Length rows display via LUNITS and angle rows via AUNITS. Accept both
|
||||||
t.parse::<f64>().ok().or_else(|| parse_angle_deg(t))
|
// 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:
|
/// 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,
|
Arc as ArcEnt, Circle as CircleEnt, Ellipse as EllipseEnt, LwPolyline as LwPolylineEnt,
|
||||||
Polyline2D, Spline as SplineEnt,
|
Polyline2D, Spline as SplineEnt,
|
||||||
};
|
};
|
||||||
|
use acadrust::types::Vector3;
|
||||||
|
use acadrust::EntityType;
|
||||||
use cadkernel::geom2d::{
|
use cadkernel::geom2d::{
|
||||||
characteristic_points, Arc, Circle, Curve, Ellipse, EllipseArc, Line, Polyline, PolylineVertex,
|
characteristic_points, Arc, Circle, Curve, Ellipse, EllipseArc, Line, Polyline, PolylineVertex,
|
||||||
Ray, SnapKind, Transform, XLine,
|
Ray, SnapKind, Transform, XLine,
|
||||||
};
|
};
|
||||||
use cadkernel::space::{PlanarCurve, Plane, Vec3};
|
use cadkernel::space::{are_coplanar, coplanarity_tolerance, PlanarCurve, Plane, Vec3};
|
||||||
use acadrust::types::Vector3;
|
|
||||||
use acadrust::EntityType;
|
|
||||||
|
|
||||||
use crate::modules::draw::modify::spline_ops::spline_to_nurbs_on;
|
use crate::modules::draw::modify::spline_ops::spline_to_nurbs_on;
|
||||||
use crate::scene::model::wire_model::SnapHint;
|
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
|
/// plane the normal describes. A spline that genuinely wanders in space gets
|
||||||
/// `None`, which is honest: flattening it to XY would move it.
|
/// `None`, which is honest: flattening it to XY would move it.
|
||||||
pub fn spline_curve(spline: &SplineEnt) -> Option<PlanarCurve> {
|
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
|
&spline.fit_points
|
||||||
} else {
|
} else {
|
||||||
&spline.control_points
|
&spline.control_points
|
||||||
};
|
};
|
||||||
let points: Vec<Vector3> = source.to_vec();
|
let points: Vec<Vector3> = source.to_vec();
|
||||||
let first = points.first()?;
|
let first = points.first()?;
|
||||||
|
if !spline_is_planar(spline) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let normal = normalized(spline.normal);
|
let normal = normalized(spline.normal);
|
||||||
let elevation = Vec3::from(xyz(*first)).dot(Vec3::from(xyz(normal)));
|
let elevation = Vec3::from(xyz(*first)).dot(Vec3::from(xyz(normal)));
|
||||||
let plane = ocs_plane(normal, elevation);
|
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)) {
|
if !points.iter().all(|p| plane.contains(xyz(*p), tolerance)) {
|
||||||
return None;
|
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(
|
Some(PlanarCurve::new(
|
||||||
plane,
|
plane,
|
||||||
Curve::Nurbs(spline_to_nurbs_on(spline, &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 entity's curve in world XY coordinates.
|
||||||
///
|
///
|
||||||
/// The editing commands — TRIM, EXTEND, FILLET, OFFSET — work in plan view,
|
/// The editing commands — TRIM, EXTEND, FILLET, OFFSET — work in plan view,
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,26 @@
|
||||||
use acadrust::entities::Spline;
|
use acadrust::entities::Spline;
|
||||||
use crate::t;
|
|
||||||
use cadkernel::geom2d::Curve as KernelCurve;
|
use cadkernel::geom2d::Curve as KernelCurve;
|
||||||
use cadkernel::space::NurbsCurve3;
|
use cadkernel::space::{NurbsCurve3, Parameterization};
|
||||||
|
|
||||||
use crate::command::EntityTransform;
|
use crate::command::EntityTransform;
|
||||||
use crate::entities::common::{
|
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::entities::traits::RenderConvertible;
|
||||||
use crate::scene::convert::acad_to_render::{RenderEntity, RenderObject};
|
use crate::scene::convert::acad_to_render::{RenderEntity, RenderObject};
|
||||||
use crate::scene::model::object::{GripApply, GripDef, PropSection, PropValue, Property};
|
use crate::scene::model::object::{GripApply, GripDef, PropSection, PropValue, Property};
|
||||||
|
use crate::t;
|
||||||
|
|
||||||
pub(crate) fn uses_fit_method(spline: &Spline) -> bool {
|
pub(crate) fn uses_fit_method(spline: &Spline) -> bool {
|
||||||
let degree = spline.degree.max(1) as usize;
|
let degree = spline.degree.max(1) as usize;
|
||||||
!spline.fit_points.is_empty() && spline.control_points.len() <= degree
|
!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 {
|
fn to_render(spl: &Spline) -> RenderEntity {
|
||||||
let n = spl.control_points.len();
|
let n = spl.control_points.len();
|
||||||
if n < 2 {
|
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,
|
// A fit spline through points in space is not a planar curve,
|
||||||
// so the kernel has nothing to say about it and the solve
|
// so the kernel has nothing to say about it and the solve
|
||||||
// here remains the only description of its shape.
|
// 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)
|
catmull_rom_polyline(&spl.fit_points, true)
|
||||||
}
|
}
|
||||||
None => fit_spline_polyline(spl),
|
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 {
|
if spl.fit_points.len() < 2 {
|
||||||
return spl.fit_points.iter().map(|p| [p.x, p.y, p.z]).collect();
|
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)
|
catmull_rom_polyline(&spl.fit_points, true)
|
||||||
} else {
|
} else {
|
||||||
fit_spline_polyline(spl)
|
fit_spline_polyline(spl)
|
||||||
|
|
@ -322,6 +330,22 @@ fn fit_spline_polyline(spl: &Spline) -> Vec<[f64; 3]> {
|
||||||
out
|
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.
|
/// Slopes used by spatial fit interpolation.
|
||||||
fn fit_spline_slopes(spl: &Spline, p: &[[f64; 3]]) -> Option<(Vec<f64>, [Vec<f64>; 3])> {
|
fn fit_spline_slopes(spl: &Spline, p: &[[f64; 3]]) -> Option<(Vec<f64>, [Vec<f64>; 3])> {
|
||||||
let n = p.len();
|
let n = p.len();
|
||||||
|
|
@ -439,6 +463,10 @@ fn control_vertices(spline: &Spline) -> Vec<acadrust::types::Vector3> {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn control_vertex_count(spline: &Spline) -> usize {
|
||||||
|
control_vertices(spline).len()
|
||||||
|
}
|
||||||
|
|
||||||
fn choice_prop(
|
fn choice_prop(
|
||||||
label: &str,
|
label: &str,
|
||||||
field: &'static str,
|
field: &'static str,
|
||||||
|
|
@ -504,10 +532,6 @@ fn convert_to_fit_method(spline: &mut Spline) -> bool {
|
||||||
uses_fit_method(spline)
|
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 {
|
fn tangent_is_set(tangent: &acadrust::types::Vector3) -> bool {
|
||||||
tangent.x * tangent.x + tangent.y * tangent.y + tangent.z * tangent.z > 1e-18
|
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 fit_method = uses_fit_method(spline);
|
||||||
let derived_control = control_vertices(spline);
|
let derived_control = control_vertices(spline);
|
||||||
let show_control_vertices = (spline.cv_frame_visible || !fit_method)
|
let show_control_vertices = (spline.cv_frame_visible || !fit_method)
|
||||||
|
&& !(fit_method && spline.flags.periodic)
|
||||||
&& derived_control.len() >= 2;
|
&& derived_control.len() >= 2;
|
||||||
let source = if show_control_vertices {
|
let source = if show_control_vertices {
|
||||||
derived_control
|
derived_control
|
||||||
|
|
@ -588,9 +613,11 @@ fn grips(spline: &Spline) -> Vec<GripDef> {
|
||||||
|
|
||||||
fn properties(spline: &Spline) -> Vec<PropSection> {
|
fn properties(spline: &Spline) -> Vec<PropSection> {
|
||||||
let fit_method = uses_fit_method(spline);
|
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 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 yes_no = |b: bool| if b { "Yes" } else { "No" };
|
||||||
let knot_param = match spline.knot_parameterization {
|
let knot_param = match spline.knot_parameterization {
|
||||||
0 => "Chord",
|
0 => "Chord",
|
||||||
|
|
@ -601,7 +628,7 @@ fn properties(spline: &Spline) -> Vec<PropSection> {
|
||||||
};
|
};
|
||||||
let current = crate::scene::view::dispatch::prop_current_vertex();
|
let current = crate::scene::view::dispatch::prop_current_vertex();
|
||||||
let (effective_begin_tangent, effective_end_tangent) = effective_fit_tangents(spline);
|
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 count = spline.fit_points.len();
|
||||||
let index = current.min(count.saturating_sub(1));
|
let index = current.min(count.saturating_sub(1));
|
||||||
let point = spline.fit_points.get(index);
|
let point = spline.fit_points.get(index);
|
||||||
|
|
@ -648,9 +675,10 @@ fn properties(spline: &Spline) -> Vec<PropSection> {
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
} else {
|
} 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 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);
|
let weight = spline.weights.get(index).copied().unwrap_or(1.0);
|
||||||
vec![
|
vec![
|
||||||
ro(
|
ro(
|
||||||
|
|
@ -679,7 +707,7 @@ fn properties(spline: &Spline) -> Vec<PropSection> {
|
||||||
"ctrl_pt_z",
|
"ctrl_pt_z",
|
||||||
point.map(|value| value.z).unwrap_or(0.0),
|
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 {
|
data_points.push(if can_show_control {
|
||||||
|
|
@ -723,48 +751,51 @@ fn properties(spline: &Spline) -> Vec<PropSection> {
|
||||||
ro(
|
ro(
|
||||||
t!("Planar").as_ref(),
|
t!("Planar").as_ref(),
|
||||||
"planar",
|
"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([
|
misc.extend([
|
||||||
edit(
|
edit_scalar(
|
||||||
t!("Start tangent vector X").as_ref(),
|
t!("Start tangent vector X").as_ref(),
|
||||||
"start_tan_x",
|
"start_tan_x",
|
||||||
effective_begin_tangent.x,
|
effective_begin_tangent.x,
|
||||||
),
|
),
|
||||||
edit(
|
edit_scalar(
|
||||||
t!("Start tangent vector Y").as_ref(),
|
t!("Start tangent vector Y").as_ref(),
|
||||||
"start_tan_y",
|
"start_tan_y",
|
||||||
effective_begin_tangent.y,
|
effective_begin_tangent.y,
|
||||||
),
|
),
|
||||||
edit(
|
edit_scalar(
|
||||||
t!("Start tangent vector Z").as_ref(),
|
t!("Start tangent vector Z").as_ref(),
|
||||||
"start_tan_z",
|
"start_tan_z",
|
||||||
effective_begin_tangent.z,
|
effective_begin_tangent.z,
|
||||||
),
|
),
|
||||||
edit(
|
edit_scalar(
|
||||||
t!("End tangent vector X").as_ref(),
|
t!("End tangent vector X").as_ref(),
|
||||||
"end_tan_x",
|
"end_tan_x",
|
||||||
effective_end_tangent.x,
|
effective_end_tangent.x,
|
||||||
),
|
),
|
||||||
edit(
|
edit_scalar(
|
||||||
t!("End tangent vector Y").as_ref(),
|
t!("End tangent vector Y").as_ref(),
|
||||||
"end_tan_y",
|
"end_tan_y",
|
||||||
effective_end_tangent.y,
|
effective_end_tangent.y,
|
||||||
),
|
),
|
||||||
edit(
|
edit_scalar(
|
||||||
t!("End tangent vector Z").as_ref(),
|
t!("End tangent vector Z").as_ref(),
|
||||||
"end_tan_z",
|
"end_tan_z",
|
||||||
effective_end_tangent.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![
|
vec![
|
||||||
PropSection {
|
PropSection {
|
||||||
|
|
@ -781,8 +812,14 @@ fn properties(spline: &Spline) -> Vec<PropSection> {
|
||||||
fn apply_geom_prop(spline: &mut Spline, field: &str, value: &str) {
|
fn apply_geom_prop(spline: &mut Spline, field: &str, value: &str) {
|
||||||
match field {
|
match field {
|
||||||
"spline_method" => {
|
"spline_method" => {
|
||||||
if value == "Control Vertices" {
|
if value == "Fit" {
|
||||||
convert_to_control_method(spline);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -796,11 +833,40 @@ fn apply_geom_prop(spline: &mut Spline, field: &str, value: &str) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
"cv_frame" => {
|
"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;
|
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 (effective_begin_tangent, effective_end_tangent) = effective_fit_tangents(spline);
|
||||||
let Some(v) = parse_f64(value) else { return };
|
let Some(v) = parse_f64(value) else { return };
|
||||||
let control_index = crate::scene::view::dispatch::prop_current_vertex()
|
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;
|
fp.z = v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"fit_tolerance" if v >= 0.0 => spline.fit_tolerance = v,
|
|
||||||
"start_tan_x" | "start_tan_y" | "start_tan_z" => {
|
"start_tan_x" | "start_tan_y" | "start_tan_z" => {
|
||||||
if !tangent_is_set(&spline.begin_tangent) {
|
if !tangent_is_set(&spline.begin_tangent) {
|
||||||
spline.begin_tangent = effective_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) {
|
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.
|
// First CV edit makes the displayed kernel controls authoritative.
|
||||||
let fit_method = uses_fit_method(spline);
|
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) {
|
if !convert_to_control_method(spline) {
|
||||||
return;
|
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)
|
spline.control_points.get_mut(grip_id)
|
||||||
} else {
|
} else {
|
||||||
spline.fit_points.get_mut(grip_id)
|
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) {
|
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| {
|
crate::scene::view::transform::apply_standard_entity_transform(spline, t, |entity, p1, p2| {
|
||||||
for cp in &mut entity.control_points {
|
for cp in &mut entity.control_points {
|
||||||
crate::scene::view::transform::reflect_xy_point(&mut cp.x, &mut cp.y, p1, p2);
|
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);
|
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 {
|
impl RenderConvertible for Spline {
|
||||||
|
|
@ -938,8 +1019,9 @@ impl crate::entities::traits::Grippable for Spline {
|
||||||
use crate::scene::model::object::{GripMenuAction, GripMenuItem};
|
use crate::scene::model::object::{GripMenuAction, GripMenuItem};
|
||||||
if grip_id == SPLINE_MODE_GRIP_ID {
|
if grip_id == SPLINE_MODE_GRIP_ID {
|
||||||
let fit_method = uses_fit_method(self);
|
let fit_method = uses_fit_method(self);
|
||||||
let can_show_control = control_vertices(self).len() >= 2;
|
let can_show_control = control_vertices(self).len() >= 2
|
||||||
return if self.cv_frame_visible || !fit_method {
|
&& !(fit_method && self.flags.periodic);
|
||||||
|
return if !shows_fit_points(self) {
|
||||||
vec![
|
vec![
|
||||||
(can_show_control && fit_method).then_some(GripMenuItem {
|
(can_show_control && fit_method).then_some(GripMenuItem {
|
||||||
label: "Fit",
|
label: "Fit",
|
||||||
|
|
@ -987,7 +1069,10 @@ impl crate::entities::traits::Grippable for Spline {
|
||||||
return;
|
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;
|
self.cv_frame_visible = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,10 @@
|
||||||
// fine until it is measured.
|
// fine until it is measured.
|
||||||
|
|
||||||
use acadrust::entities::Spline;
|
use acadrust::entities::Spline;
|
||||||
use cadkernel::geom2d::{NurbsCurve, Parameterization};
|
|
||||||
use cadkernel::space::Plane;
|
|
||||||
use acadrust::types::Vector3;
|
use acadrust::types::Vector3;
|
||||||
use acadrust::Handle;
|
use acadrust::Handle;
|
||||||
|
use cadkernel::geom2d::{NurbsCurve, Parameterization};
|
||||||
|
use cadkernel::space::Plane;
|
||||||
|
|
||||||
/// The drawing plane a spline sits on.
|
/// 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.
|
// No usable control polygon, so this is a fit-point spline.
|
||||||
let mut fit: Vec<[f64; 2]> = spl.fit_points.iter().map(&point).collect();
|
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
|
// 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
|
// 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.
|
// 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 start_tangent = tangent(&spl.begin_tangent);
|
||||||
let end_tangent = tangent(&spl.end_tangent);
|
let end_tangent = tangent(&spl.end_tangent);
|
||||||
let parameterization = match spl.knot_parameterization {
|
|
||||||
2 => Parameterization::Uniform,
|
|
||||||
1 => Parameterization::Centripetal,
|
|
||||||
_ => Parameterization::Chord,
|
|
||||||
};
|
|
||||||
NurbsCurve::interpolate(
|
NurbsCurve::interpolate(
|
||||||
&fit,
|
&fit,
|
||||||
start_tangent,
|
start_tangent,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue