forked from 1b-agent/opencadstudio-upstream
Merge pull request #1284 from ramox81/codex/autoconstrain-parity
# Conflicts: # Cargo.lock # Cargo.toml # src/app/view/mod.rs # src/ui/overlay.rs
This commit is contained in:
commit
c9eeb6f66e
23 files changed
+1461
-99
No files matched your search
@@ -3247,6 +3247,29 @@ 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),
|
||||
_ => None,
|
||||
};
|
||||
let retained_originals: Vec<_> = if self.constraint_solve_mode
|
||||
&& driven_marker.is_some()
|
||||
{
|
||||
handles
|
||||
.iter()
|
||||
.filter_map(|handle| {
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(*handle)
|
||||
.cloned()
|
||||
.map(|entity| (*handle, entity))
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let evaluates_expression = self.tabs[i]
|
||||
.properties
|
||||
.sections
|
||||
@@ -3563,7 +3586,22 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
}
|
||||
}
|
||||
}
|
||||
self.invalidate_property_targets(i, &handles);
|
||||
let driven_refs: Vec<_> = driven_marker
|
||||
.into_iter()
|
||||
.flat_map(|marker| {
|
||||
handles.iter().copied().map(move |handle| {
|
||||
crate::scene::parametric_constraints::ParametricRef::point(
|
||||
handle, marker,
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
self.invalidate_property_targets_with_originals(
|
||||
i,
|
||||
&handles,
|
||||
&driven_refs,
|
||||
&retained_originals,
|
||||
);
|
||||
self.tabs[i].dirty = true;
|
||||
self.refresh_properties();
|
||||
}
|
||||
|
||||
@@ -428,6 +428,10 @@ impl OpenCADStudio {
|
||||
backup_on_save: self.backup_on_save,
|
||||
file_assoc_enabled: self.file_assoc_enabled,
|
||||
show_constraint_values: self.show_constraint_values,
|
||||
auto_constrain: self.auto_constrain_settings.clone(),
|
||||
constraint_solve_mode: self.constraint_solve_mode,
|
||||
constraint_infer: self.constraint_infer,
|
||||
constraint_bar_display: self.constraint_bar_display,
|
||||
savetime_min: self.savetime_min,
|
||||
default_save_format: self.default_save_format.clone(),
|
||||
pick_add: self.pick_add,
|
||||
@@ -509,6 +513,15 @@ impl OpenCADStudio {
|
||||
self.backup_on_save = s.backup_on_save;
|
||||
self.file_assoc_enabled = s.file_assoc_enabled;
|
||||
self.show_constraint_values = s.show_constraint_values;
|
||||
self.auto_constrain_settings = s.auto_constrain.clone();
|
||||
self.auto_constrain_settings.sanitize();
|
||||
self.auto_constrain_distance_input =
|
||||
format!("{}", self.auto_constrain_settings.distance_tolerance);
|
||||
self.auto_constrain_angle_input =
|
||||
format!("{}", self.auto_constrain_settings.angle_tolerance_deg);
|
||||
self.constraint_solve_mode = s.constraint_solve_mode;
|
||||
self.constraint_infer = s.constraint_infer;
|
||||
self.constraint_bar_display = s.constraint_bar_display.clamp(0, 3);
|
||||
self.savetime_min = s.savetime_min;
|
||||
self.default_save_format =
|
||||
crate::io::canonical_save_format(&s.default_save_format).to_string();
|
||||
|
||||
@@ -4289,6 +4289,112 @@ impl OpenCADStudio {
|
||||
self.drafting_settings_close_confirm = false;
|
||||
Task::none()
|
||||
}
|
||||
Message::AutoConstrainSelectRow(index) => {
|
||||
if index < self.auto_constrain_settings.priority.len() {
|
||||
self.auto_constrain_selected_row = index;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::AutoConstrainToggleKind(kind) => {
|
||||
if let Some(index) = self
|
||||
.auto_constrain_settings
|
||||
.enabled
|
||||
.iter()
|
||||
.position(|candidate| *candidate == kind)
|
||||
{
|
||||
self.auto_constrain_settings.enabled.remove(index);
|
||||
} else {
|
||||
self.auto_constrain_settings.enabled.push(kind);
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::AutoConstrainMoveUp => {
|
||||
let index = self.auto_constrain_selected_row;
|
||||
if index > 0 && index < self.auto_constrain_settings.priority.len() {
|
||||
self.auto_constrain_settings.priority.swap(index, index - 1);
|
||||
self.auto_constrain_selected_row -= 1;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::AutoConstrainMoveDown => {
|
||||
let index = self.auto_constrain_selected_row;
|
||||
if index + 1 < self.auto_constrain_settings.priority.len() {
|
||||
self.auto_constrain_settings.priority.swap(index, index + 1);
|
||||
self.auto_constrain_selected_row += 1;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::AutoConstrainSelectAll => {
|
||||
self.auto_constrain_settings.enabled =
|
||||
super::settings::AutoConstraintKind::ALL.to_vec();
|
||||
Task::none()
|
||||
}
|
||||
Message::AutoConstrainClearAll => {
|
||||
self.auto_constrain_settings.enabled.clear();
|
||||
Task::none()
|
||||
}
|
||||
Message::AutoConstrainReset => {
|
||||
self.auto_constrain_settings =
|
||||
super::settings::AutoConstrainSettings::default();
|
||||
self.auto_constrain_selected_row = 0;
|
||||
self.auto_constrain_distance_input =
|
||||
format!("{}", self.auto_constrain_settings.distance_tolerance);
|
||||
self.auto_constrain_angle_input =
|
||||
format!("{}", self.auto_constrain_settings.angle_tolerance_deg);
|
||||
Task::none()
|
||||
}
|
||||
Message::AutoConstrainToggleTangentPoint => {
|
||||
self.auto_constrain_settings.tangent_must_share_point =
|
||||
!self.auto_constrain_settings.tangent_must_share_point;
|
||||
Task::none()
|
||||
}
|
||||
Message::AutoConstrainTogglePerpendicularIntersection => {
|
||||
self.auto_constrain_settings.perpendicular_must_intersect =
|
||||
!self.auto_constrain_settings.perpendicular_must_intersect;
|
||||
Task::none()
|
||||
}
|
||||
Message::AutoConstrainDistanceChanged(value) => {
|
||||
self.auto_constrain_distance_input = value;
|
||||
Task::none()
|
||||
}
|
||||
Message::AutoConstrainAngleChanged(value) => {
|
||||
self.auto_constrain_angle_input = value;
|
||||
Task::none()
|
||||
}
|
||||
action @ (Message::AutoConstrainApply | Message::AutoConstrainOk) => {
|
||||
let distance = self.auto_constrain_distance_input.trim().parse::<f64>();
|
||||
let angle = self.auto_constrain_angle_input.trim().parse::<f64>();
|
||||
match (distance, angle) {
|
||||
(Ok(distance), Ok(angle))
|
||||
if distance.is_finite()
|
||||
&& distance >= 0.0
|
||||
&& angle.is_finite()
|
||||
&& angle >= 0.0 =>
|
||||
{
|
||||
self.auto_constrain_settings.distance_tolerance = distance;
|
||||
self.auto_constrain_settings.angle_tolerance_deg = angle;
|
||||
self.auto_constrain_settings.sanitize();
|
||||
self.auto_constrain_saved =
|
||||
Some(self.auto_constrain_settings.clone());
|
||||
self.persist_settings_if_changed();
|
||||
if matches!(action, Message::AutoConstrainOk) {
|
||||
self.close_active_modal();
|
||||
}
|
||||
}
|
||||
_ => self.command_line.push_error(
|
||||
crate::t!("Distance and angle tolerances must be non-negative numbers.")
|
||||
.as_ref(),
|
||||
),
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::AutoConstrainCancel => {
|
||||
if let Some(saved) = self.auto_constrain_saved.take() {
|
||||
self.auto_constrain_settings = saved;
|
||||
}
|
||||
self.close_active_modal();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
// ── Ribbon dropdowns ──────────────────────────────────────────
|
||||
Message::ToggleRibbonDropdown(id) => {
|
||||
@@ -6857,6 +6963,11 @@ impl OpenCADStudio {
|
||||
return Task::none();
|
||||
}
|
||||
self.drafting_settings_close_confirm = false;
|
||||
if self.active_modal == Some(super::ModalKind::AutoConstrainSettings) {
|
||||
if let Some(saved) = self.auto_constrain_saved.take() {
|
||||
self.auto_constrain_settings = saved;
|
||||
}
|
||||
}
|
||||
let resume_open_queue = self.active_modal == Some(super::ModalKind::Recovery);
|
||||
self.close_active_modal();
|
||||
if resume_open_queue {
|
||||
|
||||
@@ -933,6 +933,13 @@ impl OpenCADStudio {
|
||||
return Task::none();
|
||||
}
|
||||
let i = self.active_tab;
|
||||
let constraint_hover = self
|
||||
.constraint_glyph_under(i, p)
|
||||
.map(|(_, handles)| handles)
|
||||
.unwrap_or_default();
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.set_constraint_hover_highlights(&constraint_hover);
|
||||
// Modifier-driven selection must be known before cursor_plane/axis and
|
||||
// drafting constraints are read, not merely before the final callback.
|
||||
if let Some(command) = self.tabs[i].active_cmd.as_mut() {
|
||||
@@ -1737,11 +1744,31 @@ impl OpenCADStudio {
|
||||
);
|
||||
}
|
||||
}
|
||||
let driven_refs: Vec<_> = grip
|
||||
.targets
|
||||
.iter()
|
||||
.filter_map(|target| {
|
||||
let entity = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(target.handle)?;
|
||||
crate::scene::parametric_constraints::driven_ref_for_grip(
|
||||
entity,
|
||||
target.handle,
|
||||
target.grip_id,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
// Re-solve constrained neighbors on each drag frame and include
|
||||
// their original state in the gesture's undo record.
|
||||
let solved_by_constraints = self.tabs[i]
|
||||
.scene
|
||||
.solve_parametric_constraints_preview(&edited_handles);
|
||||
.solve_parametric_constraints_preview(
|
||||
&edited_handles,
|
||||
&driven_refs,
|
||||
self.constraint_solve_mode && !driven_refs.is_empty(),
|
||||
&self.grip_originals,
|
||||
);
|
||||
for (handle, _) in &solved_by_constraints {
|
||||
let handle = *handle;
|
||||
if !self.grip_preview_handles.contains(&handle) {
|
||||
@@ -2637,6 +2664,7 @@ impl OpenCADStudio {
|
||||
// viewport so it doesn't stick while the mouse is over the
|
||||
// ribbon / panels.
|
||||
self.tabs[i].scene.set_hover_highlight(None);
|
||||
self.tabs[i].scene.set_constraint_hover_highlights(&[]);
|
||||
// Don't touch `context_menu` here. ViewportExit also fires
|
||||
// when an upper overlay (the right-click menu panel) takes
|
||||
// the cursor, so clearing the menu state on every exit
|
||||
@@ -2949,6 +2977,34 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn constraint_glyph_under(
|
||||
&self,
|
||||
i: usize,
|
||||
cursor: iced::Point,
|
||||
) -> Option<(
|
||||
crate::scene::parametric_constraints::ConstraintKind,
|
||||
Vec<acadrust::Handle>,
|
||||
)> {
|
||||
if self.tabs[i].scene.current_layout != "Model" {
|
||||
return None;
|
||||
}
|
||||
let vp_size = self.tabs[i].scene.selection.borrow().vp_size;
|
||||
let scope = self.tabs[i].current_parametric_scope();
|
||||
let id = self.tabs[i].scene.constraint_glyph_hit(
|
||||
scope,
|
||||
vp_size,
|
||||
self.show_constraint_values,
|
||||
self.constraint_bar_display,
|
||||
cursor,
|
||||
)?;
|
||||
let set = self.tabs[i].scene.parametric_constraint_set(scope)?;
|
||||
let constraint = set.get(id)?;
|
||||
let mut handles: Vec<_> = constraint.refs.iter().map(|r| r.entity).collect();
|
||||
handles.sort_unstable_by_key(|handle| handle.value());
|
||||
handles.dedup();
|
||||
Some((constraint.kind, handles))
|
||||
}
|
||||
|
||||
pub(super) fn on_viewport_left_press(&mut self) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
if let Some(command) = self.tabs[i].active_cmd.as_mut() {
|
||||
@@ -3004,6 +3060,23 @@ impl OpenCADStudio {
|
||||
};
|
||||
let (vw, vh) = vp_size;
|
||||
|
||||
if self.tabs[i].active_cmd.is_none() {
|
||||
if let Some((kind, handles)) = self.constraint_glyph_under(i, p) {
|
||||
self.tabs[i].scene.deselect_all();
|
||||
self.tabs[i].scene.select_entities(&handles);
|
||||
self.refresh_selected_grips();
|
||||
self.refresh_properties();
|
||||
self.command_line
|
||||
.push_info(&format!("{kind:?} constraint selected."));
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.selection
|
||||
.borrow_mut()
|
||||
.clear_left_selection_gesture();
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
|
||||
// An engaged grip owns the next left press (click-move-click placement
|
||||
// or the release of a press-drag). Do not let the same press arm the
|
||||
// normal box/lasso state or trigger another viewport control; the
|
||||
@@ -4432,6 +4505,7 @@ properties={:.1}ms picked={}",
|
||||
scope,
|
||||
canvas_sz,
|
||||
self.show_constraint_values,
|
||||
self.constraint_bar_display,
|
||||
p_full,
|
||||
)
|
||||
})
|
||||
|
||||
Reference in new issue
Block a user