forked from 1b-agent/opencadstudio-upstream
Harden spline editing and source joins
This commit is contained in:
1 parent
e11796af29
commit
da34032ffc
5 files changed
+316
-22
No files matched your search
Generated
+2
-2
@@ -894,7 +894,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||
[[package]]
|
||||
name = "cadkernel"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=2c189defcfd7dad9499746470abb7380fdef0e20#2c189defcfd7dad9499746470abb7380fdef0e20"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=979e668a2a81ef28147b694510b05f6e87bb96c5#979e668a2a81ef28147b694510b05f6e87bb96c5"
|
||||
dependencies = [
|
||||
"acadrust",
|
||||
"cavalier_contours",
|
||||
@@ -905,7 +905,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "cadkernel-constraints"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=2c189defcfd7dad9499746470abb7380fdef0e20#2c189defcfd7dad9499746470abb7380fdef0e20"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=979e668a2a81ef28147b694510b05f6e87bb96c5#979e668a2a81ef28147b694510b05f6e87bb96c5"
|
||||
dependencies = [
|
||||
"nalgebra",
|
||||
]
|
||||
|
||||
+2
-2
@@ -31,8 +31,8 @@ rfd = "0.17"
|
||||
clap = { version = "4", features = ["derive", "string"] }
|
||||
env_logger = "0.11"
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "b49cbbfc49ba267712045831a601d2f3d0ed386a", features = ["serde"] }
|
||||
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "2c189defcfd7dad9499746470abb7380fdef0e20", features = ["acis", "offset"] }
|
||||
cadkernel-constraints = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "2c189defcfd7dad9499746470abb7380fdef0e20" }
|
||||
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "979e668a2a81ef28147b694510b05f6e87bb96c5", features = ["acis", "offset"] }
|
||||
cadkernel-constraints = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "979e668a2a81ef28147b694510b05f6e87bb96c5" }
|
||||
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
|
||||
flate2 = "1"
|
||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }
|
||||
|
||||
@@ -3111,12 +3111,11 @@ mod tests {
|
||||
for command in ["SETBYLAYER", "OVERKILL"] {
|
||||
let mut app = fresh_app();
|
||||
let _ = app.run_command_line(command);
|
||||
assert_eq!(
|
||||
assert!(
|
||||
app.tabs[app.active_tab]
|
||||
.active_cmd
|
||||
.as_ref()
|
||||
.map(|active| active.name()),
|
||||
Some("SELECT"),
|
||||
.is_some_and(|active| active.is_selection_gathering()),
|
||||
"{command} should gather an explicit selection"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ impl JoinCommand {
|
||||
pub fn with_source(mut self, handle: Handle, entity: EntityType) -> Self {
|
||||
self.picked = Some(entity); self.on_entity_pick(handle, DVec3::ZERO); self
|
||||
}
|
||||
fn has_arc_source(&self) -> bool { matches!(&self.source, Some((_, EntityType::Arc(_)))) }
|
||||
}
|
||||
impl CadCommand for JoinCommand {
|
||||
fn name(&self) -> &'static str { "JOIN" }
|
||||
@@ -42,6 +43,8 @@ impl CadCommand for JoinCommand {
|
||||
fn options(&self) -> Vec<crate::command::CmdOption> {
|
||||
if matches!(&self.source,Some((_,EntityType::Arc(_)))) { vec![crate::command::CmdOption::new("Close","L")] } else {Vec::new()}
|
||||
}
|
||||
fn wants_text_input(&self) -> bool { self.has_arc_source() }
|
||||
fn point_step_accepts_keywords(&self) -> bool { self.has_arc_source() }
|
||||
fn on_text_input(&mut self,text:&str) -> Option<CmdResult> {
|
||||
if matches!(text.trim().to_ascii_uppercase().as_str(),"L"|"CLOSE") {
|
||||
if let Some((handle,EntityType::Arc(arc)))=&self.source {
|
||||
@@ -122,7 +125,7 @@ pub fn join_to_source(source: &EntityType, candidates: &[(Handle, &EntityType)])
|
||||
spline.control_points=joined.control_points().iter().map(|p|Vector3::new(p[0],p[1],p[2])).collect();
|
||||
spline.knots=joined.knots().to_vec();spline.weights=joined.weights().to_vec();
|
||||
spline.fit_points.clear();spline.flags.rational=joined.is_rational();
|
||||
spline.dwg_flags1 &= !1;spline.dxf_flags &= !32;
|
||||
spline.dwg_flags1 &= !1;spline.dxf_flags &= !(32 | 1024);
|
||||
spline.flags.planar=cadkernel::space::are_coplanar(joined.control_points(),&[]);
|
||||
spline.begin_tangent=Vector3::new(0.0,0.0,0.0);spline.end_tangent=Vector3::new(0.0,0.0,0.0);
|
||||
Some(EntityType::Spline(spline))
|
||||
@@ -511,6 +514,94 @@ mod join_tests {
|
||||
l.thickness
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn source_join_consumes_only_compatible_lines_and_keeps_direction() {
|
||||
let source_handle = Handle::new(11);
|
||||
let rejected_handle = Handle::new(12);
|
||||
let joined_handle = Handle::new(13);
|
||||
let mut source = line(5.0, 0.0, 1.0, 0.0, 2.0);
|
||||
source.common_mut().handle = source_handle;
|
||||
let rejected = line(1.0, 0.0, 1.0, 2.0, 0.0);
|
||||
let joined = line(8.0, 0.0, 6.0, 0.0, 0.0);
|
||||
let (result, consumed) = join_to_source(
|
||||
&source,
|
||||
&[(rejected_handle, &rejected), (joined_handle, &joined)],
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(consumed, vec![joined_handle]);
|
||||
let EntityType::Line(result) = result else {
|
||||
panic!("expected a line");
|
||||
};
|
||||
assert_eq!(result.common.handle, source_handle);
|
||||
assert_eq!(result.start, Vector3::new(8.0, 0.0, 0.0));
|
||||
assert_eq!(result.end, Vector3::new(1.0, 0.0, 0.0));
|
||||
assert_eq!(result.thickness, 2.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn source_join_elevates_splines_and_clears_fit_representation() {
|
||||
let source_handle = Handle::new(21);
|
||||
let candidate_handle = Handle::new(22);
|
||||
let mut source = acadrust::entities::Spline::new();
|
||||
source.common.handle = source_handle;
|
||||
source.degree = 1;
|
||||
source.control_points = vec![
|
||||
Vector3::new(0.0, 0.0, 0.0),
|
||||
Vector3::new(1.0, 0.0, 0.0),
|
||||
];
|
||||
source.knots = vec![0.0, 0.0, 1.0, 1.0];
|
||||
source.fit_points = vec![Vector3::new(0.0, 0.0, 0.0)];
|
||||
source.dwg_flags1 = 1;
|
||||
source.dxf_flags = 32 | 1024;
|
||||
let mut candidate = acadrust::entities::Spline::new();
|
||||
candidate.degree = 3;
|
||||
candidate.control_points = vec![
|
||||
Vector3::new(1.0, 0.0, 0.0),
|
||||
Vector3::new(1.3, 0.2, 0.0),
|
||||
Vector3::new(1.7, 0.8, 0.0),
|
||||
Vector3::new(2.0, 1.0, 0.0),
|
||||
];
|
||||
candidate.knots = vec![0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0];
|
||||
let candidate_entity = EntityType::Spline(candidate);
|
||||
let (result, consumed) = join_to_source(
|
||||
&EntityType::Spline(source),
|
||||
&[(candidate_handle, &candidate_entity)],
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(consumed, vec![candidate_handle]);
|
||||
let EntityType::Spline(result) = result else {
|
||||
panic!("expected a spline");
|
||||
};
|
||||
assert_eq!(result.common.handle, source_handle);
|
||||
assert_eq!(result.degree, 3);
|
||||
assert!(result.fit_points.is_empty());
|
||||
assert_eq!(result.dwg_flags1 & 1, 0);
|
||||
assert_eq!(result.dxf_flags & (32 | 1024), 0);
|
||||
assert!(result.flags.planar);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arc_source_accepts_the_typed_close_option() {
|
||||
let handle = Handle::new(31);
|
||||
let mut arc = acadrust::entities::Arc::new();
|
||||
arc.common.handle = handle;
|
||||
arc.center = Vector3::new(2.0, 3.0, 4.0);
|
||||
arc.radius = 5.0;
|
||||
arc.thickness = 0.75;
|
||||
let mut command = JoinCommand::new().with_source(handle, EntityType::Arc(arc));
|
||||
assert!(command.input_kind().wants_text());
|
||||
assert!(command.point_step_accepts_keywords());
|
||||
let Some(CmdResult::ReplaceMany(mut replacements, _)) = command.on_text_input("Close") else {
|
||||
panic!("expected the arc to close");
|
||||
};
|
||||
let Some(EntityType::Circle(circle)) = replacements[0].1.pop() else {
|
||||
panic!("expected a circle");
|
||||
};
|
||||
assert_eq!(circle.center, Vector3::new(2.0, 3.0, 4.0));
|
||||
assert_eq!(circle.radius, 5.0);
|
||||
assert_eq!(circle.thickness, 0.75);
|
||||
}
|
||||
}
|
||||
|
||||
/// True when every vertex lies on one straight line (within tolerance).
|
||||
|
||||
@@ -52,6 +52,22 @@ pub struct SplineditCommand {
|
||||
join_candidates: Vec<crate::command::SelectionEntity>,
|
||||
}
|
||||
|
||||
fn clear_fit_method(spline: &mut acadrust::entities::Spline) {
|
||||
spline.fit_points.clear();
|
||||
spline.begin_tangent = Vector3::ZERO;
|
||||
spline.end_tangent = Vector3::ZERO;
|
||||
spline.dwg_flags1 &= !1;
|
||||
spline.dxf_flags &= !(32 | 1024);
|
||||
}
|
||||
|
||||
fn weights_are_rational(weights: &[f64]) -> bool {
|
||||
weights.first().is_some_and(|first| {
|
||||
weights
|
||||
.iter()
|
||||
.any(|weight| (*weight - *first).abs() > 1e-12)
|
||||
})
|
||||
}
|
||||
|
||||
impl SplineditCommand {
|
||||
pub fn new() -> Self {
|
||||
Self { step: Step::SelectSpline, handle: acadrust::Handle::NULL, spline: None, pending: None, history: Vec::new(), pick_context: None, join_candidates: Vec::new() }
|
||||
@@ -126,19 +142,16 @@ impl SplineditCommand {
|
||||
source.control_points.iter().map(|point| [point.x, point.y, point.z]).collect(),
|
||||
source.knots.clone(), weights)?
|
||||
};
|
||||
let curve = curve.with_periodicity(source.flags.closed || source.flags.periodic)
|
||||
.elevated(degree - current)?
|
||||
let curve = curve.with_periodicity(source.flags.closed || source.flags.periodic);
|
||||
let elevation = degree.checked_sub(curve.degree())?;
|
||||
let curve = if elevation == 0 { curve } else { curve.elevated(elevation)? }
|
||||
.compact_knots(source.control_tolerance.max(1e-9))?;
|
||||
let mut result = source.clone();
|
||||
result.degree = curve.degree() as i32;
|
||||
result.control_points = curve.control_points().iter().map(|point| Vector3::new(point[0], point[1], point[2])).collect();
|
||||
result.knots = curve.knots().to_vec();
|
||||
result.weights = curve.weights().to_vec();
|
||||
result.fit_points.clear();
|
||||
result.begin_tangent = Vector3::ZERO;
|
||||
result.end_tangent = Vector3::ZERO;
|
||||
result.dwg_flags1 &= !1;
|
||||
result.dxf_flags &= !(32 | 1024);
|
||||
clear_fit_method(&mut result);
|
||||
result.flags.rational = curve.is_rational();
|
||||
result.flags.planar = crate::entities::curve::spline_is_planar(&result);
|
||||
return Some(result);
|
||||
@@ -158,6 +171,9 @@ impl SplineditCommand {
|
||||
let world = planar.plane.point_at(*point);
|
||||
Vector3::new(world[0], world[1], world[2])
|
||||
}).collect();
|
||||
clear_fit_method(&mut result);
|
||||
result.flags.rational = curve.is_rational();
|
||||
result.flags.planar = crate::entities::curve::spline_is_planar(&result);
|
||||
Some(result)
|
||||
}
|
||||
}
|
||||
@@ -262,7 +278,10 @@ impl CadCommand for SplineditCommand {
|
||||
},
|
||||
Step::Elevate => {
|
||||
let current_order = self.spline.as_ref()?.degree.max(1) as usize + 1;
|
||||
let order = upper.parse::<usize>().ok().filter(|order| *order >= current_order && *order <= 26)?;
|
||||
let Some(order) = upper.parse::<usize>().ok()
|
||||
.filter(|order| *order >= current_order && *order <= 26) else {
|
||||
return Some(CmdResult::NeedPoint);
|
||||
};
|
||||
if order == current_order {
|
||||
self.step = Step::Refine;
|
||||
return Some(CmdResult::NeedPoint);
|
||||
@@ -290,8 +309,8 @@ impl CadCommand for SplineditCommand {
|
||||
let mut spline = self.spline.clone()?;
|
||||
spline.weights.resize(count, 1.0);
|
||||
spline.weights[index] = weight;
|
||||
spline.flags.rational = true;
|
||||
spline.fit_points.clear();
|
||||
spline.flags.rational = weights_are_rational(&spline.weights);
|
||||
clear_fit_method(&mut spline);
|
||||
return Some(self.replace(spline));
|
||||
}
|
||||
}
|
||||
@@ -329,7 +348,8 @@ impl CadCommand for SplineditCommand {
|
||||
spline.control_points = curve.control_points().iter().map(|point| Vector3::new(point[0], point[1], point[2])).collect();
|
||||
spline.weights = curve.weights().to_vec();
|
||||
spline.knots = curve.knots().to_vec();
|
||||
spline.fit_points.clear();
|
||||
clear_fit_method(&mut spline);
|
||||
spline.flags.rational = curve.is_rational();
|
||||
spline.flags.planar = crate::entities::curve::spline_is_planar(&spline);
|
||||
self.replace(spline)
|
||||
}
|
||||
@@ -338,7 +358,8 @@ impl CadCommand for SplineditCommand {
|
||||
let Some(mut spline) = self.spline.clone() else { return CmdResult::NeedPoint; };
|
||||
let Some(vertex) = spline.control_points.get_mut(index) else { return CmdResult::NeedPoint; };
|
||||
*vertex = Vector3::new(point.x, point.y, point.z);
|
||||
spline.fit_points.clear();
|
||||
clear_fit_method(&mut spline);
|
||||
spline.flags.planar = crate::entities::curve::spline_is_planar(&spline);
|
||||
self.replace(spline)
|
||||
}
|
||||
_ => CmdResult::NeedPoint,
|
||||
@@ -418,7 +439,7 @@ fn change_closure(source: &acadrust::entities::Spline, closed: bool) -> Option<a
|
||||
} else if closed {
|
||||
NurbsCurve3::from_weighted_control_polygon(degree, &controls, &weights, true)?
|
||||
} else {
|
||||
let curve = stored_curve()?;
|
||||
let curve = stored_curve()?.with_periodicity(false);
|
||||
curve.without_control_vertex(controls.len().checked_sub(1)?)?
|
||||
};
|
||||
let mut result = source.clone();
|
||||
@@ -429,7 +450,7 @@ fn change_closure(source: &acadrust::entities::Spline, closed: bool) -> Option<a
|
||||
result.flags.closed = closed;
|
||||
result.flags.periodic = closed;
|
||||
if closed { result.dxf_flags |= 2048; } else { result.dxf_flags &= !2048; }
|
||||
result.flags.rational = if fit_method { false } else { source.flags.rational || curve.is_rational() };
|
||||
result.flags.rational = !fit_method && curve.is_rational();
|
||||
result.fit_points = if !closed && fit_method {
|
||||
fit_points.iter().map(|point| Vector3::new(point[0], point[1], point[2])).collect()
|
||||
} else { Vec::new() };
|
||||
@@ -443,5 +464,188 @@ fn change_closure(source: &acadrust::entities::Spline, closed: bool) -> Option<a
|
||||
Some(result)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use acadrust::entities::Spline;
|
||||
use acadrust::Handle;
|
||||
use iced::Rectangle;
|
||||
|
||||
fn control_spline(points: &[[f64; 3]], degree: usize) -> Spline {
|
||||
let mut spline = Spline::new();
|
||||
spline.degree = degree as i32;
|
||||
spline.control_points = points
|
||||
.iter()
|
||||
.map(|point| Vector3::new(point[0], point[1], point[2]))
|
||||
.collect();
|
||||
spline.knots = cadkernel::space::clamped_uniform_knots(degree, points.len());
|
||||
spline
|
||||
}
|
||||
|
||||
fn selected_command(spline: Spline) -> SplineditCommand {
|
||||
let mut command = SplineditCommand::new();
|
||||
command.inject_picked_entity(EntityType::Spline(spline));
|
||||
command.on_entity_pick(Handle::new(7), DVec3::ZERO);
|
||||
command
|
||||
}
|
||||
|
||||
fn replacement(result: CmdResult) -> Spline {
|
||||
let CmdResult::ReplaceManyContinue(mut replacements) = result else {
|
||||
panic!("expected a continuing replacement");
|
||||
};
|
||||
let Some(EntityType::Spline(spline)) = replacements[0].1.pop() else {
|
||||
panic!("expected a spline replacement");
|
||||
};
|
||||
spline
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moving_a_control_vertex_clears_fit_state_and_recomputes_planarity() {
|
||||
let mut spline = control_spline(
|
||||
&[
|
||||
[0.0, 0.0, 0.0],
|
||||
[1.0, 0.0, 0.0],
|
||||
[2.0, 1.0, 0.0],
|
||||
[3.0, 0.0, 0.0],
|
||||
],
|
||||
3,
|
||||
);
|
||||
spline.fit_points = vec![Vector3::new(0.0, 0.0, 0.0)];
|
||||
spline.begin_tangent = Vector3::new(1.0, 0.0, 0.0);
|
||||
spline.end_tangent = Vector3::new(1.0, 0.0, 0.0);
|
||||
spline.dwg_flags1 = 1;
|
||||
spline.dxf_flags = 32 | 1024;
|
||||
spline.flags.planar = true;
|
||||
let mut command = selected_command(spline);
|
||||
command.on_text_input("MOVE");
|
||||
let moved = replacement(command.on_point(DVec3::new(0.0, 0.0, 1.0)));
|
||||
assert!(moved.fit_points.is_empty());
|
||||
assert_eq!(moved.begin_tangent, Vector3::ZERO);
|
||||
assert_eq!(moved.end_tangent, Vector3::ZERO);
|
||||
assert_eq!(moved.dwg_flags1 & 1, 0);
|
||||
assert_eq!(moved.dxf_flags & (32 | 1024), 0);
|
||||
assert!(!moved.flags.planar);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn equal_control_weights_are_not_marked_rational() {
|
||||
let spline = control_spline(
|
||||
&[
|
||||
[0.0, 0.0, 0.0],
|
||||
[1.0, 1.0, 0.0],
|
||||
[2.0, 1.0, 0.0],
|
||||
[3.0, 0.0, 0.0],
|
||||
],
|
||||
3,
|
||||
);
|
||||
let mut command = selected_command(spline);
|
||||
command.on_text_input("REFINE");
|
||||
command.on_text_input("WEIGHT");
|
||||
let weighted = replacement(command.on_text_input("1").unwrap());
|
||||
assert!(!weighted.flags.rational);
|
||||
assert_eq!(weighted.weights, vec![1.0; 4]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deleting_the_only_weighted_vertex_clears_rational_and_fit_flags() {
|
||||
let mut spline = control_spline(
|
||||
&[
|
||||
[-0.6, 0.0, 0.0],
|
||||
[-0.2, 0.4, 0.0],
|
||||
[0.2, 0.4, 0.0],
|
||||
[0.6, 0.0, 0.0],
|
||||
],
|
||||
2,
|
||||
);
|
||||
spline.weights = vec![1.0, 2.0, 1.0, 1.0];
|
||||
spline.flags.rational = true;
|
||||
spline.dwg_flags1 = 1;
|
||||
spline.dxf_flags = 32 | 1024;
|
||||
let mut command = selected_command(spline);
|
||||
command.on_text_input("REFINE");
|
||||
command.on_text_input("DELETE");
|
||||
command.pick_context = Some(crate::command::PointPickContext {
|
||||
view: glam::Mat4::IDENTITY,
|
||||
eye: DVec3::ZERO,
|
||||
bounds: Rectangle::new(iced::Point::ORIGIN, iced::Size::new(100.0, 100.0)),
|
||||
aperture_px: 2.0,
|
||||
});
|
||||
let deleted = replacement(command.on_point(DVec3::new(-0.2, 0.4, 0.0)));
|
||||
assert_eq!(deleted.control_points.len(), 3);
|
||||
assert!(!deleted.flags.rational);
|
||||
assert_eq!(deleted.dwg_flags1 & 1, 0);
|
||||
assert_eq!(deleted.dxf_flags & (32 | 1024), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fit_only_degree_elevation_uses_the_interpolated_curve_degree() {
|
||||
let mut spline = Spline::new();
|
||||
spline.degree = 1;
|
||||
spline.fit_points = vec![
|
||||
Vector3::new(0.0, 0.0, 0.0),
|
||||
Vector3::new(1.0, 2.0, 0.0),
|
||||
Vector3::new(3.0, 0.0, 0.0),
|
||||
];
|
||||
let mut command = selected_command(spline);
|
||||
command.on_text_input("REFINE");
|
||||
command.on_text_input("ELEVATE");
|
||||
assert!(matches!(command.on_text_input("invalid"), Some(CmdResult::NeedPoint)));
|
||||
assert!(matches!(command.step, Step::Elevate));
|
||||
let elevated = replacement(command.on_text_input("5").unwrap());
|
||||
assert_eq!(elevated.degree, 4);
|
||||
assert!(elevated.fit_points.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_closed_weighted_control_curve_can_be_opened() {
|
||||
let curve = cadkernel::space::NurbsCurve3::from_weighted_control_polygon(
|
||||
2,
|
||||
&[
|
||||
[0.0, 0.0, 0.0],
|
||||
[2.0, 0.0, 0.0],
|
||||
[2.0, 2.0, 1.0],
|
||||
[0.0, 2.0, 0.0],
|
||||
],
|
||||
&[1.0, 2.0, 0.75, 1.5],
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
let mut spline = control_spline(curve.control_points(), curve.degree());
|
||||
spline.knots = curve.knots().to_vec();
|
||||
spline.weights = curve.weights().to_vec();
|
||||
spline.flags.closed = true;
|
||||
spline.flags.periodic = true;
|
||||
spline.flags.rational = true;
|
||||
let opened = change_closure(&spline, false).expect("closed control curve opens");
|
||||
assert!(!opened.flags.closed);
|
||||
assert!(!opened.flags.periodic);
|
||||
assert!(cadkernel::space::NurbsCurve3::new_strict(
|
||||
opened.degree as usize,
|
||||
opened
|
||||
.control_points
|
||||
.iter()
|
||||
.map(|point| [point.x, point.y, point.z])
|
||||
.collect(),
|
||||
opened.knots.clone(),
|
||||
opened.weights.clone(),
|
||||
)
|
||||
.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn control_vertex_pick_uses_the_screen_aperture() {
|
||||
let spline = control_spline(&[[-0.5, 0.0, 0.0], [0.5, 0.0, 0.0]], 1);
|
||||
let mut command = selected_command(spline);
|
||||
command.pick_context = Some(crate::command::PointPickContext {
|
||||
view: glam::Mat4::IDENTITY,
|
||||
eye: DVec3::ZERO,
|
||||
bounds: Rectangle::new(iced::Point::ORIGIN, iced::Size::new(100.0, 100.0)),
|
||||
aperture_px: 2.0,
|
||||
});
|
||||
assert_eq!(command.picked_vertex(DVec3::new(-0.49, 0.0, 0.0)), Some(0));
|
||||
assert_eq!(command.picked_vertex(DVec3::new(0.0, 0.5, 0.0)), None);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Autocomplete registry ─────────────────────────────────
|
||||
inventory::submit!(crate::command::CommandRegistration { names: &["SPLINEDIT"] }); // SplineditCommand
|
||||
Reference in new issue
Block a user