review(#1627): one UPDATEFIELD, honest counts, safe field boxes, one-pass host redraw

- UPDATEFIELD stays the command main already had (Select objects, undo,
  locale strings); it now takes the PR's reach — a block reference's
  attributes and block definition, a table's cell texts — and reports
  the fields that really changed. The PR's second implementation and its
  duplicate registration are gone.
- FIELDEVAL's range error goes through the locale catalog.
- Field boxes: no slicing inside a character, one layout per multi-line
  attribute, and TEXT boxes scale about the annotative reference point
  like the glyphs do. Comment and indentation put back where they belong.
- bump_text_hosts classifies every host in one pass over the drawing
  instead of scanning all layouts and blocks per host.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-06 20:42:07 +03:00
1 parent 711799371a
commit af75cf42a8
6 files changed
+180 -169

No files matched your search

+34 -40
View File
@@ -542,60 +542,54 @@ impl OpenCADStudio {
}
/// UPDATEFIELD: every field the objects host is evaluated again and its
/// text written back. A table's fields are hosted by the cell texts of its
/// drawing (its `*T` block); fields a table keeps without such a text are
/// evaluated when drawn and are not counted here.
/// text written back, whatever FIELDEVAL says. A block reference brings
/// its attributes and the texts of its block definition; a table, the
/// cell texts of its drawing (its `*T` block).
fn update_fields(&mut self, i: usize, handles: &[Handle]) {
let doc = &self.tabs[i].scene.document;
let mut found = 0;
let mut updates: Vec<(Handle, String)> = Vec::new();
let mut hosts = handles.to_vec();
for h in handles {
let Some(entity) = doc.get_entity(*h) else { continue };
let hosts: Vec<Handle> = match entity {
EntityType::Table(t) => t
let record = match doc.get_entity(*h) {
Some(EntityType::Insert(insert)) => {
hosts.extend(insert.attributes.iter().map(|a| a.common.handle));
doc.block_records.get(&insert.block_name)
}
Some(EntityType::Table(t)) => t
.block_record_handle
.and_then(|r| doc.block_records.iter().find(|b| b.handle == r))
.or_else(|| doc.block_records.get(&t.block_name).filter(|_| !t.block_name.is_empty()))
.map(|record| record.entity_handles.clone())
.unwrap_or_default(),
_ => vec![*h],
.or_else(|| doc.block_records.get(&t.block_name).filter(|_| !t.block_name.is_empty())),
_ => None,
};
for host in hosts {
if !doc.get_entity(host).is_some_and(|e| crate::entities::field::hosts_field(doc, e)) {
continue;
}
found += 1;
if let Some(text) = crate::entities::field::resolve(doc, host) {
updates.push((host, text));
}
if let Some(record) = record {
hosts.extend(record.entity_handles.iter().copied());
}
}
let attributes: Vec<&codec::entities::EntityCommon> = handles
.iter()
.filter_map(|h| match doc.get_entity(*h) {
Some(EntityType::Insert(insert)) => Some(insert.attributes.iter().map(|a| &a.common)),
_ => None,
})
.flatten()
.collect();
let found = hosts
.iter()
.filter(|h| match doc.get_entity(**h) {
Some(entity) => crate::entities::field::hosts_field(doc, entity),
None => attributes.iter().any(|common| {
common.handle == **h && crate::entities::field::common_hosts_field(doc, common)
}),
})
.count();
self.command_line.push_output(&crate::tf!("{found} field(s) found."));
if found == 0 {
return;
}
self.push_undo_snapshot(i, "UPDATEFIELD");
let mut changed = Vec::new();
for (h, text) in updates {
if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(h) {
match entity {
EntityType::MText(m) => m.value = text,
EntityType::Text(t) => t.value = text,
EntityType::AttributeDefinition(a) => a.default_value = text,
_ => continue,
}
changed.push((h, crate::scene::ChangeKind::Modified));
}
let updated = self.tabs[i].scene.update_fields(32, Some(&hosts));
if updated > 0 {
self.tabs[i].dirty = true;
}
let updated = changed.len();
// Table cell texts live in a block definition.
let scene = &mut self.tabs[i].scene;
if scene.changes_touch_block_definition(&changed) {
scene.bump_geometry();
} else {
scene.bump_entities(&changed);
}
self.tabs[i].dirty = true;
self.command_line.push_output(&crate::tf!("{updated} field(s) updated."));
}
+3 -43
View File
@@ -17,48 +17,6 @@ impl OpenCADStudio {
self.open_field_dialog(FieldTarget::NewText);
Some(Task::none())
}
// UPDATEFIELD — re-evaluate the fields of the selected objects
// (a block reference's attributes included), whatever FIELDEVAL.
"UPDATEFIELD" => {
let selected: Vec<codec::Handle> =
self.tabs[i].scene.selected_entities().into_iter().map(|(h, _)| h).collect();
if selected.is_empty() {
use crate::modules::draw::select::SelectObjectsCommand;
let cmd = SelectObjectsCommand::new("UPDATEFIELD");
self.command_line.push_info(&cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(cmd));
return Some(Task::none());
}
// A block reference brings its attributes and the fields of its
// block definition; a table, the texts of its cells.
let document = &self.tabs[i].scene.document;
let mut hosts = selected.clone();
for handle in &selected {
let record = match document.get_entity(*handle) {
Some(codec::EntityType::Insert(insert)) => {
hosts.extend(insert.attributes.iter().map(|a| a.common.handle));
document.block_records.iter().find(|r| r.name.eq_ignore_ascii_case(&insert.block_name))
}
Some(codec::EntityType::Table(table)) => table
.block_record_handle
.and_then(|h| document.block_records.iter().find(|r| r.handle == h)),
_ => None,
};
if let Some(record) = record {
hosts.extend(record.entity_handles.iter().copied());
}
}
self.push_undo_snapshot(i, "UPDATEFIELD");
let found = self.tabs[i].scene.update_fields(32, Some(&hosts));
self.tabs[i].scene.deselect_all();
self.refresh_properties();
if found > 0 {
self.tabs[i].dirty = true;
}
self.command_line.push_output(crate::tf!("{} field(s) found.", found).as_ref());
self.command_line.push_output(crate::tf!("{} field(s) updated.", found).as_ref());
Some(Task::none())
}
// FIELDEVAL — the events that update fields, kept in the drawing.
cmd if cmd == "FIELDEVAL" || cmd.starts_with("FIELDEVAL ") || cmd.starts_with("SETVAR FIELDEVAL") => {
let value = cmd
@@ -85,7 +43,9 @@ impl OpenCADStudio {
}
}
_ => {
self.command_line.push_error("Requires an integer between 0 and 31.");
let (min, max) = (0, 31);
self.command_line
.push_error(&crate::tf!("Requires an integer between {min} and {max}."));
ask(self);
}
}
-1
View File
@@ -709,7 +709,6 @@ inventory::submit!(crate::command::CommandRegistration {
"UOSNAP",
"FIELDDISPLAY",
"FIELDEVAL",
"UPDATEFIELD",
"PDFIMPORTMODE",
"PDFIMPORTFILTER",
"PDFIMPORTLAYERS",