From 1f0b8888331a61a93781980cdcc49a4ad4b007f5 Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Fri, 18 Sep 2026 22:40:39 +0300 Subject: [PATCH] Turn an object onto its Horizontal/Vertical axis before solving MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The solver cannot start from an axis lying exactly across the datum: the distance and direction equations are singular there, so a horizontal line, a text at 0 degrees or an ellipse whose major axis is horizontal stayed put when Vertical was applied. The command now turns the object about its anchor (line start, picked segment's first vertex, text insertion point, ellipse center) onto the axis first — a polyline moves only the segment's end vertex — and lets the constraint hold what already fits. Two points on one entity follow the same rule about the first point. Co-Authored-By: Claude Fable 5.1 --- src/app/command_driver.rs | 47 +++++++++++++++++++++ src/scene/parametric_constraints.rs | 63 +++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/src/app/command_driver.rs b/src/app/command_driver.rs index d8a2f787a..65385f215 100644 --- a/src/app/command_driver.rs +++ b/src/app/command_driver.rs @@ -3486,6 +3486,53 @@ impl OpenCADStudio { } } } + // The solver cannot start from an axis lying exactly across + // the datum (its equations are singular there), so turn the + // object onto the axis about its anchor first — the + // reference turns it about that anchor too — and let the + // constraint hold what already fits. + if let Some((handle, anchor, end, vertex)) = + crate::scene::parametric_constraints::axis_alignment_target( + &self.tabs[i].scene.document, + &refs, + ) + { + let axis = (end.x - anchor.x, end.y - anchor.y); + let length = axis.0.hypot(axis.1); + let target = if axis.0 * direction.x + axis.1 * direction.y >= 0.0 { + (direction.x, direction.y) + } else { + (-direction.x, -direction.y) + }; + let angle = (axis.0 * target.1 - axis.1 * target.0) + .atan2(axis.0 * target.0 + axis.1 * target.1); + if length > 1.0e-9 && angle.abs() > 1.0e-9 { + match vertex { + Some(index) => { + let moved = + self.tabs[i].scene.document.get_entity(handle).cloned(); + if let Some(mut entity) = moved { + if crate::scene::parametric_constraints::set_polyline_vertex( + &mut entity, + index, + anchor.x + length * target.0, + anchor.y + length * target.1, + ) { + self.tabs[i].scene.update_entity(entity); + } + } + } + None => self.tabs[i].scene.transform_entities( + &[handle], + &EntityTransform::Rotate { + center: glam::DVec3::new(anchor.x, anchor.y, anchor.z), + axis: glam::DVec3::Z, + angle_rad: angle, + }, + ), + } + } + } let id = self.tabs[i] .scene .parametric_constraint_set_mut(scope) diff --git a/src/scene/parametric_constraints.rs b/src/scene/parametric_constraints.rs index e599ceb4f..bd0ee0c99 100644 --- a/src/scene/parametric_constraints.rs +++ b/src/scene/parametric_constraints.rs @@ -202,6 +202,69 @@ pub(crate) fn directional_axis_endpoints( } } +/// The axis a lone Horizontal/Vertical reference — or two points on one +/// entity — turns onto its datum: the entity, the anchor that stays, the +/// axis end that moves, and for a polyline the vertex to move instead of +/// turning the whole entity. +pub(crate) fn axis_alignment_target( + document: &acadrust::CadDocument, + refs: &[ParametricRef], +) -> Option<(Handle, Vector3, Vector3, Option)> { + let (handle, start_marker, end_marker) = match refs { + [reference] => { + let entity = document.get_entity(reference.entity)?; + if reference.directional_axis().is_some() { + let [start, end] = directional_axis_endpoints(entity, *reference)?; + return Some((reference.entity, start, end, None)); + } + let index = reference.segment_index().map_or(0, |index| index as i32); + (reference.entity, index, index + 1) + } + [first, second] if first.entity == second.entity => { + (first.entity, first.marker?, second.marker?) + } + _ => return None, + }; + let entity = document.get_entity(handle)?; + let start = resolve_point(entity, start_marker)?; + let end = resolve_point(entity, end_marker)?; + let vertex = matches!( + entity, + acadrust::EntityType::LwPolyline(_) | acadrust::EntityType::Polyline2D(_) + ) + .then(|| usize::try_from(end_marker).ok()) + .flatten(); + Some((handle, start, end, vertex)) +} + +/// Moves one polyline vertex in its plane; `false` for any other entity. +pub(crate) fn set_polyline_vertex( + entity: &mut acadrust::EntityType, + index: usize, + x: f64, + y: f64, +) -> bool { + match entity { + acadrust::EntityType::LwPolyline(polyline) => polyline + .vertices + .get_mut(index) + .map(|vertex| { + vertex.location.x = x; + vertex.location.y = y; + }) + .is_some(), + acadrust::EntityType::Polyline2D(polyline) => polyline + .vertices + .get_mut(index) + .map(|vertex| { + vertex.location.x = x; + vertex.location.y = y; + }) + .is_some(), + _ => false, + } +} + /// Grabbed points are exact kernel inputs; the solver anchors the remaining /// endpoint coordinates according to the line's directional constraints. pub(crate) fn grip_solve_anchor_refs(