Match Fix geometric constraint behavior

1) FXCONSTRAINT (and the new GCFIX name) prompts "Select point or [Object]
   <Object>:" — a pick fixes that constraint point; Enter or O asks for one
   line, polyline segment, circle, arc, ellipse or spline; misses and
   unsupported objects report the reference messages and re-prompt.
2) Point, block, text, MText, attribute and table insertion points are
   addressable constraint points.
3) Re-applying Fixed to the same point or object reports that the constraint
   already exists instead of adding a duplicate.
4) A persistent Fixed holds its pre-edit geometry through MOVE, ROTATE,
   SCALE, STRETCH, grip and Properties edits: fixed points stay, free points
   of a partly fixed entity follow the edit, and size retention skips fixed
   entities.
5) Object mode on a polyline fixes the picked segment (natively persisted)
   instead of the whole polyline; arc segments pin endpoints, center and
   radius.
6) Ribbon label "Fix" with a padlock icon.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
ramox81andClaude Fable 5.1 committed 2026-09-18 15:38:33 +03:00
1 parent 2b38f798c0
commit f998b59193
9 files changed
+483 -206

No files matched your search

+139
View File
@@ -0,0 +1,139 @@
//! GCFIX / FXCONSTRAINT — locks a constraint point, or a whole curve /
//! polyline segment, at its current position. Picks resolve on the host (a
//! `CadCommand` has no document access): `AddFixedConstraint` carries the
//! click, the driver maps it to a `ParametricRef` and re-prompts on a miss,
//! the way the reference does.
use acadrust::{EntityType, Handle};
use glam::DVec3;
use crate::command::{CadCommand, CmdOption, CmdResult, CoincidentPick};
use crate::scene::parametric_constraints::ParametricRef;
#[derive(Clone, Copy)]
enum Step {
PointOrObject,
Object,
}
pub struct FixConstraintCommand {
step: Step,
}
impl FixConstraintCommand {
pub const INVALID_OBJECT: &'static str =
"Invalid selection for Fixed. Select a line, polyline segment, circle, arc, ellipse or spline.";
pub const NO_POINT: &'static str = "No valid constraint point found.";
pub const NO_OBJECT: &'static str = "No object found.";
pub fn new() -> Self {
Self {
step: Step::PointOrObject,
}
}
/// The Object-mode reading of a single preselected entity: a whole
/// curve, or a one-segment polyline's segment. Anything else prompts.
pub fn preselected_reference(entity: &EntityType, handle: Handle) -> Option<ParametricRef> {
match entity {
EntityType::Line(_)
| EntityType::Circle(_)
| EntityType::Arc(_)
| EntityType::Ellipse(_)
| EntityType::Spline(_) => Some(ParametricRef::whole(handle)),
EntityType::LwPolyline(polyline) => (polyline.vertices.len() == 2
&& !polyline.is_closed)
.then(|| ParametricRef::segment(handle, 0)),
EntityType::Polyline2D(polyline) => (polyline.vertices.len() == 2
&& !polyline.is_closed())
.then(|| ParametricRef::segment(handle, 0)),
_ => None,
}
}
fn pick(&self, handle: Option<Handle>, point: DVec3) -> CmdResult {
CmdResult::AddFixedConstraint(CoincidentPick {
handle,
point,
whole_curve: matches!(self.step, Step::Object),
})
}
}
impl CadCommand for FixConstraintCommand {
fn name(&self) -> &'static str {
"FXCONSTRAINT"
}
fn prompt(&self) -> String {
match self.step {
Step::PointOrObject => "FXCONSTRAINT Select point or [Object] <Object>:".to_string(),
Step::Object => "FXCONSTRAINT Select object:".to_string(),
}
}
fn options(&self) -> Vec<CmdOption> {
matches!(self.step, Step::PointOrObject)
.then(|| vec![CmdOption::new("Object", "O")])
.unwrap_or_default()
}
fn wants_text_input(&self) -> bool {
true
}
fn point_step_accepts_keywords(&self) -> bool {
matches!(self.step, Step::PointOrObject)
}
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
let keyword = text.trim().trim_start_matches('_').to_ascii_uppercase();
let to_object =
matches!(self.step, Step::PointOrObject) && matches!(keyword.as_str(), "O" | "OBJECT");
to_object.then(|| {
self.step = Step::Object;
CmdResult::NeedPoint
})
}
fn needs_entity_pick(&self) -> bool {
true
}
fn entity_pick_accepts_points(&self) -> bool {
true
}
fn entity_pick_highlights_hover(&self) -> bool {
true
}
fn on_entity_pick(&mut self, handle: Handle, point: DVec3) -> CmdResult {
if handle.is_null() {
return self.on_point(point);
}
self.pick(Some(handle), point)
}
fn on_point(&mut self, point: DVec3) -> CmdResult {
match self.step {
Step::PointOrObject => self.pick(None, point),
// A click on nothing at "Select object:".
Step::Object => CmdResult::ReportError(Self::NO_OBJECT.to_string()),
}
}
fn on_enter(&mut self) -> CmdResult {
match self.step {
Step::PointOrObject => {
self.step = Step::Object;
CmdResult::NeedPoint
}
Step::Object => CmdResult::Cancel,
}
}
fn on_escape(&mut self) -> CmdResult {
CmdResult::Cancel
}
}
+3
View File
@@ -6,6 +6,8 @@ mod coincident;
mod concentric;
mod constraint_bar;
mod equal_distance;
#[path = "fixed.rs"]
mod fixed_command;
mod geom_constraint;
#[path = "horizontal.rs"]
mod horizontal_command;
@@ -21,6 +23,7 @@ pub use coincident::{coincident_tool, CoincidentConstraintCommand};
pub use concentric::ConcentricConstraintCommand;
pub use constraint_bar::ConstraintBarOptionCommand;
pub use equal_distance::{equal_distance_tool, EqualDistanceConstraintCommand};
pub use fixed_command::FixConstraintCommand;
pub use geom_constraint::GeomConstraintCommand;
pub use horizontal_command::HorizontalConstraintCommand;
pub use perpendicular_command::{PerpendicularConstraintCommand, PerpendicularPick};
+2 -1
View File
@@ -136,7 +136,7 @@ pub mod fixed {
pub fn tool() -> ToolDef {
ToolDef {
id: "FXCONSTRAINT",
label: "Fixed",
label: "Fix",
icon: IconKind::Svg(include_bytes!("../../../assets/icons/constrain/fixed.svg")),
event: ModuleEvent::Command("FXCONSTRAINT".to_string()),
}
@@ -171,6 +171,7 @@ inventory::submit!(crate::command::CommandRegistration {
"NRCONSTRAINT",
"LCONSTRAINT",
"FXCONSTRAINT",
"GCFIX",
"SYCONSTRAINT",
]
});