forked from 1b-agent/opencadstudio-upstream
Merge pull request #1352 from ramox81/codex/symmetric-constraint-parity
Complete Symmetric constraint behavior
This commit is contained in:
commit
da02e38208
11 files changed
+755
-68
No files matched your search
+133
-2
@@ -150,9 +150,14 @@ impl OpenCADStudio {
|
||||
entity, target.handle, target.grip_id,
|
||||
)).unwrap_or_default()
|
||||
}).collect();
|
||||
let retain_size = self.constraint_solve_mode
|
||||
&& !driven_refs.is_empty()
|
||||
&& driven_refs.iter().all(|reference| reference.marker.is_some());
|
||||
let solved = self.tabs[i].scene.solve_parametric_constraints_preview(
|
||||
&touched, &driven_refs,
|
||||
self.constraint_solve_mode && !driven_refs.is_empty(), &self.grip_originals,
|
||||
&touched,
|
||||
&driven_refs,
|
||||
retain_size,
|
||||
&self.grip_originals,
|
||||
);
|
||||
for (handle, entity) in solved {
|
||||
if let Some(slot) = self.tabs[i].scene.document.get_entity_mut(handle) {
|
||||
@@ -3388,6 +3393,132 @@ impl OpenCADStudio {
|
||||
self.commit_undo_delta(i, pd);
|
||||
}
|
||||
}
|
||||
CmdResult::AddSymmetricConstraint {
|
||||
selection,
|
||||
axis,
|
||||
label,
|
||||
} => {
|
||||
use crate::command::SymmetricConstraintSelection;
|
||||
use crate::scene::parametric_constraints::ConstraintKind;
|
||||
|
||||
let scope = self.tabs[i].current_parametric_scope();
|
||||
let to_world = |point: glam::DVec3| {
|
||||
acadrust::types::Vector3::new(point.x, point.y, point.z)
|
||||
};
|
||||
let resolve_point = |pick: crate::command::CoincidentPick| {
|
||||
if let Some(handle) = pick.handle {
|
||||
crate::scene::parametric_constraints::nearest_parametric_point_on_entity(
|
||||
&self.tabs[i].scene.document,
|
||||
scope,
|
||||
handle,
|
||||
to_world(pick.point),
|
||||
)
|
||||
} else {
|
||||
crate::scene::parametric_constraints::nearest_parametric_point(
|
||||
&self.tabs[i].scene.document,
|
||||
scope,
|
||||
to_world(pick.point),
|
||||
None,
|
||||
)
|
||||
}
|
||||
};
|
||||
let (first, second) = match selection {
|
||||
SymmetricConstraintSelection::Objects(first, second) => (first, second),
|
||||
SymmetricConstraintSelection::Points(first, second) => {
|
||||
let (Some(first), Some(second)) =
|
||||
(resolve_point(first), resolve_point(second))
|
||||
else {
|
||||
self.command_line.push_error(
|
||||
"Symmetric: select supported endpoints, centers, midpoints, or vertices.",
|
||||
);
|
||||
return Task::none();
|
||||
};
|
||||
(first, second)
|
||||
}
|
||||
};
|
||||
if first == second
|
||||
|| axis.entity == first.entity
|
||||
|| axis.entity == second.entity
|
||||
{
|
||||
self.command_line
|
||||
.push_error("Symmetric: select two different references and a separate line axis.");
|
||||
return Task::none();
|
||||
}
|
||||
let refs = vec![first, second, axis];
|
||||
if let Err(message) = self.tabs[i].scene.validate_parametric_constraint(
|
||||
ConstraintKind::Symmetric,
|
||||
&refs,
|
||||
None,
|
||||
) {
|
||||
self.command_line.push_error(message);
|
||||
return Task::none();
|
||||
}
|
||||
let duplicate = self.tabs[i]
|
||||
.scene
|
||||
.parametric_constraint_set(scope)
|
||||
.is_some_and(|set| {
|
||||
set.constraints.iter().any(|constraint| {
|
||||
constraint.enabled
|
||||
&& constraint.kind == ConstraintKind::Symmetric
|
||||
&& matches!(constraint.refs.as_slice(), [a, b, m]
|
||||
if *m == axis
|
||||
&& ((*a == first && *b == second)
|
||||
|| (*a == second && *b == first)))
|
||||
})
|
||||
});
|
||||
if duplicate {
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
self.command_line
|
||||
.push_output("The Symmetric constraint already exists.");
|
||||
return Task::none();
|
||||
}
|
||||
let constraints_before = self.tabs[i]
|
||||
.scene
|
||||
.parametric_constraint_set(scope)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| {
|
||||
crate::scene::parametric_constraints::ParametricConstraintSet::new(scope)
|
||||
});
|
||||
let mut touched = Vec::new();
|
||||
for reference in &refs {
|
||||
if !touched.contains(&reference.entity) {
|
||||
touched.push(reference.entity);
|
||||
}
|
||||
}
|
||||
let pending = self.begin_undo(i, label, touched.len(), true);
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.record_undo_parametric_constraints_before(scope, constraints_before);
|
||||
let id = self.tabs[i].scene.parametric_constraint_set_mut(scope).add(
|
||||
ConstraintKind::Symmetric,
|
||||
refs,
|
||||
None,
|
||||
);
|
||||
self.tabs[i].scene.note_parametric_constraint_applied(
|
||||
scope,
|
||||
id,
|
||||
self.constraint_bar_display,
|
||||
);
|
||||
let changes = touched
|
||||
.into_iter()
|
||||
.map(|handle| (handle, crate::scene::ChangeKind::Modified))
|
||||
.collect::<Vec<_>>();
|
||||
self.tabs[i].scene.bump_entities_with_initial_parametric_policy(
|
||||
&changes,
|
||||
&[first, axis],
|
||||
false,
|
||||
);
|
||||
self.tabs[i].dirty = true;
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
self.command_line
|
||||
.push_output("Symmetric constraint applied.");
|
||||
self.refresh_properties();
|
||||
if let Some(pd) = pending {
|
||||
self.commit_undo_delta(i, pd);
|
||||
}
|
||||
}
|
||||
CmdResult::AddPerpendicularConstraint {
|
||||
first,
|
||||
second,
|
||||
|
||||
+33
-20
@@ -1618,27 +1618,40 @@ impl OpenCADStudio {
|
||||
"SYCONSTRAINT" => {
|
||||
let handles = self.tabs[i].scene.selected_handles_in_order();
|
||||
if handles.is_empty() {
|
||||
use crate::modules::draw::select::SelectObjectsCommand;
|
||||
let sel = SelectObjectsCommand::new(cmd);
|
||||
self.command_line.push_info(&sel.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(sel));
|
||||
} else if handles.len() != 3 {
|
||||
self.command_line.push_output(
|
||||
"Select exactly three entities (two circles/arcs, then the mirror line), then run this constraint again.",
|
||||
);
|
||||
use crate::modules::parametric::SymmetricConstraintCommand;
|
||||
let command = SymmetricConstraintCommand::new();
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
} else {
|
||||
use crate::command::CmdResult;
|
||||
use crate::scene::parametric_constraints::{ConstraintKind, ParametricRef};
|
||||
return Some(self.apply_cmd_result(CmdResult::AddParametricConstraint {
|
||||
kind: ConstraintKind::Symmetric,
|
||||
refs: vec![
|
||||
ParametricRef::center(handles[0]),
|
||||
ParametricRef::center(handles[1]),
|
||||
ParametricRef::whole(handles[2]),
|
||||
],
|
||||
driving_param: None,
|
||||
label: "Symmetric constraint",
|
||||
}));
|
||||
use crate::command::{
|
||||
CmdResult, SymmetricConstraintSelection,
|
||||
};
|
||||
use crate::modules::parametric::SymmetricConstraintCommand;
|
||||
|
||||
let refs = (handles.len() == 3).then(|| {
|
||||
let first = self.tabs[i].scene.document.get_entity(handles[0])?;
|
||||
let second = self.tabs[i].scene.document.get_entity(handles[1])?;
|
||||
let axis = self.tabs[i].scene.document.get_entity(handles[2])?;
|
||||
SymmetricConstraintCommand::preselected_refs(
|
||||
(first, handles[0]),
|
||||
(second, handles[1]),
|
||||
(axis, handles[2]),
|
||||
)
|
||||
}).flatten();
|
||||
if let Some([first, second, axis]) = refs {
|
||||
return Some(self.apply_cmd_result(CmdResult::AddSymmetricConstraint {
|
||||
selection: SymmetricConstraintSelection::Objects(first, second),
|
||||
axis,
|
||||
label: "Symmetric constraint",
|
||||
}));
|
||||
}
|
||||
self.tabs[i].scene.deselect_all();
|
||||
self.command_line.push_error(
|
||||
"Symmetric: select two compatible objects followed by a line axis.",
|
||||
);
|
||||
let command = SymmetricConstraintCommand::new();
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+12
-2
@@ -2791,7 +2791,16 @@ handles={handles_ms:.1} panel={:.1} ribbon={ribbon_ms:.1} tail={:.1} selected={}
|
||||
handles: &[Handle],
|
||||
driven_refs: &[crate::scene::parametric_constraints::ParametricRef],
|
||||
) {
|
||||
self.invalidate_property_targets_with_originals(i, handles, driven_refs, &[]);
|
||||
let retain_size = self.constraint_solve_mode
|
||||
&& !driven_refs.is_empty()
|
||||
&& driven_refs.iter().all(|reference| reference.marker.is_some());
|
||||
self.invalidate_property_targets_with_originals(
|
||||
i,
|
||||
handles,
|
||||
driven_refs,
|
||||
retain_size,
|
||||
&[],
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn invalidate_property_targets_with_originals(
|
||||
@@ -2799,6 +2808,7 @@ handles={handles_ms:.1} panel={:.1} ribbon={ribbon_ms:.1} tail={:.1} selected={}
|
||||
i: usize,
|
||||
handles: &[Handle],
|
||||
driven_refs: &[crate::scene::parametric_constraints::ParametricRef],
|
||||
retain_size: bool,
|
||||
retained_originals: &[(Handle, acadrust::EntityType)],
|
||||
) {
|
||||
let mut context_object_changed = false;
|
||||
@@ -2835,7 +2845,7 @@ handles={handles_ms:.1} panel={:.1} ribbon={ribbon_ms:.1} tail={:.1} selected={}
|
||||
.bump_entities_with_parametric_originals(
|
||||
&changes,
|
||||
driven_refs,
|
||||
self.constraint_solve_mode && !driven_refs.is_empty(),
|
||||
retain_size,
|
||||
retained_originals,
|
||||
);
|
||||
}
|
||||
|
||||
+22
-12
@@ -3263,15 +3263,17 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
self.tabs[i].properties.active_field = None;
|
||||
let handles = self.property_target_handles(i);
|
||||
if !handles.is_empty() {
|
||||
let driven_marker = match field {
|
||||
"start_x" | "start_y" | "start_z" => Some(0),
|
||||
"end_x" | "end_y" | "end_z" => Some(1),
|
||||
"center_x" | "center_y" | "center_z" => Some(-3),
|
||||
let driven_reference = match field {
|
||||
"start_x" | "start_y" | "start_z" => Some(Some(0)),
|
||||
"end_x" | "end_y" | "end_z" => Some(Some(1)),
|
||||
"center_x" | "center_y" | "center_z" => Some(Some(-3)),
|
||||
"radius" | "diameter" | "circumference" | "area"
|
||||
| "major_r" | "minor_r" | "ratio" => Some(None),
|
||||
_ => None,
|
||||
};
|
||||
let retained_originals: Vec<_> = if self.constraint_solve_mode
|
||||
&& driven_marker.is_some()
|
||||
{
|
||||
let retain_size = self.constraint_solve_mode
|
||||
&& driven_reference.is_some_and(|marker| marker.is_some());
|
||||
let retained_originals: Vec<_> = if retain_size {
|
||||
handles
|
||||
.iter()
|
||||
.filter_map(|handle| {
|
||||
@@ -3602,13 +3604,20 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
}
|
||||
}
|
||||
}
|
||||
let driven_refs: Vec<_> = driven_marker
|
||||
let driven_refs: Vec<_> = driven_reference
|
||||
.into_iter()
|
||||
.flat_map(|marker| {
|
||||
handles.iter().copied().map(move |handle| {
|
||||
crate::scene::parametric_constraints::ParametricRef::point(
|
||||
handle, marker,
|
||||
)
|
||||
handles.iter().copied().map(move |handle| match marker {
|
||||
Some(marker) => {
|
||||
crate::scene::parametric_constraints::ParametricRef::point(
|
||||
handle, marker,
|
||||
)
|
||||
}
|
||||
None => {
|
||||
crate::scene::parametric_constraints::ParametricRef::whole(
|
||||
handle,
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
@@ -3616,6 +3625,7 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
i,
|
||||
&handles,
|
||||
&driven_refs,
|
||||
retain_size,
|
||||
&retained_originals,
|
||||
);
|
||||
self.tabs[i].dirty = true;
|
||||
|
||||
Reference in new issue
Block a user