forked from 1b-agent/opencadstudio-upstream
feat: ATTREQ — attribute prompting during INSERT
When inserting a block that contains AttributeDefinition entities, the INSERT command now enters an attribute-filling phase: each attdef is prompted tag-by-tag (showing its prompt text and default value). Pressing Enter accepts the default. The completed AttributeEntity list is embedded in the Insert before committing. Blocks without attdefs continue to insert immediately as before. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
f73d38f0a2
commit
a20558a024
4 files changed
+168
-17
No files matched your search
@@ -1,4 +1,4 @@
|
||||
use acadrust::entities::Insert;
|
||||
use acadrust::entities::{AttributeEntity, Insert};
|
||||
use acadrust::types::Vector3;
|
||||
use acadrust::EntityType;
|
||||
use glam::Vec3;
|
||||
@@ -19,11 +19,22 @@ pub fn tool() -> ToolDef {
|
||||
enum Step {
|
||||
Name,
|
||||
Point { name: String },
|
||||
/// Attribute filling: prompt the user tag by tag.
|
||||
FillAttr {
|
||||
/// Attribute definitions: (tag, prompt_text, default_value).
|
||||
attdefs: Vec<(String, String, String)>,
|
||||
/// Index of the attdef currently being prompted.
|
||||
idx: usize,
|
||||
/// (tag, value) pairs collected so far.
|
||||
values: Vec<(String, String)>,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct InsertBlockCommand {
|
||||
available: Vec<String>,
|
||||
step: Step,
|
||||
/// Pending Insert entity stored while attr-filling is in progress.
|
||||
pending_insert: Option<Insert>,
|
||||
}
|
||||
|
||||
impl InsertBlockCommand {
|
||||
@@ -31,6 +42,7 @@ impl InsertBlockCommand {
|
||||
Self {
|
||||
available,
|
||||
step: Step::Name,
|
||||
pending_insert: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,6 +63,19 @@ impl CadCommand for InsertBlockCommand {
|
||||
format!("INSERT Enter block name:{hint}")
|
||||
}
|
||||
Step::Point { name } => format!("INSERT Specify insertion point for \"{}\":", name),
|
||||
Step::FillAttr { attdefs, idx, .. } => {
|
||||
if let Some((tag, prompt, default)) = attdefs.get(*idx) {
|
||||
let default_hint = if default.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(" <{default}>")
|
||||
};
|
||||
let prompt_text = if prompt.is_empty() { tag.as_str() } else { prompt.as_str() };
|
||||
format!("INSERT {prompt_text}{default_hint}:")
|
||||
} else {
|
||||
"INSERT Filling attributes...".into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,37 +83,104 @@ impl CadCommand for InsertBlockCommand {
|
||||
match &self.step {
|
||||
Step::Name => CmdResult::NeedPoint,
|
||||
Step::Point { name } => {
|
||||
CmdResult::CommitAndExit(EntityType::Insert(Insert::new(
|
||||
let ins = Insert::new(
|
||||
name.clone(),
|
||||
Vector3::new(pt.x as f64, pt.y as f64, pt.z as f64),
|
||||
)))
|
||||
);
|
||||
let block_name = name.clone();
|
||||
self.pending_insert = Some(ins);
|
||||
// Signal the host to check for attdefs.
|
||||
CmdResult::AttreqNeeded { block_name }
|
||||
}
|
||||
Step::FillAttr { .. } => CmdResult::NeedPoint,
|
||||
}
|
||||
}
|
||||
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
CmdResult::Cancel
|
||||
match &self.step {
|
||||
Step::Name | Step::Point { .. } => CmdResult::Cancel,
|
||||
Step::FillAttr { .. } => {
|
||||
// Treat Enter as accepting the default.
|
||||
self.accept_attr_value("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn wants_text_input(&self) -> bool {
|
||||
matches!(self.step, Step::Name)
|
||||
matches!(self.step, Step::Name | Step::FillAttr { .. })
|
||||
}
|
||||
|
||||
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
||||
if !matches!(self.step, Step::Name) {
|
||||
return None;
|
||||
match &self.step {
|
||||
Step::Name => {
|
||||
let name = text.trim();
|
||||
if !self.available.iter().any(|c| c.eq_ignore_ascii_case(name)) {
|
||||
return None;
|
||||
}
|
||||
self.step = Step::Point {
|
||||
name: name.to_string(),
|
||||
};
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
Step::FillAttr { .. } => Some(self.accept_attr_value(text)),
|
||||
Step::Point { .. } => None,
|
||||
}
|
||||
let name = text.trim();
|
||||
if !self.available.iter().any(|candidate| candidate.eq_ignore_ascii_case(name)) {
|
||||
return None;
|
||||
}
|
||||
self.step = Step::Point {
|
||||
name: name.to_string(),
|
||||
};
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
|
||||
fn on_preview_wires(&mut self, _pt: Vec3) -> Vec<WireModel> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn attreq_set_attdefs(&mut self, attdefs: Vec<(String, String, String)>) {
|
||||
self.step = Step::FillAttr { attdefs, idx: 0, values: vec![] };
|
||||
}
|
||||
|
||||
fn attreq_take_insert(&mut self) -> Option<acadrust::EntityType> {
|
||||
self.pending_insert.take().map(|ins| EntityType::Insert(ins))
|
||||
}
|
||||
}
|
||||
|
||||
impl InsertBlockCommand {
|
||||
/// Accept the current attribute value (empty = use default) and advance.
|
||||
/// Returns CommitAndExit when all attdefs have been filled.
|
||||
fn accept_attr_value(&mut self, text: &str) -> CmdResult {
|
||||
let (tag, default, next_idx, total) = match &self.step {
|
||||
Step::FillAttr { attdefs, idx, .. } => {
|
||||
let (tag, _, default) = &attdefs[*idx];
|
||||
(tag.clone(), default.clone(), idx + 1, attdefs.len())
|
||||
}
|
||||
_ => return CmdResult::Cancel,
|
||||
};
|
||||
|
||||
let value = if text.trim().is_empty() { default } else { text.trim().to_string() };
|
||||
|
||||
if let Step::FillAttr { ref mut values, ref mut idx, .. } = self.step {
|
||||
values.push((tag, value));
|
||||
*idx = next_idx;
|
||||
}
|
||||
|
||||
if next_idx >= total {
|
||||
// All attdefs filled — build the INSERT with AttributeEntity list.
|
||||
let values = match &self.step {
|
||||
Step::FillAttr { values, .. } => values.clone(),
|
||||
_ => vec![],
|
||||
};
|
||||
let mut ins = match self.pending_insert.take() {
|
||||
Some(i) => i,
|
||||
None => return CmdResult::Cancel,
|
||||
};
|
||||
for (tag, value) in values {
|
||||
let mut attr = AttributeEntity {
|
||||
tag,
|
||||
value: value.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
attr.set_value(&value);
|
||||
ins.attributes.push(attr);
|
||||
}
|
||||
CmdResult::CommitAndExit(EntityType::Insert(ins))
|
||||
} else {
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user