feat: ATTEDIT — edit block attribute values interactively

- attedit.rs: AtteditCommand picks an INSERT entity then iterates through
  each attribute tag prompting for a new value (Enter keeps existing)
- attedit_pending_handle() / attedit_set_attrs() trait methods inject
  attribute data into the command after entity-pick (avoids document access
  in the command itself)
- cmd_result.rs: NeedPoint handler injects attr data; ReplaceEntity detects
  the __ATTEDIT__ sentinel and calls apply_attedit() on the document
- apply_attedit() encodes tag/value pairs as __ATTEDIT__tag\x01val\x02...
- ROADMAP.md: ATTEDIT marked ✅

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-04-08 00:19:43 +03:00
commit 6f741a92e0
6 changed files with 229 additions and 1 deletions

View file

@ -8,6 +8,36 @@ impl H7CAD {
let i = self.active_tab;
match result {
CmdResult::NeedPoint => {
// If ATTEDIT just completed entity pick, inject attribute data.
let attedit_handle = self.tabs[i]
.active_cmd
.as_ref()
.and_then(|c| c.attedit_pending_handle());
if let Some(ins_handle) = attedit_handle {
if let Some(acadrust::EntityType::Insert(ins)) =
self.tabs[i].scene.document.get_entity(ins_handle)
{
let attrs: Vec<(String, String)> = ins
.attributes
.iter()
.map(|a| (a.tag.clone(), a.get_value().to_string()))
.collect();
if attrs.is_empty() {
self.command_line
.push_error("ATTEDIT This INSERT has no attributes.");
self.tabs[i].active_cmd = None;
return Task::none();
}
if let Some(cmd) = &mut self.tabs[i].active_cmd {
cmd.attedit_set_attrs(attrs);
}
} else {
self.command_line
.push_error("ATTEDIT Please select an INSERT entity with attributes.");
self.tabs[i].active_cmd = None;
return Task::none();
}
}
let prompt = self.tabs[i].active_cmd.as_ref().map(|c| c.prompt());
if let Some(p) = prompt {
self.command_line.push_info(&p);
@ -170,6 +200,27 @@ impl H7CAD {
self.refresh_properties();
}
CmdResult::ReplaceEntity(handle, new_entities) => {
// Detect ATTEDIT sentinel.
if new_entities.len() == 1 {
if let acadrust::EntityType::XLine(ref xl) = new_entities[0] {
let layer = xl.common.layer.clone();
if let Some(encoded) = layer.strip_prefix("__ATTEDIT__") {
let label = self.history_label_from_active_cmd(i, "ATTEDIT");
self.push_undo_snapshot(i, label);
crate::modules::home::modify::attedit::apply_attedit(
&mut self.tabs[i].scene.document,
handle,
encoded,
);
self.tabs[i].dirty = true;
self.tabs[i].active_cmd = None;
self.tabs[i].snap_result = None;
self.command_line.push_output("ATTEDIT Attribute values updated.");
return Task::none();
}
}
}
// Detect SPLINEDIT sentinel: a single XLine with a magic layer name.
if new_entities.len() == 1 {
if let acadrust::EntityType::XLine(ref xl) = new_entities[0] {

View file

@ -1442,6 +1442,13 @@ impl H7CAD {
self.tabs[i].active_cmd = Some(Box::new(cmd_obj));
}
"ATTEDIT"|"ATE"|"-ATTEDIT" => {
use crate::modules::home::modify::attedit::AtteditCommand;
let cmd_obj = AtteditCommand::new();
self.command_line.push_info(&cmd_obj.prompt());
self.tabs[i].active_cmd = Some(Box::new(cmd_obj));
}
"ALIGN"|"AL" => {
use crate::modules::home::modify::align::AlignCommand;
let cmd = AlignCommand::new();