Complete spline fit and control grip modes

This commit is contained in:
ramox81 committed 2026-09-17 14:29:26 +03:00
1 parent 6260723937
commit 5497f455ef
4 files changed
+152 -27

No files matched your search

+2 -4
View File
@@ -1723,15 +1723,13 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
.get_entity(popup.handle)
.is_some_and(|entity| match (item.action, entity) {
(GripMenuAction::ShowFit, acadrust::EntityType::Spline(spline)) => {
!spline.cv_frame_visible
&& crate::entities::spline::uses_fit_method(spline)
crate::entities::spline::shows_fit_points(spline)
}
(
GripMenuAction::ShowControlVertices,
acadrust::EntityType::Spline(spline),
) => {
spline.cv_frame_visible
|| !crate::entities::spline::uses_fit_method(spline)
crate::entities::spline::shows_control_vertices(spline)
}
_ => false,
});
+2 -1
View File
@@ -591,7 +591,8 @@ bg={bg_ms:.1}ms n={view_count}"
};
let control_polygon = tab.selected_handle.and_then(|handle| {
let spline = match tab.scene.document.get_entity(handle) {
Some(acadrust::EntityType::Spline(spline)) if spline.cv_frame_visible => spline,
Some(acadrust::EntityType::Spline(spline))
if crate::entities::spline::shows_control_vertices(spline) => spline,
_ => return None,
};
if tab
+134 -19
View File
@@ -18,7 +18,13 @@ pub(crate) fn uses_fit_method(spline: &Spline) -> bool {
}
pub(crate) fn shows_fit_points(spline: &Spline) -> bool {
uses_fit_method(spline) && (!spline.cv_frame_visible || spline.flags.periodic)
!spline.fit_points.is_empty() && (!spline.cv_frame_visible || spline.flags.periodic)
}
pub(crate) fn shows_control_vertices(spline: &Spline) -> bool {
!shows_fit_points(spline)
&& !(uses_fit_method(spline) && spline.flags.periodic)
&& control_vertices(spline).len() >= 2
}
pub(crate) fn fit_nurbs3(spline: &Spline) -> Option<NurbsCurve3> {
@@ -105,14 +111,24 @@ pub(crate) fn spline_knot_points(spline: &Spline) -> Vec<glam::DVec3> {
}
pub(crate) fn replace_with_nurbs(spline: &mut Spline, curve: &NurbsCurve3) {
let mut fit_points = spline.fit_points.clone();
spline.degree = curve.degree() as i32;
spline.knots = curve.knots().to_vec();
spline.control_points = curve.control_points().iter()
.map(|point| acadrust::types::Vector3::new(point[0], point[1], point[2])).collect();
spline.weights = curve.weights().to_vec();
spline.fit_points.clear();
if !fit_points.is_empty() {
if let (Some(first), Some(point)) = (fit_points.first_mut(), spline.control_points.first()) {
*first = *point;
}
if let (Some(last), Some(point)) = (fit_points.last_mut(), spline.control_points.last()) {
*last = *point;
}
}
spline.fit_points = fit_points;
spline.begin_tangent = acadrust::types::Vector3::ZERO;
spline.end_tangent = acadrust::types::Vector3::ZERO;
spline.cv_frame_visible = true;
spline.flags.rational = spline.weights.windows(2)
.any(|pair| (pair[0] - pair[1]).abs() > 1e-12);
spline.flags.closed = false;
@@ -615,7 +631,15 @@ fn index_prop(label: &str, field: &'static str, index: usize, count: usize) -> P
fn convert_to_control_method(spline: &mut Spline) -> bool {
if !uses_fit_method(spline) {
return true;
let valid = spline.control_points.len() >= 2;
if valid {
spline.fit_points.clear();
spline.begin_tangent = acadrust::types::Vector3::ZERO;
spline.end_tangent = acadrust::types::Vector3::ZERO;
spline.dwg_flags1 &= !1;
spline.dxf_flags &= !(32 | 1024);
}
return valid;
}
let Some(planar) = crate::entities::curve::spline_curve(spline) else {
return false;
@@ -647,18 +671,74 @@ fn convert_to_control_method(spline: &mut Spline) -> bool {
}
fn convert_to_fit_method(spline: &mut Spline) -> bool {
uses_fit_method(spline)
if uses_fit_method(spline) {
return true;
}
if spline.fit_points.len() < 2 {
return false;
}
spline.control_points.clear();
spline.knots.clear();
spline.weights.clear();
spline.flags.rational = false;
spline.dwg_flags1 |= 1;
spline.dxf_flags |= 32 | 1024;
true
}
fn derived_fit_points(spline: &Spline) -> Vec<acadrust::types::Vector3> {
if spline.flags.closed || spline.flags.periodic {
return Vec::new();
}
let Some(curve) = nurbs3(spline) else {
return Vec::new();
};
let (start, end) = curve.domain();
if !(start.is_finite() && end.is_finite() && end > start) {
return Vec::new();
}
let tolerance = (end - start).abs().max(1.0) * 1.0e-12;
let mut parameters = vec![start];
for knot in curve.knots().iter().copied() {
if knot > start + tolerance
&& knot < end - tolerance
&& parameters
.last()
.is_none_or(|previous| (knot - *previous).abs() > tolerance)
{
parameters.push(knot);
}
}
parameters.push(end);
parameters
.into_iter()
.map(|parameter| {
let point = curve.point_at_knot(parameter);
acadrust::types::Vector3::new(point[0], point[1], point[2])
})
.collect()
}
fn can_show_fit_points(spline: &Spline) -> bool {
spline.fit_points.len() >= 2 || derived_fit_points(spline).len() >= 2
}
fn prepare_fit_point_view(spline: &mut Spline) -> bool {
if spline.fit_points.len() < 2 {
spline.fit_points = derived_fit_points(spline);
}
spline.fit_points.len() >= 2
}
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)
let show_fit = shows_fit_points(spline);
let show_control_vertices = !show_fit
&& !(uses_fit_method(spline) && spline.flags.periodic)
&& derived_control.len() >= 2;
let source = if show_control_vertices {
derived_control
} else if fit_method {
} else if show_fit {
spline.fit_points.clone()
} else {
spline.control_points.clone()
@@ -885,7 +965,7 @@ fn apply_geom_prop(spline: &mut Spline, field: &str, value: &str) {
match field {
"spline_method" => {
if value == "Fit" {
if convert_to_fit_method(spline) {
if prepare_fit_point_view(spline) {
spline.cv_frame_visible = false;
}
} else if value == "Control Vertices" {
@@ -896,6 +976,12 @@ fn apply_geom_prop(spline: &mut Spline, field: &str, value: &str) {
return;
}
"knot_param" => {
if shows_fit_points(spline)
&& !uses_fit_method(spline)
&& !convert_to_fit_method(spline)
{
return;
}
spline.knot_parameterization = match value {
"Chord" => 0,
"Square Root" => 1,
@@ -930,9 +1016,30 @@ fn apply_geom_prop(spline: &mut Spline, field: &str, value: &str) {
{
return;
}
if matches!(field, "ctrl_pt_x" | "ctrl_pt_y" | "ctrl_pt_z" | "weight")
&& uses_fit_method(spline)
&& !convert_to_control_method(spline)
if matches!(field, "ctrl_pt_x" | "ctrl_pt_y" | "ctrl_pt_z" | "weight") {
if !shows_fit_points(spline) && !spline.fit_points.is_empty() {
if !convert_to_control_method(spline) {
return;
}
} else if uses_fit_method(spline) && !convert_to_control_method(spline) {
return;
}
}
if shows_fit_points(spline)
&& !uses_fit_method(spline)
&& matches!(
field,
"fit_pt_x"
| "fit_pt_y"
| "fit_pt_z"
| "start_tan_x"
| "start_tan_y"
| "start_tan_z"
| "end_tan_x"
| "end_tan_y"
| "end_tan_z"
)
&& !convert_to_fit_method(spline)
{
return;
}
@@ -1015,16 +1122,23 @@ fn apply_grip(spline: &mut Spline, grip_id: usize, apply: GripApply) {
return;
}
// First CV edit makes the displayed kernel controls authoritative.
let fit_method = uses_fit_method(spline);
if spline.cv_frame_visible && fit_method && !spline.flags.periodic {
let editing_fit = shows_fit_points(spline);
if editing_fit && !uses_fit_method(spline) {
if !convert_to_fit_method(spline) {
return;
}
} else if !editing_fit
&& (uses_fit_method(spline) || !spline.fit_points.is_empty())
&& !spline.flags.periodic
{
if !convert_to_control_method(spline) {
return;
}
}
let target = if (spline.cv_frame_visible && !spline.flags.periodic) || !fit_method {
spline.control_points.get_mut(grip_id)
} else {
let target = if editing_fit {
spline.fit_points.get_mut(grip_id)
} else {
spline.control_points.get_mut(grip_id)
};
if let Some(cp) = target {
match apply {
@@ -1088,9 +1202,10 @@ impl crate::entities::traits::Grippable for Spline {
let fit_method = uses_fit_method(self);
let can_show_control = control_vertices(self).len() >= 2
&& !(fit_method && self.flags.periodic);
let can_show_fit = can_show_fit_points(self);
return if !shows_fit_points(self) {
vec![
(can_show_control && fit_method).then_some(GripMenuItem {
can_show_fit.then_some(GripMenuItem {
label: "Fit",
action: GripMenuAction::ShowFit,
}),
@@ -1131,7 +1246,7 @@ impl crate::entities::traits::Grippable for Spline {
use crate::scene::model::object::GripMenuAction as A;
match action {
A::ShowFit => {
if convert_to_fit_method(self) {
if prepare_fit_point_view(self) {
self.cv_frame_visible = false;
return;
}
+14 -3
View File
@@ -2961,8 +2961,8 @@ fn solve_scope(
}
}
(EntityType::Spline(spline), EntityGeom::Spline { curve, control_z }) => {
let mut changed =
spline.fit_points.len() > 0 || spline.control_points.len() != curve.poles.len();
let mut changed = crate::entities::spline::uses_fit_method(spline)
|| spline.control_points.len() != curve.poles.len();
let control_points: Vec<_> = curve
.poles
.iter()
@@ -2983,7 +2983,18 @@ fn solve_scope(
updated.knots = curve.knots.clone();
updated.control_points = control_points;
updated.weights = curve.weights.iter().map(|id| store.get(*id)).collect();
updated.fit_points.clear();
if !updated.fit_points.is_empty() {
if let (Some(first), Some(point)) =
(updated.fit_points.first_mut(), updated.control_points.first())
{
*first = *point;
}
if let (Some(last), Some(point)) =
(updated.fit_points.last_mut(), updated.control_points.last())
{
*last = *point;
}
}
updated.begin_tangent = Vector3::ZERO;
updated.end_tangent = Vector3::ZERO;
updated.flags.rational = updated