forked from 1b-agent/opencadstudio-upstream
Add Properties-panel Constraints/Parameters sections, a constraint-pill
value toggle, and redesigned constraint-ribbon icons Properties panel: - New "Parameters" section (shown with no selection) listing every named parameter with inline name/formula editing, add, and delete, backed by new PropValue::ParamRow/ParamAddRow and command_driver handlers (resolve_named_parameter_edit, on_prop_param_commit/_delete/_add_new). A fresh parameter's formula now defaults to "1", not "0" — a 0-length seed value is a genuine solver singularity for a Distance constraint driven by that parameter, with no defined direction to grow back out of. - New "Constraints" section (shown for a single selected entity) listing every constraint touching it as a clickable PropValue::EntityLink that selects the linked entities in the viewport, danger-tinted when the constraint conflicts. Constraint pills: - New Options > General toggle, "Show values and parameter names on constraint markers" (on by default, persisted). Off, every pill shows just its bare glyph instead of glyph+driven-value/parameter-name — the glyph alone is enough to see a constraint is present, without the value text covering nearby geometry on a dense sketch. Constraint-ribbon icons: - Replaced all 18 unicode/letter glyph icons (including the plain "F", "L", "M", "S" letters) with proper SVGs under assets/icons/constrain/, matching the Modify section's two-color style (#B4B6B9 white / #6DB7ED blue, consistent stroke width). Tangent reuses the Draw section's white-arc-plus-blue-dots motif. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
1 parent
faf3473b2b
commit
bd9a2d08f7
36 files changed
+707
-26
No files matched your search
@@ -1051,6 +1051,147 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
|
||||
/// Re-solves every entity touched by an enabled constraint driven by
|
||||
/// `name` — the targeted, single-row counterpart to
|
||||
/// `apply_named_parameter_editor_rows`'s "re-solve what it affects": an
|
||||
/// inline Properties-panel edit changes one parameter, not the whole
|
||||
/// table, so unlike that whole-table Apply this can cheaply scope the
|
||||
/// re-solve to just the entities that actually reference it.
|
||||
fn resolve_named_parameter_edit(&mut self, i: usize, name: &str) {
|
||||
let touched: Vec<Handle> = self.tabs[i]
|
||||
.scene
|
||||
.sketch_constraints
|
||||
.iter()
|
||||
.flat_map(|set| set.constraints.iter())
|
||||
.filter(|c| {
|
||||
c.enabled
|
||||
&& matches!(&c.driving_param, Some(crate::scene::named_parameters::DrivingValue::Named(n)) if n == name)
|
||||
})
|
||||
.flat_map(|c| c.refs.iter().map(|r| r.entity))
|
||||
.collect();
|
||||
self.tabs[i].dirty = true;
|
||||
if touched.is_empty() {
|
||||
return;
|
||||
}
|
||||
let pending = self.begin_undo(i, "Edit named parameter", touched.len(), true);
|
||||
let changes: Vec<(Handle, crate::scene::ChangeKind)> =
|
||||
touched.into_iter().map(|h| (h, crate::scene::ChangeKind::Modified)).collect();
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
if let Some(pd) = pending {
|
||||
self.commit_undo_delta(i, pd);
|
||||
}
|
||||
}
|
||||
|
||||
/// Commits one field of one Parameters-section row (Properties panel) —
|
||||
/// the per-row, commit-on-submit counterpart to
|
||||
/// `apply_named_parameter_editor_rows`'s whole-table Apply. A Formula
|
||||
/// edit is a plain redefine (`ParameterTable::set` on the existing
|
||||
/// name); a Name edit is a remove-then-set (there's no rename primitive
|
||||
/// — `ParameterTable` upserts by name), restoring the old name if the
|
||||
/// new one fails to validate so nothing is silently lost. Either way
|
||||
/// this only re-solves what actually depends on this one parameter —
|
||||
/// no "duplicate name" scan across the whole table is needed the way
|
||||
/// the buffered modal Apply needs one, since every row here is always
|
||||
/// already a real, distinct table entry.
|
||||
pub(super) fn on_prop_param_commit(
|
||||
&mut self,
|
||||
index: usize,
|
||||
field: crate::ui::window::named_parameters::ParamField,
|
||||
) -> Task<Message> {
|
||||
use crate::ui::properties::FieldKey;
|
||||
use crate::ui::window::named_parameters::ParamField;
|
||||
let i = self.active_tab;
|
||||
let key = FieldKey::Param(index, field);
|
||||
self.tabs[i].properties.active_field = None;
|
||||
let Some(typed) = self.tabs[i].properties.edit_buf.remove(&key) else {
|
||||
return Task::none();
|
||||
};
|
||||
let typed = typed.trim().to_string();
|
||||
let Some(current) = self.tabs[i].scene.named_parameters().iter().nth(index).cloned() else {
|
||||
self.refresh_properties();
|
||||
return Task::none();
|
||||
};
|
||||
|
||||
let (result, resolve_name) = match field {
|
||||
ParamField::Formula => {
|
||||
if typed.is_empty() || typed == current.source {
|
||||
self.refresh_properties();
|
||||
return Task::none();
|
||||
}
|
||||
(self.tabs[i].scene.named_parameters_mut().set(¤t.name, &typed), current.name.clone())
|
||||
}
|
||||
ParamField::Name => {
|
||||
if typed.is_empty() || typed == current.name {
|
||||
self.refresh_properties();
|
||||
return Task::none();
|
||||
}
|
||||
if self.tabs[i].scene.named_parameters().contains(&typed) {
|
||||
self.command_line.push_error(crate::tf!("Parameter '{}' already exists.", typed).as_ref());
|
||||
self.refresh_properties();
|
||||
return Task::none();
|
||||
}
|
||||
self.tabs[i].scene.named_parameters_mut().remove(¤t.name);
|
||||
let outcome = self.tabs[i].scene.named_parameters_mut().set(&typed, ¤t.source);
|
||||
if outcome.is_err() {
|
||||
let _ = self.tabs[i].scene.named_parameters_mut().set(¤t.name, ¤t.source);
|
||||
}
|
||||
(outcome, typed.clone())
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = result {
|
||||
self.command_line.push_error(crate::tf!("Parameter '{}': {}", current.name, e).as_ref());
|
||||
self.refresh_properties();
|
||||
return Task::none();
|
||||
}
|
||||
self.resolve_named_parameter_edit(i, &resolve_name);
|
||||
self.refresh_properties();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Removes Parameters-section row `index` immediately — no confirmation,
|
||||
/// matching AutoCAD's own Parameters Manager delete button. Any
|
||||
/// constraint that referenced it gets the same "undefined reference"
|
||||
/// resolve-failure treatment a formula's own bad reference already gets
|
||||
/// (`build_constraint`) — re-solving surfaces that rather than needing
|
||||
/// special-case handling here.
|
||||
pub(super) fn on_prop_param_delete(&mut self, index: usize) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
let Some(name) = self.tabs[i].scene.named_parameters().iter().nth(index).map(|p| p.name.clone()) else {
|
||||
return Task::none();
|
||||
};
|
||||
self.tabs[i].scene.named_parameters_mut().remove(&name);
|
||||
self.resolve_named_parameter_edit(i, &name);
|
||||
self.refresh_properties();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// The Parameters section's "+ Add parameter" row: appends a fresh,
|
||||
/// uniquely-named parameter (`param1`, `param2`, …) the user then
|
||||
/// renames/redefines inline via `on_prop_param_commit`. Defaults to `1`,
|
||||
/// not `0`: renaming this onto a name an existing Distance/Radius
|
||||
/// constraint already references (unusual, but reachable — define
|
||||
/// first, wire up the constraint's reference later) would otherwise
|
||||
/// briefly collapse that geometry to a zero-length/zero-radius
|
||||
/// degenerate state, which is a genuine numerical singularity for a
|
||||
/// point-to-point distance constraint to grow back out of (no defined
|
||||
/// direction to separate two exactly-coincident points from). `1` never
|
||||
/// hits that.
|
||||
pub(super) fn on_prop_param_add_new(&mut self) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
let mut n = 1usize;
|
||||
let name = loop {
|
||||
let candidate = format!("param{n}");
|
||||
if !self.tabs[i].scene.named_parameters().contains(&candidate) {
|
||||
break candidate;
|
||||
}
|
||||
n += 1;
|
||||
};
|
||||
let _ = self.tabs[i].scene.named_parameters_mut().set(&name, "1");
|
||||
self.refresh_properties();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn apply_cmd_result_inner(&mut self, result: CmdResult) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
let preserve_commit_layer = self.tabs[i]
|
||||
@@ -6370,6 +6511,80 @@ mod sketch_constraint_undo_tests {
|
||||
assert!((len - 3.0).abs() < 1e-6, "length should track the redefined parameter, got {len}");
|
||||
}
|
||||
|
||||
/// End-to-end through the actual `Message` handlers a Properties-panel
|
||||
/// click/keystroke would fire — not `apply_named_parameter_editor_rows`
|
||||
/// directly — covering the whole Parameters-section lifecycle: add,
|
||||
/// rename, redefine, and confirm a referencing constraint re-solves
|
||||
/// after each commit (not just after a whole-table Apply, since this
|
||||
/// path commits per field).
|
||||
#[test]
|
||||
fn properties_panel_parameter_row_add_rename_redefine_and_delete() {
|
||||
use crate::ui::window::named_parameters::ParamField;
|
||||
|
||||
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, 0.0);
|
||||
let _ = app.apply_cmd_result(CmdResult::AddSketchConstraint {
|
||||
kind: ConstraintKind::Distance,
|
||||
refs: vec![SketchRef::point(line, 0), SketchRef::point(line, 1)],
|
||||
driving_param: Some(crate::scene::named_parameters::DrivingValue::Named("len".to_string())),
|
||||
label: "Distance constraint",
|
||||
});
|
||||
|
||||
// Add: the first row is auto-named "param1".
|
||||
let _ = app.update(Message::PropParamAddNew);
|
||||
assert!(app.tabs[app.active_tab].scene.named_parameters().contains("param1"));
|
||||
let index = app.tabs[app.active_tab].scene.named_parameters().iter().position(|p| p.name == "param1").unwrap();
|
||||
|
||||
// Rename param1 -> len (commit-on-submit, not per keystroke: input
|
||||
// alone must not touch the table yet).
|
||||
let _ = app.update(Message::PropParamInput { index, field: ParamField::Name, value: "len".to_string() });
|
||||
assert!(app.tabs[app.active_tab].scene.named_parameters().contains("param1"), "typing alone must not commit");
|
||||
let _ = app.update(Message::PropParamCommit { index, field: ParamField::Name });
|
||||
assert!(!app.tabs[app.active_tab].scene.named_parameters().contains("param1"));
|
||||
assert!(app.tabs[app.active_tab].scene.named_parameters().contains("len"));
|
||||
|
||||
// Redefine its formula to 8 and confirm the Distance constraint
|
||||
// (already referencing "len" by name, defined before this) re-solves.
|
||||
let _ = app.update(Message::PropParamInput { index, field: ParamField::Formula, value: "8".to_string() });
|
||||
let _ = app.update(Message::PropParamCommit { index, field: ParamField::Formula });
|
||||
assert_eq!(app.tabs[app.active_tab].scene.named_parameters().resolve("len"), Ok(8.0));
|
||||
let (start, end) = match app.tabs[app.active_tab].scene.document.get_entity(line) {
|
||||
Some(acadrust::EntityType::Line(l)) => (l.start, l.end),
|
||||
other => panic!("expected a Line, got {other:?}"),
|
||||
};
|
||||
let len = ((end.x - start.x).powi(2) + (end.y - start.y).powi(2)).sqrt();
|
||||
assert!((len - 8.0).abs() < 1e-6, "length should track the redefined parameter, got {len}");
|
||||
|
||||
// Delete: the row is gone from the table.
|
||||
let _ = app.update(Message::PropParamDelete(index));
|
||||
assert!(!app.tabs[app.active_tab].scene.named_parameters().contains("len"));
|
||||
}
|
||||
|
||||
/// A Constraints-section row click selects every entity the constraint
|
||||
/// references, replacing whatever was selected before.
|
||||
#[test]
|
||||
fn properties_panel_constraint_link_click_selects_its_entities() {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
let _ = app.automation_op(r#"{"op":"new"}"#);
|
||||
let a = add_line(&mut app, 0.0, 0.0, 10.0, 0.0);
|
||||
let b = add_line(&mut app, 0.0, 5.0, 10.0, 5.0);
|
||||
let unrelated = add_line(&mut app, 20.0, 20.0, 30.0, 20.0);
|
||||
let _ = app.apply_cmd_result(CmdResult::AddSketchConstraint {
|
||||
kind: ConstraintKind::Parallel,
|
||||
refs: vec![SketchRef::whole(a), SketchRef::whole(b)],
|
||||
driving_param: None,
|
||||
label: "Parallel constraint",
|
||||
});
|
||||
app.tabs[app.active_tab].scene.select_entity(unrelated, true);
|
||||
|
||||
let _ = app.update(Message::PropConstraintLinkClick(vec![a, b]));
|
||||
|
||||
let selected: std::collections::HashSet<Handle> =
|
||||
app.tabs[app.active_tab].scene.selected_entities().into_iter().map(|(h, _)| h).collect();
|
||||
assert_eq!(selected, std::collections::HashSet::from([a, b]));
|
||||
}
|
||||
|
||||
/// A row that fails to validate (here, a formula that doesn't parse)
|
||||
/// must not silently vanish or corrupt the table: it's reported as an
|
||||
/// error and simply isn't added, while a valid row alongside it still
|
||||
|
||||
@@ -557,6 +557,13 @@ pub(super) struct OpenCADStudio {
|
||||
/// this only governs whether the graph is *created* in a scope that
|
||||
/// doesn't have one yet.
|
||||
pub write_dwg_native_constraints: bool,
|
||||
/// When true (default), a sketch constraint's viewport pill shows its
|
||||
/// glyph plus a driven value or named-parameter name (e.g. "⌀ 5.000" or
|
||||
/// "⌀ hole_dia"). When false, every pill shows just the bare glyph — the
|
||||
/// value/name text can cover important canvas detail on a dense sketch,
|
||||
/// and the glyph alone is already enough to see *that* a constraint is
|
||||
/// present (`docs/parametric_system_design.md` §6.3/§7).
|
||||
pub show_constraint_values: bool,
|
||||
/// Minutes between autosaves to a `.sv$` recovery file (SAVETIME command);
|
||||
/// 0 disables autosave.
|
||||
pub savetime_min: i32,
|
||||
@@ -2046,6 +2053,9 @@ pub enum Message {
|
||||
/// Toggle writing AutoCAD-native constraint objects on save, from
|
||||
/// Options. See `write_dwg_native_constraints`'s doc comment.
|
||||
WriteDwgNativeConstraintsChanged(bool),
|
||||
/// Toggle showing driven values/named-parameter names on constraint
|
||||
/// pills, from Options. See `show_constraint_values`'s doc comment.
|
||||
ShowConstraintValuesChanged(bool),
|
||||
/// Switch the interface language and redraw localized views.
|
||||
LanguageChanged(crate::i18n::Language),
|
||||
/// Drop every entity from the active drawing.
|
||||
@@ -2790,6 +2800,31 @@ pub enum Message {
|
||||
/// and re-solve every constraint that reads a named parameter; stays
|
||||
/// open.
|
||||
NamedParametersApply,
|
||||
// ── Parameters / Constraints sections embedded in the Properties panel
|
||||
// (AutoCAD-style: a live section instead of the modal above, per-row
|
||||
// commit-on-submit instead of a whole-table Apply) ─────────────────────
|
||||
/// Live text of one column of parameter row `index`, keyed by its
|
||||
/// `ParameterTable::iter()` position — see `PropValue::ParamRow`.
|
||||
PropParamInput {
|
||||
index: usize,
|
||||
field: crate::ui::window::named_parameters::ParamField,
|
||||
value: String,
|
||||
},
|
||||
/// Commit row `index`'s buffered edit for `field` to `Scene::
|
||||
/// named_parameters` (Enter / losing focus) and re-solve whatever it
|
||||
/// drives.
|
||||
PropParamCommit {
|
||||
index: usize,
|
||||
field: crate::ui::window::named_parameters::ParamField,
|
||||
},
|
||||
/// Remove parameter row `index` immediately (no confirmation, matching
|
||||
/// AutoCAD's own Parameters Manager delete button).
|
||||
PropParamDelete(usize),
|
||||
/// Append a fresh, uniquely-named parameter to `Scene::named_parameters`.
|
||||
PropParamAddNew,
|
||||
/// A Constraints-section row was clicked: select every entity in the
|
||||
/// list (replacing the current selection) in the viewport.
|
||||
PropConstraintLinkClick(Vec<acadrust::Handle>),
|
||||
// ── About window ────────────────────────────────────────────────────
|
||||
AboutOpen,
|
||||
/// Close whatever in-canvas modal dialog is open (Plan B).
|
||||
@@ -3479,6 +3514,7 @@ impl OpenCADStudio {
|
||||
backup_on_save: true,
|
||||
file_assoc_enabled: true,
|
||||
write_dwg_native_constraints: false,
|
||||
show_constraint_values: true,
|
||||
savetime_min: 10,
|
||||
default_bg_color: None,
|
||||
default_paper_bg_color: None,
|
||||
|
||||
@@ -406,6 +406,38 @@ impl OpenCADStudio {
|
||||
read_only(t!("Visual Style").as_ref(), tab.visual_style.clone()),
|
||||
],
|
||||
},
|
||||
// Document-wide named-parameter table (AutoCAD
|
||||
// Parameters Manager equivalent), embedded here
|
||||
// instead of a separate modal — see `PropValue::
|
||||
// ParamRow`'s doc comment. Belongs on the no-
|
||||
// selection (drawing-level) page, not a per-entity
|
||||
// one: a parameter isn't owned by any one entity.
|
||||
PropSection {
|
||||
title: t!("Parameters").into_owned(),
|
||||
props: {
|
||||
let mut props: Vec<Property> = scene
|
||||
.named_parameters()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, p)| Property {
|
||||
label: String::new(),
|
||||
field: "named_parameter",
|
||||
value: PropValue::ParamRow {
|
||||
index,
|
||||
name: p.name.clone(),
|
||||
formula: p.source.clone(),
|
||||
resolved: scene.named_parameters().resolve(&p.name).map_err(|e| e.to_string()),
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
props.push(Property {
|
||||
label: String::new(),
|
||||
field: "named_parameter_add",
|
||||
value: PropValue::ParamAddRow,
|
||||
});
|
||||
props
|
||||
},
|
||||
},
|
||||
];
|
||||
ui::PropertiesPanel {
|
||||
title: t!("No selection").into_owned(),
|
||||
@@ -2126,6 +2158,53 @@ impl OpenCADStudio {
|
||||
if compact_solid {
|
||||
retain_compact_solid_sections(&mut sections);
|
||||
}
|
||||
// Persistent sketch-constraint list for this entity —
|
||||
// the Properties-panel analogue of the viewport's
|
||||
// constraint glyph pills: one row per constraint
|
||||
// touching `handle`, clickable to select every entity
|
||||
// it relates (PropConstraintLinkClick). Omitted
|
||||
// entirely when the entity has none — an unconstrained
|
||||
// entity (the common case) shouldn't gain empty
|
||||
// bookkeeping.
|
||||
{
|
||||
let scope = self.tabs[i].current_sketch_scope();
|
||||
let scene = &self.tabs[i].scene;
|
||||
if let Some(set) = scene.sketch_constraint_set(scope) {
|
||||
let props: Vec<crate::scene::model::object::Property> = set
|
||||
.constraints_touching(handle)
|
||||
.map(|c| {
|
||||
let conflicting = set.conflicts.iter().any(|(id, _)| *id == c.id);
|
||||
let value_suffix = match &c.driving_param {
|
||||
Some(crate::scene::named_parameters::DrivingValue::Literal(v)) => {
|
||||
format!(" = {v:.2}")
|
||||
}
|
||||
Some(crate::scene::named_parameters::DrivingValue::Named(name)) => {
|
||||
match scene.named_parameters().resolve(name) {
|
||||
Ok(v) => format!(" = {name} ({v:.2})"),
|
||||
Err(_) => format!(" = {name} (?)"),
|
||||
}
|
||||
}
|
||||
None => String::new(),
|
||||
};
|
||||
let label = format!("{} {:?}{}", c.kind.glyph_symbol(), c.kind, value_suffix);
|
||||
let mut handles: Vec<acadrust::Handle> = c.refs.iter().map(|r| r.entity).collect();
|
||||
handles.sort_by_key(|h| h.value());
|
||||
handles.dedup();
|
||||
crate::scene::model::object::Property {
|
||||
label,
|
||||
field: "sketch_constraint",
|
||||
value: crate::scene::model::object::PropValue::EntityLink { handles, conflicting },
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
if !props.is_empty() {
|
||||
sections.push(crate::scene::model::object::PropSection {
|
||||
title: t!("Constraints").into_owned(),
|
||||
props,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
let title = match entity {
|
||||
acadrust::EntityType::Insert(ins) => {
|
||||
let is_xref = self.tabs[i]
|
||||
@@ -3102,6 +3181,15 @@ fn make_sections_read_only(
|
||||
}
|
||||
PropValue::Stepper { display, .. } => display.clone(),
|
||||
PropValue::AttrText { value, .. } => value.clone(),
|
||||
// The Constraints/Parameters sections aren't per-entity editable
|
||||
// fields to begin with (a constraint link is a navigation
|
||||
// action, not a value; a parameter belongs to the whole
|
||||
// document, not the locked entity) — locking the entity doesn't
|
||||
// change either, so just carry through readable text instead of
|
||||
// collapsing them to a value that was never editable anyway.
|
||||
PropValue::EntityLink { handles, .. } => format!("{} entity link(s)", handles.len()),
|
||||
PropValue::ParamRow { name, formula, .. } => format!("{name} = {formula}"),
|
||||
PropValue::ParamAddRow => String::new(),
|
||||
};
|
||||
property.field = "locked_read_only";
|
||||
property.value = PropValue::ReadOnly(text);
|
||||
|
||||
@@ -244,6 +244,12 @@ pub struct UserSettings {
|
||||
/// graph keeps it synced regardless of this setting).
|
||||
#[serde(default)]
|
||||
pub write_dwg_native_constraints: bool,
|
||||
/// When true (default), a sketch constraint's viewport pill shows its
|
||||
/// glyph plus a driven value or named-parameter name. When false, every
|
||||
/// pill shows just the bare glyph, so the value/name text doesn't cover
|
||||
/// canvas detail on a dense sketch.
|
||||
#[serde(default = "default_show_constraint_values")]
|
||||
pub show_constraint_values: bool,
|
||||
/// Minutes between autosaves to a `.sv$` recovery file (SAVETIME command).
|
||||
/// 0 disables autosave.
|
||||
pub savetime_min: i32,
|
||||
@@ -312,6 +318,10 @@ fn default_dimension_continue_mode() -> i16 {
|
||||
1
|
||||
}
|
||||
|
||||
fn default_show_constraint_values() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn deserialize_clipromptlines<'de, D>(de: D) -> Result<i32, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
@@ -361,6 +371,7 @@ impl Default for UserSettings {
|
||||
backup_on_save: true,
|
||||
file_assoc_enabled: true,
|
||||
write_dwg_native_constraints: false,
|
||||
show_constraint_values: true,
|
||||
savetime_min: 10,
|
||||
default_save_format: crate::io::DEFAULT_SAVE_FORMAT.to_string(),
|
||||
pick_add: true,
|
||||
|
||||
@@ -397,6 +397,7 @@ impl OpenCADStudio {
|
||||
backup_on_save: self.backup_on_save,
|
||||
file_assoc_enabled: self.file_assoc_enabled,
|
||||
write_dwg_native_constraints: self.write_dwg_native_constraints,
|
||||
show_constraint_values: self.show_constraint_values,
|
||||
savetime_min: self.savetime_min,
|
||||
default_save_format: self.default_save_format.clone(),
|
||||
pick_add: self.pick_add,
|
||||
@@ -467,6 +468,7 @@ impl OpenCADStudio {
|
||||
self.backup_on_save = s.backup_on_save;
|
||||
self.file_assoc_enabled = s.file_assoc_enabled;
|
||||
self.write_dwg_native_constraints = s.write_dwg_native_constraints;
|
||||
self.show_constraint_values = s.show_constraint_values;
|
||||
self.savetime_min = s.savetime_min;
|
||||
self.default_save_format =
|
||||
crate::io::canonical_save_format(&s.default_save_format).to_string();
|
||||
|
||||
@@ -5949,6 +5949,28 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
// ── Parameters / Constraints sections embedded in the
|
||||
// Properties panel ──────────────────────────────────────────────
|
||||
Message::PropParamInput { index, field, value } => {
|
||||
self.tabs[self.active_tab]
|
||||
.properties
|
||||
.edit_buf
|
||||
.insert(crate::ui::properties::FieldKey::Param(index, field), value);
|
||||
Task::none()
|
||||
}
|
||||
Message::PropParamCommit { index, field } => self.on_prop_param_commit(index, field),
|
||||
Message::PropParamDelete(index) => self.on_prop_param_delete(index),
|
||||
Message::PropParamAddNew => self.on_prop_param_add_new(),
|
||||
Message::PropConstraintLinkClick(handles) => {
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.deselect_all();
|
||||
for h in handles {
|
||||
self.tabs[i].scene.select_entity(h, false);
|
||||
}
|
||||
self.refresh_properties();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
// ── Options / About windows ───────────────────────────────────
|
||||
Message::OptionsOpen => {
|
||||
self.active_modal = Some(super::ModalKind::Options);
|
||||
@@ -6312,6 +6334,11 @@ impl OpenCADStudio {
|
||||
self.persist_settings_if_changed();
|
||||
Task::none()
|
||||
}
|
||||
Message::ShowConstraintValuesChanged(enabled) => {
|
||||
self.show_constraint_values = enabled;
|
||||
self.persist_settings_if_changed();
|
||||
Task::none()
|
||||
}
|
||||
Message::LanguageChanged(language) => {
|
||||
if self.language == language {
|
||||
return Task::none();
|
||||
|
||||
+6
-4
@@ -839,10 +839,12 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
)?;
|
||||
let point = iced::Point::new(bounds.x + screen.x, bounds.y + screen.y);
|
||||
let is_conflicting = set.conflicts.iter().any(|(id, _)| *id == c.id);
|
||||
point
|
||||
.x
|
||||
.is_finite()
|
||||
.then(|| (point, crate::scene::sketch_constraints::glyph_label(c), is_conflicting))
|
||||
let label = if self.show_constraint_values {
|
||||
crate::scene::sketch_constraints::glyph_label(c)
|
||||
} else {
|
||||
c.kind.glyph_symbol().to_string()
|
||||
};
|
||||
point.x.is_finite().then(|| (point, label, is_conflicting))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -198,6 +198,7 @@ impl OpenCADStudio {
|
||||
&self.default_save_format,
|
||||
self.file_assoc_enabled,
|
||||
self.write_dwg_native_constraints,
|
||||
self.show_constraint_values,
|
||||
&self.ui_theme,
|
||||
&self.theme_color_inputs,
|
||||
self.language,
|
||||
|
||||
Reference in new issue
Block a user