Merge pull request #1332 from ramox81/codex/concentric-constraint-parity

# Conflicts:
#	src/entities/arc.rs
#	src/entities/ellipse.rs
#	src/modules/parametric/mod.rs
#	src/scene/parametric_solve.rs
#	src/ui/overlay.rs
This commit is contained in:
Hakan Seven committed 2026-09-17 17:24:14 +03:00
commit 16c417f81d
17 files changed
+692 -52

No files matched your search

+158
View File
@@ -0,0 +1,158 @@
use acadrust::{EntityType, Handle};
use glam::DVec3;
use crate::command::{CadCommand, CmdResult};
use crate::scene::parametric_constraints::ParametricRef;
use crate::t;
pub struct ConcentricConstraintCommand {
first: Option<ParametricRef>,
picked_entity: Option<EntityType>,
}
impl ConcentricConstraintCommand {
pub fn new() -> Self {
Self {
first: None,
picked_entity: None,
}
}
pub fn with_first(first: ParametricRef) -> Self {
Self {
first: Some(first),
picked_entity: None,
}
}
fn invalid_selection() -> CmdResult {
CmdResult::ReportError(
t!("Invalid selection for Concentric. Select a circle, arc, ellipse or polyline arc segment.")
.into_owned(),
)
}
fn polyline_segment_is_arc(entity: &EntityType, index: usize) -> bool {
match entity {
EntityType::LwPolyline(polyline) => polyline
.vertices
.get(index)
.is_some_and(|vertex| vertex.bulge.abs() > 1.0e-12),
EntityType::Polyline2D(polyline) => polyline
.vertices
.get(index)
.is_some_and(|vertex| vertex.bulge.abs() > 1.0e-12),
_ => false,
}
}
fn picked_reference(entity: &EntityType, handle: Handle, point: DVec3) -> Option<ParametricRef> {
match entity {
EntityType::Circle(_) | EntityType::Arc(_) | EntityType::Ellipse(_) => {
Some(ParametricRef::center(handle))
}
EntityType::LwPolyline(_) | EntityType::Polyline2D(_) => {
let planar = crate::entities::curve::entity_curve(entity)?;
let local = planar.plane.project(point.to_array())?;
let segments = planar.curve.segments();
let (index, _) = cadkernel::geom2d::nearest_of(segments.iter(), local)?;
matches!(segments.get(index), Some(cadkernel::geom2d::Curve::Arc(_)))
.then(|| ParametricRef::segment_center(handle, index))
}
_ => None,
}
}
pub fn preselected_reference(entity: &EntityType, handle: Handle) -> Option<ParametricRef> {
match entity {
EntityType::Circle(_) | EntityType::Arc(_) | EntityType::Ellipse(_) => {
Some(ParametricRef::center(handle))
}
EntityType::LwPolyline(polyline) => {
let segment_count = if polyline.is_closed {
polyline.vertices.len()
} else {
polyline.vertices.len().saturating_sub(1)
};
(segment_count == 1 && Self::polyline_segment_is_arc(entity, 0))
.then(|| ParametricRef::segment_center(handle, 0))
}
EntityType::Polyline2D(polyline) => {
let segment_count = if polyline.is_closed() {
polyline.vertices.len()
} else {
polyline.vertices.len().saturating_sub(1)
};
(segment_count == 1 && Self::polyline_segment_is_arc(entity, 0))
.then(|| ParametricRef::segment_center(handle, 0))
}
_ => None,
}
}
}
impl CadCommand for ConcentricConstraintCommand {
fn name(&self) -> &'static str {
"GCCONCENTRIC"
}
fn prompt(&self) -> String {
if self.first.is_some() {
t!("GCCONCENTRIC Select second object:").into_owned()
} else {
t!("GCCONCENTRIC Select first object:").into_owned()
}
}
fn needs_entity_pick(&self) -> bool {
true
}
fn entity_pick_highlights_hover(&self) -> bool {
true
}
fn inject_before_entity_pick(&self) -> bool {
true
}
fn inject_picked_entity(&mut self, entity: EntityType) {
self.picked_entity = Some(entity);
}
fn on_entity_pick(&mut self, handle: Handle, point: DVec3) -> CmdResult {
if handle.is_null() {
return CmdResult::NeedPoint;
}
let Some(entity) = self.picked_entity.take() else {
return CmdResult::NeedPoint;
};
let Some(reference) = Self::picked_reference(&entity, handle, point) else {
return Self::invalid_selection();
};
let Some(first) = self.first else {
self.first = Some(reference);
return CmdResult::NeedPoint;
};
if first == reference {
return Self::invalid_selection();
}
CmdResult::AddConcentricConstraint {
first,
second: reference,
label: "Concentric constraint",
}
}
fn on_point(&mut self, _point: DVec3) -> CmdResult {
CmdResult::NeedPoint
}
fn on_enter(&mut self) -> CmdResult {
CmdResult::Cancel
}
fn on_escape(&mut self) -> CmdResult {
CmdResult::Cancel
}
}
+1 -1
View File
@@ -21,7 +21,7 @@ impl GeomConstraintCommand {
"T" | "TANGENT" => "TCONSTRAINT",
"SM" | "SMOOTH" => "GCSMOOTH",
"C" | "COINCIDENT" => "GCCOINCIDENT",
"CON" | "CONCENTRIC" => "NCONSTRAINT",
"CON" | "CONCENTRIC" => "GCCONCENTRIC",
"COL" | "COLLINEAR" | "COLINEAR" => "LCONSTRAINT",
"SY" | "SYMMETRIC" => "SYCONSTRAINT",
"E" | "EQUAL" => "ECONSTRAINT",
+6 -4
View File
@@ -3,6 +3,7 @@
//! resulting constraints through the geometry kernel.
mod coincident;
mod concentric;
mod equal_distance;
mod geom_constraint;
#[path = "perpendicular.rs"]
@@ -14,6 +15,7 @@ mod tangent_command;
mod tools;
mod value;
pub use coincident::{coincident_tool, CoincidentConstraintCommand};
pub use concentric::ConcentricConstraintCommand;
pub use equal_distance::{equal_distance_tool, EqualDistanceConstraintCommand};
pub use geom_constraint::GeomConstraintCommand;
pub use perpendicular_command::{PerpendicularConstraintCommand, PerpendicularPick};
@@ -23,8 +25,8 @@ pub use point_on_entity::{
pub use smooth::SmoothConstraintCommand;
pub use tangent_command::TangentConstraintCommand;
pub use tools::{
colinear, concentric, equal, fixed, horizontal, normal, parallel, perpendicular, symmetric,
tangent, vertical,
colinear, concentric as concentric_tool, equal, fixed, horizontal, normal, parallel,
perpendicular, symmetric, tangent, vertical,
};
pub use value::{
angle_tool, dimensional_tools, distance_tool, AngleConstraintCommand,
@@ -67,7 +69,7 @@ impl CadModule for ParametricModule {
"GCSMOOTH", "Smooth",
include_bytes!("../../../assets/icons/constrain/smooth.svg"),
)),
RibbonItem::LargeTool(concentric::tool()),
RibbonItem::LargeTool(concentric_tool::tool()),
RibbonItem::LargeTool(horizontal::tool()),
RibbonItem::LargeTool(symmetric::tool()),
RibbonItem::LargeTool(fixed::tool()),
@@ -170,7 +172,7 @@ mod tests {
groups[0].tools.iter().map(item_id).collect::<Vec<_>>(),
[
"AUTOCONSTRAIN", "CCONSTRAINT", "PCONSTRAINT", "TCONSTRAINT",
"LCONSTRAINT", "QCONSTRAINT", "GCSMOOTH", "NCONSTRAINT",
"LCONSTRAINT", "QCONSTRAINT", "GCSMOOTH", "GCCONCENTRIC",
"HCONSTRAINT", "SYCONSTRAINT", "FXCONSTRAINT", "VCONSTRAINT",
"ECONSTRAINT", "GCVISIBILITY", "GCSHOWALL", "GCHIDEALL",
]
+3 -2
View File
@@ -88,12 +88,12 @@ pub mod concentric {
use super::*;
pub fn tool() -> ToolDef {
ToolDef {
id: "NCONSTRAINT",
id: "GCCONCENTRIC",
label: "Concentric",
icon: IconKind::Svg(include_bytes!(
"../../../assets/icons/constrain/concentric.svg"
)),
event: ModuleEvent::Command("NCONSTRAINT".to_string()),
event: ModuleEvent::Command("GCCONCENTRIC".to_string()),
}
}
}
@@ -168,6 +168,7 @@ inventory::submit!(crate::command::CommandRegistration {
"ECONSTRAINT",
"TCONSTRAINT",
"NCONSTRAINT",
"GCCONCENTRIC",
"NRCONSTRAINT",
"LCONSTRAINT",
"FXCONSTRAINT",