forked from 1b-agent/opencadstudio-upstream
feat(props): editable block Name row + collapsible XYZ groups
- Block reference Name (Misc) is editable: the dropdown re-points the insert to another definition; typing a new name renames the definition everywhere (record re-key, Block marker, every INSERT reference). Anonymous (*) and xref blocks stay read-only, with the same guards added to Scene::rename_block. - Custom single-control dropdown (text field + caret) built on the shared floating_below overlay so the list always opens downward, unlike iced's space-based menu placement. - Consecutive "<Base> X/Y/Z" rows (Position, Scale, Start, Center, ...) collapse into one expandable summary row across all entities; the expand state persists across rebuilds and selection changes. - The BEDIT block tab no longer offers the layout Rename/Delete context menu. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
1 parent
47f3409588
commit
b5dbef5776
10 files changed
+583
-51
No files matched your search
@@ -1671,6 +1671,11 @@ pub enum Message {
|
||||
},
|
||||
/// User committed a geometry/common field edit (Enter pressed).
|
||||
PropGeomCommit(&'static str),
|
||||
/// Toggle a collapsed coordinate group ("Position", "Scale", …) open or
|
||||
/// closed in the Properties panel, keyed `section:base`.
|
||||
PropGroupToggle(String),
|
||||
/// Toggle the editable-dropdown (block Name) option list open/closed.
|
||||
PropEditChoiceToggle,
|
||||
/// User is typing in a block-attribute value field (live buffer update),
|
||||
/// keyed by the attribute tag.
|
||||
PropAttrInput { tag: String, value: String },
|
||||
|
||||
@@ -20,6 +20,9 @@ impl OpenCADStudio {
|
||||
// closes, matching the deselect / reselect / click-away expectation.
|
||||
let color_palette_open = self.tabs[i].properties.color_palette_open;
|
||||
let edit_buf = std::mem::take(&mut self.tabs[i].properties.edit_buf);
|
||||
// Expanded coordinate groups persist across rebuilds AND selection
|
||||
// changes — it's a per-user view preference, not per-entity state.
|
||||
let expanded_groups = std::mem::take(&mut self.tabs[i].properties.expanded_groups);
|
||||
// Which entities the previous panel was built for — an uncommitted
|
||||
// edit buffer only survives a rebuild for the *same* selection.
|
||||
let prev_handles = std::mem::take(&mut self.tabs[i].properties.source_handles);
|
||||
@@ -497,6 +500,41 @@ impl OpenCADStudio {
|
||||
let src_mm = if src == 0 { 1.0 } else { insunits_to_mm(src) };
|
||||
let factor = if host_mm.abs() > 1e-12 { src_mm / host_mm } else { 1.0 };
|
||||
set_row(&mut sections, "unit_factor", format!("{factor:.4}"));
|
||||
|
||||
// Name row: editable for regular blocks — pick an
|
||||
// existing definition to re-point this reference, or
|
||||
// type a new name to rename the definition (every
|
||||
// insert of it follows). Anonymous (*) and
|
||||
// xref(-dependent) blocks keep the read-only row.
|
||||
let regular = |br: &acadrust::tables::BlockRecord| {
|
||||
!br.is_anonymous()
|
||||
&& !br.flags.is_xref
|
||||
&& !br.name.contains('|')
|
||||
};
|
||||
let editable = doc
|
||||
.block_records
|
||||
.get(&ins.block_name)
|
||||
.map(®ular)
|
||||
.unwrap_or(false);
|
||||
if editable {
|
||||
let mut options: Vec<String> = doc
|
||||
.block_records
|
||||
.iter()
|
||||
.filter(|br| regular(br))
|
||||
.map(|br| br.name.clone())
|
||||
.collect();
|
||||
options.sort_by(|a, b| {
|
||||
a.to_lowercase().cmp(&b.to_lowercase())
|
||||
});
|
||||
set_row_value(
|
||||
&mut sections,
|
||||
"block",
|
||||
crate::scene::model::object::PropValue::EditChoice {
|
||||
value: ins.block_name.clone(),
|
||||
options,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
// Underlay: name + path from the referenced definition.
|
||||
acadrust::EntityType::Underlay(ul) => {
|
||||
@@ -919,6 +957,7 @@ impl OpenCADStudio {
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
panel.expanded_groups = expanded_groups;
|
||||
panel.source_handles = new_handles;
|
||||
panel.prop_vertex = prop_vertex;
|
||||
panel
|
||||
@@ -1455,6 +1494,16 @@ fn merge_prop_value(
|
||||
selected: VARIES_LABEL.into(),
|
||||
options: options.clone(),
|
||||
},
|
||||
(
|
||||
PropValue::EditChoice { options, .. },
|
||||
PropValue::EditChoice {
|
||||
options: other_options,
|
||||
..
|
||||
},
|
||||
) if options == other_options => PropValue::EditChoice {
|
||||
value: VARIES_LABEL.into(),
|
||||
options: options.clone(),
|
||||
},
|
||||
(PropValue::EditText(_), PropValue::EditText(_)) => {
|
||||
PropValue::EditText(VARIES_LABEL.into())
|
||||
}
|
||||
|
||||
+107
-2
@@ -1416,6 +1416,40 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
} else if field == "block" {
|
||||
// Name dropdown on a block reference: re-point the
|
||||
// selected inserts to the picked definition. A stale
|
||||
// typed value in the text buffer would mask the pick,
|
||||
// so drop it; the pick also closes the list.
|
||||
self.tabs[i].properties.edit_buf.remove("block");
|
||||
self.tabs[i].properties.edit_choice_open = false;
|
||||
let canon = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.block_records
|
||||
.get(&value)
|
||||
.map(|br| br.name.clone());
|
||||
if let Some(canon) = canon {
|
||||
let mut changed = false;
|
||||
for &handle in &handles {
|
||||
if self.tabs[i].scene.is_layer_locked(handle) {
|
||||
continue;
|
||||
}
|
||||
if let Some(acadrust::EntityType::Insert(ins)) =
|
||||
self.tabs[i].scene.document.get_entity_mut(handle)
|
||||
{
|
||||
if ins.block_name != canon {
|
||||
ins.block_name = canon.clone();
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if changed {
|
||||
// The picked block may not be in the defn cache
|
||||
// yet — rebuild block definitions too.
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
}
|
||||
}
|
||||
} else if field == "plot_style" {
|
||||
// Named plot-style pick: ByLayer / ByBlock clear the
|
||||
// handle; a named style resolves through the drawing's
|
||||
@@ -1515,9 +1549,21 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
let handles = self.property_target_handles(i);
|
||||
if !handles.is_empty() {
|
||||
if let Some(raw_val) = self.tabs[i].properties.edit_buf.remove(field) {
|
||||
let val = crate::app::expr_eval::eval_to_string(&raw_val);
|
||||
// Block names are free-form text — a name like "10-5"
|
||||
// must not be arithmetic-evaluated.
|
||||
let val = if field == "block" {
|
||||
raw_val
|
||||
} else {
|
||||
crate::app::expr_eval::eval_to_string(&raw_val)
|
||||
};
|
||||
self.push_undo_snapshot(i, "CHPROP");
|
||||
if field == "frozen_layers" {
|
||||
if field == "block" {
|
||||
// Name row on a block reference: an existing name
|
||||
// re-points the selected inserts; a new one renames
|
||||
// the definition they share. Commit closes the list.
|
||||
self.tabs[i].properties.edit_choice_open = false;
|
||||
self.apply_block_name_commit(i, &handles, val.trim());
|
||||
} else if field == "frozen_layers" {
|
||||
// Resolve layer names → handles, then apply to viewports.
|
||||
let layer_handles: Vec<acadrust::Handle> = val
|
||||
.split(',')
|
||||
@@ -1633,6 +1679,65 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Properties-panel Name commit for block references. A `new` matching an
|
||||
/// existing regular block re-points the selected inserts to it; otherwise
|
||||
/// the definition the (single-block) selection references is renamed and
|
||||
/// every insert of it follows. Anonymous/xref definitions never get here —
|
||||
/// their Name row stays read-only.
|
||||
fn apply_block_name_commit(&mut self, i: usize, handles: &[acadrust::Handle], new: &str) {
|
||||
if new.is_empty() {
|
||||
return;
|
||||
}
|
||||
// Existing target → swap the selected references to it. Anonymous,
|
||||
// xref and xref-dependent definitions are not valid targets.
|
||||
let target = self.tabs[i].scene.document.block_records.get(new).map(|br| {
|
||||
(
|
||||
br.name.clone(),
|
||||
br.is_anonymous() || br.flags.is_xref || br.name.contains('|'),
|
||||
)
|
||||
});
|
||||
if let Some((canon, protected)) = target {
|
||||
if protected {
|
||||
return;
|
||||
}
|
||||
let mut changed = false;
|
||||
for &handle in handles {
|
||||
if self.tabs[i].scene.is_layer_locked(handle) {
|
||||
continue;
|
||||
}
|
||||
if let Some(acadrust::EntityType::Insert(ins)) =
|
||||
self.tabs[i].scene.document.get_entity_mut(handle)
|
||||
{
|
||||
if ins.block_name != canon {
|
||||
ins.block_name = canon.clone();
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if changed {
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
}
|
||||
return;
|
||||
}
|
||||
// New name → rename. Only when every selected insert references the
|
||||
// same definition; a mixed selection makes the rename target ambiguous.
|
||||
let mut old: Option<String> = None;
|
||||
for &handle in handles {
|
||||
if let Some(acadrust::EntityType::Insert(ins)) =
|
||||
self.tabs[i].scene.document.get_entity(handle)
|
||||
{
|
||||
match &old {
|
||||
None => old = Some(ins.block_name.clone()),
|
||||
Some(o) if o.eq_ignore_ascii_case(&ins.block_name) => {}
|
||||
_ => return,
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(old) = old {
|
||||
self.tabs[i].scene.rename_block(&old, new);
|
||||
}
|
||||
}
|
||||
|
||||
/// Commit an edited block-attribute value from the Properties panel.
|
||||
/// Writes the new value into the matching attribute (by tag) of every
|
||||
/// selected INSERT, then re-tessellates so the attribute text repaints.
|
||||
|
||||
+22
-1
@@ -2695,6 +2695,20 @@ impl OpenCADStudio {
|
||||
|
||||
Message::PropGeomCommit(field) => self.on_prop_geom_commit(field),
|
||||
|
||||
Message::PropGroupToggle(key) => {
|
||||
let groups = &mut self.tabs[self.active_tab].properties.expanded_groups;
|
||||
if !groups.remove(&key) {
|
||||
groups.insert(key);
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::PropEditChoiceToggle => {
|
||||
let panel = &mut self.tabs[self.active_tab].properties;
|
||||
panel.edit_choice_open = !panel.edit_choice_open;
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::PropAttrInput { tag, value } => {
|
||||
self.tabs[self.active_tab]
|
||||
.properties
|
||||
@@ -2896,7 +2910,14 @@ impl OpenCADStudio {
|
||||
}
|
||||
|
||||
Message::LayoutContextMenu(name) => {
|
||||
if name != "Model" {
|
||||
// No menu on the transient BEDIT block tab: Rename/Delete are
|
||||
// layout operations and don't apply to it.
|
||||
let is_block_tab = self.tabs[self.active_tab]
|
||||
.block_edit
|
||||
.as_ref()
|
||||
.map(|be| be.block_name == name)
|
||||
.unwrap_or(false);
|
||||
if name != "Model" && !is_block_tab {
|
||||
self.layout_context_menu = Some(name);
|
||||
}
|
||||
Task::none()
|
||||
|
||||
Reference in new issue
Block a user