FIELD: Field dialog, FIELD command and the ATTDEF field buttons

A Field dialog with the fields the application evaluates and the
reference format choices, FIELD placing a field as multi-line text, the
attribute definition dialogs' field buttons, field attachment on commit,
system variable / file size / regional date providers for field
evaluation, and the codec patch with the field authoring API.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
ramox81andClaude Opus 5.5 committed 2026-10-02 09:56:54 +03:00
1 parent 53f2983e9c
commit 87211f4654
40 files changed
+1859 -14

No files matched your search

+190
View File
@@ -0,0 +1,190 @@
// FIELD — places the field chosen in the Field dialog as a multi-line text,
// and the object pick behind the dialog's "Select object" button.
use codec::entities::mtext::AttachmentPoint;
use codec::entities::MText;
use codec::types::{Handle, Vector3};
use codec::EntityType;
use glam::DVec3;
use crate::command::{CadCommand, CmdResult, InputKind, WorkingPlane};
/// A field to attach to the entity a command commits: the field code and the
/// objects it refers to.
pub type TextField = (String, Vec<Handle>);
#[derive(Clone, Copy, PartialEq)]
enum Step {
Point,
Height,
Justify,
}
/// `Specify start point or [Height/Justify]:` → an MTEXT holding the field.
pub struct FieldPlaceCommand {
step: Step,
value: String,
field: TextField,
style: String,
height: f64,
attachment: AttachmentPoint,
plane: WorkingPlane,
}
impl FieldPlaceCommand {
pub fn new(value: String, field: TextField, style: String, height: f64) -> Self {
Self {
step: Step::Point,
value,
field,
style,
height,
attachment: AttachmentPoint::TopLeft,
plane: WorkingPlane::default(),
}
}
}
fn short(v: f64) -> String {
let t = format!("{v:.4}");
t.trim_end_matches('0').trim_end_matches('.').to_string()
}
impl CadCommand for FieldPlaceCommand {
fn name(&self) -> &'static str {
"FIELD"
}
fn set_working_plane(&mut self, plane: WorkingPlane) {
self.plane = plane;
}
fn prompt(&self) -> String {
match self.step {
Step::Point => "Specify start point or [Height/Justify]:".into(),
Step::Height => format!("Specify height <{}>:", short(self.height)),
Step::Justify => "Enter justification [TL/TC/TR/ML/MC/MR/BL/BC/BR] <TL>:".into(),
}
}
fn input_kind(&self) -> InputKind {
match self.step {
Step::Justify => InputKind::SingleToken,
_ => InputKind::Point,
}
}
fn point_step_accepts_keywords(&self) -> bool {
self.step == Step::Point
}
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
let word = text.trim().trim_start_matches('_').to_ascii_uppercase();
match self.step {
Step::Point => match word.as_str() {
"H" | "HEIGHT" => self.step = Step::Height,
"J" | "JUSTIFY" => self.step = Step::Justify,
"" => return Some(CmdResult::ReportError("Point or option keyword required.".into())),
_ => return Some(CmdResult::ReportError("Point or option keyword required.".into())),
},
Step::Height => {
if word.is_empty() {
self.step = Step::Point;
return Some(CmdResult::NeedPoint);
}
match word.parse::<f64>() {
Ok(v) if v > 0.0 => {
self.height = v;
self.step = Step::Point;
}
Ok(_) => return Some(CmdResult::ReportError("Value must be positive and nonzero.".into())),
Err(_) => return Some(CmdResult::ReportError("Requires numeric distance or two points.".into())),
}
}
Step::Justify => {
self.attachment = match word.as_str() {
"" | "TL" => AttachmentPoint::TopLeft,
"TC" => AttachmentPoint::TopCenter,
"TR" => AttachmentPoint::TopRight,
"ML" => AttachmentPoint::MiddleLeft,
"MC" => AttachmentPoint::MiddleCenter,
"MR" => AttachmentPoint::MiddleRight,
"BL" => AttachmentPoint::BottomLeft,
"BC" => AttachmentPoint::BottomCenter,
"BR" => AttachmentPoint::BottomRight,
_ => return Some(CmdResult::ReportError("Invalid option keyword.".into())),
};
self.step = Step::Point;
}
}
Some(CmdResult::NeedPoint)
}
fn on_point(&mut self, pt: DVec3) -> CmdResult {
if self.step == Step::Height {
return CmdResult::NeedPoint;
}
let p = self.plane.to_local(pt);
let mut mtext = MText::new();
mtext.value = self.value.clone();
mtext.insertion_point = Vector3::new(p.x, p.y, p.z);
mtext.height = self.height;
mtext.style = self.style.clone();
mtext.attachment_point = self.attachment;
CmdResult::CommitAndExit(self.plane.place_entity(EntityType::MText(mtext)))
}
fn on_enter(&mut self) -> CmdResult {
match self.step {
Step::Point => CmdResult::ReportError("Point or option keyword required.".into()),
_ => self.on_text_input("").unwrap_or(CmdResult::NeedPoint),
}
}
fn text_field(&self) -> Option<TextField> {
Some(self.field.clone())
}
}
/// The Field dialog's "Select object": one object is picked and handed back
/// (`_FIELD_OBJECT <handle>`); Esc returns without one.
pub struct FieldObjectPickCommand;
impl CadCommand for FieldObjectPickCommand {
fn name(&self) -> &'static str {
"FIELD"
}
fn prompt(&self) -> String {
"Select object:".into()
}
fn needs_entity_pick(&self) -> bool {
true
}
fn entity_pick_highlights_hover(&self) -> bool {
true
}
fn on_entity_pick(&mut self, handle: Handle, _pt: DVec3) -> CmdResult {
if handle.is_null() {
return CmdResult::NeedPoint;
}
CmdResult::Dispatch(format!("_FIELD_OBJECT {:X}", handle.value()))
}
fn on_point(&mut self, _pt: DVec3) -> CmdResult {
CmdResult::NeedPoint
}
fn on_enter(&mut self) -> CmdResult {
CmdResult::Dispatch("_FIELD_OBJECT".into())
}
fn on_escape(&mut self) -> CmdResult {
CmdResult::Dispatch("_FIELD_OBJECT".into())
}
}
inventory::submit!(crate::command::CommandRegistration { names: &["FIELD"] });
+1
View File
@@ -7,6 +7,7 @@ pub mod angular_dim;
pub mod data_extract;
pub mod data_link;
pub mod ddedit;
pub mod field_cmd;
pub mod diameter_dim;
pub mod dim_baseline;
pub mod dim_continue;