forked from 1b-agent/opencadstudio-upstream
Integrate standard parametric PR fixes
This commit is contained in:
1 parent
d738ebfcc6
commit
06fcf09ae3
7 files changed
+179
-16
No files matched your search
@@ -966,22 +966,20 @@ impl OpenCADStudio {
|
|||||||
TableCellEditStart::Started
|
TableCellEditStart::Started
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes and re-solves the first conflicting constraint in the active scope.
|
fn remove_parametric_constraint(
|
||||||
pub(super) fn resolve_one_parametric_conflict(&mut self) {
|
&mut self,
|
||||||
|
id: crate::scene::parametric_constraints::ConstraintId,
|
||||||
|
label: &'static str,
|
||||||
|
) -> bool {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let scope = self.tabs[i].current_parametric_scope();
|
let scope = self.tabs[i].current_parametric_scope();
|
||||||
let Some(set) = self.tabs[i].scene.parametric_constraint_set(scope) else {
|
let Some(set) = self.tabs[i].scene.parametric_constraint_set(scope) else {
|
||||||
return;
|
return false;
|
||||||
};
|
|
||||||
let Some(&(id, _kind)) = set.conflicts.first() else {
|
|
||||||
return;
|
|
||||||
};
|
};
|
||||||
let Some(constraint) = set.get(id) else {
|
let Some(constraint) = set.get(id) else {
|
||||||
return;
|
return false;
|
||||||
};
|
};
|
||||||
let touched: Vec<Handle> = constraint.refs.iter().map(|r| r.entity).collect();
|
let touched: Vec<Handle> = constraint.refs.iter().map(|r| r.entity).collect();
|
||||||
let label = "Remove conflicting constraint";
|
|
||||||
|
|
||||||
let constraints_before = set.clone();
|
let constraints_before = set.clone();
|
||||||
let pending = self.begin_undo(i, label, touched.len(), true);
|
let pending = self.begin_undo(i, label, touched.len(), true);
|
||||||
self.tabs[i]
|
self.tabs[i]
|
||||||
@@ -999,6 +997,30 @@ impl OpenCADStudio {
|
|||||||
if let Some(pd) = pending {
|
if let Some(pd) = pending {
|
||||||
self.commit_undo_delta(i, pd);
|
self.commit_undo_delta(i, pd);
|
||||||
}
|
}
|
||||||
|
self.refresh_properties();
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes and re-solves the first conflicting constraint in the active scope.
|
||||||
|
pub(super) fn resolve_one_parametric_conflict(&mut self) {
|
||||||
|
let i = self.active_tab;
|
||||||
|
let scope = self.tabs[i].current_parametric_scope();
|
||||||
|
let Some(id) = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.parametric_constraint_set(scope)
|
||||||
|
.and_then(|set| set.conflicts.first().map(|(id, _)| *id))
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.remove_parametric_constraint(id, "Remove conflicting constraint");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes one user-selected constraint from the active standard graph scope.
|
||||||
|
pub(super) fn delete_parametric_constraint(
|
||||||
|
&mut self,
|
||||||
|
id: crate::scene::parametric_constraints::ConstraintId,
|
||||||
|
) {
|
||||||
|
self.remove_parametric_constraint(id, "Delete constraint");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rebuilds the active tab's `Scene::named_parameters` from the
|
/// Rebuilds the active tab's `Scene::named_parameters` from the
|
||||||
@@ -7867,6 +7889,44 @@ mod parametric_constraint_undo_tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deleting_one_parametric_constraint_is_undoable() {
|
||||||
|
let mut app = OpenCADStudio::new_for_test();
|
||||||
|
let _ = app.automation_op(r#"{"op":"new"}"#);
|
||||||
|
let line = add_line(&mut app, 0.0, 0.0, 10.0, 3.0);
|
||||||
|
let _ = app.apply_cmd_result(CmdResult::AddParametricConstraint {
|
||||||
|
kind: ConstraintKind::Horizontal,
|
||||||
|
refs: vec![ParametricRef::whole(line)],
|
||||||
|
driving_param: None,
|
||||||
|
label: "Horizontal constraint",
|
||||||
|
});
|
||||||
|
let id = app.tabs[app.active_tab]
|
||||||
|
.scene
|
||||||
|
.parametric_constraint_set(ParametricScope::ModelSpace)
|
||||||
|
.unwrap()
|
||||||
|
.constraints[0]
|
||||||
|
.id;
|
||||||
|
|
||||||
|
app.delete_parametric_constraint(id);
|
||||||
|
assert!(app.tabs[app.active_tab]
|
||||||
|
.scene
|
||||||
|
.parametric_constraint_set(ParametricScope::ModelSpace)
|
||||||
|
.unwrap()
|
||||||
|
.constraints
|
||||||
|
.is_empty());
|
||||||
|
|
||||||
|
app.undo_steps(1);
|
||||||
|
assert_eq!(
|
||||||
|
app.tabs[app.active_tab]
|
||||||
|
.scene
|
||||||
|
.parametric_constraint_set(ParametricScope::ModelSpace)
|
||||||
|
.unwrap()
|
||||||
|
.constraints
|
||||||
|
.len(),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// `named_parameters_design.md` stage 4: applying the PARAMETERS editor's
|
/// `named_parameters_design.md` stage 4: applying the PARAMETERS editor's
|
||||||
/// working buffer both defines the parameter and re-solves whatever
|
/// working buffer both defines the parameter and re-solves whatever
|
||||||
/// constraint already references it by name — the entire point of a
|
/// constraint already references it by name — the entire point of a
|
||||||
@@ -7950,6 +8010,31 @@ mod parametric_constraint_undo_tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The no-selection page must expose the active drawing's named parameters.
|
||||||
|
#[test]
|
||||||
|
fn no_selection_properties_exposes_named_parameters() {
|
||||||
|
let mut app = OpenCADStudio::new_for_test();
|
||||||
|
let _ = app.automation_op(r#"{"op":"new"}"#);
|
||||||
|
app.tabs[app.active_tab]
|
||||||
|
.scene
|
||||||
|
.named_parameters_mut()
|
||||||
|
.set("width", "12")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
app.refresh_properties();
|
||||||
|
|
||||||
|
assert!(app.tabs[app.active_tab]
|
||||||
|
.properties
|
||||||
|
.sections
|
||||||
|
.iter()
|
||||||
|
.flat_map(|section| §ion.props)
|
||||||
|
.any(|property| matches!(
|
||||||
|
&property.value,
|
||||||
|
crate::scene::model::object::PropValue::ParamRow { name, .. }
|
||||||
|
if name == "width"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
/// End-to-end through the actual `Message` handlers a Properties-panel
|
/// End-to-end through the actual `Message` handlers a Properties-panel
|
||||||
/// click/keystroke would fire — not `apply_named_parameter_editor_rows`
|
/// click/keystroke would fire — not `apply_named_parameter_editor_rows`
|
||||||
/// directly — covering the whole Parameters-section lifecycle: add,
|
/// directly — covering the whole Parameters-section lifecycle: add,
|
||||||
|
|||||||
@@ -2902,6 +2902,8 @@ pub enum Message {
|
|||||||
/// A Constraints-section row was clicked: select every entity in the
|
/// A Constraints-section row was clicked: select every entity in the
|
||||||
/// list (replacing the current selection) in the viewport.
|
/// list (replacing the current selection) in the viewport.
|
||||||
PropConstraintLinkClick(Vec<acadrust::Handle>),
|
PropConstraintLinkClick(Vec<acadrust::Handle>),
|
||||||
|
/// Remove one parametric constraint from the active standard graph scope.
|
||||||
|
PropConstraintDelete(crate::scene::parametric_constraints::ConstraintId),
|
||||||
// ── About window ────────────────────────────────────────────────────
|
// ── About window ────────────────────────────────────────────────────
|
||||||
AboutOpen,
|
AboutOpen,
|
||||||
// ── Graphics warning ────────────────────────────────────────────────
|
// ── Graphics warning ────────────────────────────────────────────────
|
||||||
|
|||||||
+36
-1
@@ -484,6 +484,35 @@ impl OpenCADStudio {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
PropSection {
|
||||||
|
title: t!("Parameters").into_owned(),
|
||||||
|
props: {
|
||||||
|
let mut props: Vec<Property> = scene
|
||||||
|
.named_parameters()
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, parameter)| Property {
|
||||||
|
label: String::new(),
|
||||||
|
field: "named_parameter",
|
||||||
|
value: PropValue::ParamRow {
|
||||||
|
index,
|
||||||
|
name: parameter.name.clone(),
|
||||||
|
formula: parameter.source.clone(),
|
||||||
|
resolved: scene
|
||||||
|
.named_parameters()
|
||||||
|
.resolve(¶meter.name)
|
||||||
|
.map_err(|error| error.to_string()),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
props.push(Property {
|
||||||
|
label: String::new(),
|
||||||
|
field: "named_parameter_add",
|
||||||
|
value: PropValue::ParamAddRow,
|
||||||
|
});
|
||||||
|
props
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
ui::PropertiesPanel {
|
ui::PropertiesPanel {
|
||||||
title: t!("No selection").into_owned(),
|
title: t!("No selection").into_owned(),
|
||||||
@@ -2202,6 +2231,11 @@ impl OpenCADStudio {
|
|||||||
let scope = self.tabs[i].current_parametric_scope();
|
let scope = self.tabs[i].current_parametric_scope();
|
||||||
let scene = &self.tabs[i].scene;
|
let scene = &self.tabs[i].scene;
|
||||||
if let Some(set) = scene.parametric_constraint_set(scope) {
|
if let Some(set) = scene.parametric_constraint_set(scope) {
|
||||||
|
let parameters = if set.local_parameters.is_empty() {
|
||||||
|
scene.named_parameters()
|
||||||
|
} else {
|
||||||
|
&set.local_parameters
|
||||||
|
};
|
||||||
let props: Vec<crate::scene::model::object::Property> = set
|
let props: Vec<crate::scene::model::object::Property> = set
|
||||||
.constraints_touching(handle)
|
.constraints_touching(handle)
|
||||||
.map(|c| {
|
.map(|c| {
|
||||||
@@ -2219,7 +2253,7 @@ impl OpenCADStudio {
|
|||||||
crate::scene::named_parameters::DrivingValue::Named(
|
crate::scene::named_parameters::DrivingValue::Named(
|
||||||
name,
|
name,
|
||||||
),
|
),
|
||||||
) => match scene.named_parameters().resolve(name) {
|
) => match parameters.resolve(name) {
|
||||||
Ok(v) => format!(" = {name} ({v:.2})"),
|
Ok(v) => format!(" = {name} ({v:.2})"),
|
||||||
Err(_) => format!(" = {name} (?)"),
|
Err(_) => format!(" = {name} (?)"),
|
||||||
},
|
},
|
||||||
@@ -2239,6 +2273,7 @@ impl OpenCADStudio {
|
|||||||
label,
|
label,
|
||||||
field: "parametric_constraint",
|
field: "parametric_constraint",
|
||||||
value: crate::scene::model::object::PropValue::EntityLink {
|
value: crate::scene::model::object::PropValue::EntityLink {
|
||||||
|
id: c.id,
|
||||||
handles,
|
handles,
|
||||||
conflicting,
|
conflicting,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6250,6 +6250,10 @@ impl OpenCADStudio {
|
|||||||
Message::PropParamCommit { index, field } => self.on_prop_param_commit(index, field),
|
Message::PropParamCommit { index, field } => self.on_prop_param_commit(index, field),
|
||||||
Message::PropParamDelete(index) => self.on_prop_param_delete(index),
|
Message::PropParamDelete(index) => self.on_prop_param_delete(index),
|
||||||
Message::PropParamAddNew => self.on_prop_param_add_new(),
|
Message::PropParamAddNew => self.on_prop_param_add_new(),
|
||||||
|
Message::PropConstraintDelete(id) => {
|
||||||
|
self.delete_parametric_constraint(id);
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
Message::PropConstraintLinkClick(handles) => {
|
Message::PropConstraintLinkClick(handles) => {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
self.tabs[i].scene.deselect_all();
|
self.tabs[i].scene.deselect_all();
|
||||||
|
|||||||
@@ -331,6 +331,12 @@ impl CadCommand for DistanceConstraintCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
||||||
|
// An existing parameter name wins over a mode keyword with the same spelling.
|
||||||
|
if let Some(value @ DrivingValue::Named(_)) =
|
||||||
|
parse_driving_value(text, &self.known_param_names)
|
||||||
|
{
|
||||||
|
return self.build(value);
|
||||||
|
}
|
||||||
// Mode-switch keywords re-prompt instead of building — consumed
|
// Mode-switch keywords re-prompt instead of building — consumed
|
||||||
// inputs return `Some(NeedPoint)` so the driver doesn't re-offer
|
// inputs return `Some(NeedPoint)` so the driver doesn't re-offer
|
||||||
// the same token a second time (matches e.g. `trim.rs`'s
|
// the same token a second time (matches e.g. `trim.rs`'s
|
||||||
@@ -605,6 +611,24 @@ mod tests {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_parameter_named_like_a_mode_keyword_remains_usable() {
|
||||||
|
let mut scene = Scene::new();
|
||||||
|
let circle = add_circle(&mut scene);
|
||||||
|
scene.named_parameters_mut().set("r", "5").unwrap();
|
||||||
|
let mut cmd =
|
||||||
|
DistanceConstraintCommand::new(&scene, circle).expect("Circle supports DCONSTRAINT");
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
cmd.on_text_input("R"),
|
||||||
|
Some(CmdResult::AddParametricConstraint {
|
||||||
|
kind: ConstraintKind::Radius,
|
||||||
|
driving_param: Some(DrivingValue::Named(name)),
|
||||||
|
..
|
||||||
|
}) if name == "r"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ordinary_distances_reject_nonpositive_and_nonfinite_values() {
|
fn ordinary_distances_reject_nonpositive_and_nonfinite_values() {
|
||||||
let mut scene = Scene::new();
|
let mut scene = Scene::new();
|
||||||
|
|||||||
@@ -62,8 +62,10 @@ pub enum PropValue {
|
|||||||
/// row for one parametric constraint. Clicking it selects every
|
/// row for one parametric constraint. Clicking it selects every
|
||||||
/// entity in `handles` in the viewport. `conflicting` mirrors
|
/// entity in `handles` in the viewport. `conflicting` mirrors
|
||||||
/// `ParametricConstraintSet::conflicts`, tinting the row the same danger
|
/// `ParametricConstraintSet::conflicts`, tinting the row the same danger
|
||||||
/// color the constraint's glyph pill already uses.
|
/// color the constraint's glyph pill already uses. `id` routes the row's
|
||||||
|
/// delete action back to that constraint in the active scope.
|
||||||
EntityLink {
|
EntityLink {
|
||||||
|
id: crate::scene::parametric_constraints::ConstraintId,
|
||||||
handles: Vec<Handle>,
|
handles: Vec<Handle>,
|
||||||
conflicting: bool,
|
conflicting: bool,
|
||||||
},
|
},
|
||||||
|
|||||||
+16
-5
@@ -865,8 +865,8 @@ impl PropertiesPanel {
|
|||||||
self.render_hatch_pattern_row(label, current)
|
self.render_hatch_pattern_row(label, current)
|
||||||
}
|
}
|
||||||
PropValue::AttrText { tag, value } => self.render_attr_row(tag, value),
|
PropValue::AttrText { tag, value } => self.render_attr_row(tag, value),
|
||||||
PropValue::EntityLink { handles, conflicting } => {
|
PropValue::EntityLink { id, handles, conflicting } => {
|
||||||
render_entity_link_row(label, handles.clone(), *conflicting)
|
render_entity_link_row(label, *id, handles.clone(), *conflicting)
|
||||||
}
|
}
|
||||||
PropValue::ParamRow { index, name, formula, resolved } => {
|
PropValue::ParamRow { index, name, formula, resolved } => {
|
||||||
self.render_param_row(*index, name, formula, resolved)
|
self.render_param_row(*index, name, formula, resolved)
|
||||||
@@ -1933,8 +1933,13 @@ fn render_ro_with_tooltip_row<'a>(
|
|||||||
/// field, so this doesn't use the usual `prop_row_widget` label|value split.
|
/// field, so this doesn't use the usual `prop_row_widget` label|value split.
|
||||||
/// Clicking it selects every entity in `handles`; a conflicting/redundant
|
/// Clicking it selects every entity in `handles`; a conflicting/redundant
|
||||||
/// constraint (mirrors the viewport glyph pill's own color cue) tints red.
|
/// constraint (mirrors the viewport glyph pill's own color cue) tints red.
|
||||||
fn render_entity_link_row<'a>(label: &'a str, handles: Vec<Handle>, conflicting: bool) -> Element<'a, Message> {
|
fn render_entity_link_row<'a>(
|
||||||
let btn = button(text(label).size(FONT_SZ).width(Length::Fill))
|
label: &'a str,
|
||||||
|
id: crate::scene::parametric_constraints::ConstraintId,
|
||||||
|
handles: Vec<Handle>,
|
||||||
|
conflicting: bool,
|
||||||
|
) -> Element<'a, Message> {
|
||||||
|
let link = button(text(label).size(FONT_SZ).width(Length::Fill))
|
||||||
.on_press(Message::PropConstraintLinkClick(handles))
|
.on_press(Message::PropConstraintLinkClick(handles))
|
||||||
.style(move |theme: &Theme, status| {
|
.style(move |theme: &Theme, status| {
|
||||||
let palette = theme.palette();
|
let palette = theme.palette();
|
||||||
@@ -1955,7 +1960,13 @@ fn render_entity_link_row<'a>(label: &'a str, handles: Vec<Handle>, conflicting:
|
|||||||
})
|
})
|
||||||
.padding([3, 8])
|
.padding([3, 8])
|
||||||
.width(Length::Fill);
|
.width(Length::Fill);
|
||||||
container(btn).width(Length::Fill).into()
|
let delete = button(text("\u{2715}").size(FONT_SZ))
|
||||||
|
.on_press(Message::PropConstraintDelete(id))
|
||||||
|
.style(button::text)
|
||||||
|
.padding([2, 6]);
|
||||||
|
container(row![link, delete].spacing(2).align_y(iced::Center))
|
||||||
|
.width(Length::Fill)
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Parameters section: "+ Add parameter" row ──────────────────────────────
|
// ── Parameters section: "+ Add parameter" row ──────────────────────────────
|
||||||
|
|||||||
Reference in new issue
Block a user