Merge PR #1590 from ramox81/codex/field-table-placeholders: table formula buttons, block placeholders, attribute fields on insert, plot-time fields

The PR pinned the codec to its fork through [patch]; codec #81 is merged
upstream and main already points at it, so the patch section is dropped.
This commit is contained in:
Hakan Seven committed 2026-10-02 20:09:27 +03:00
commit 29ed1cae9b
29 files changed
+524 -12

No files matched your search

+6
View File
@@ -4105,6 +4105,12 @@ insert =
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-average = Average
.field-sum = Sum
.field-object-name = Object name
.field-block-placeholder = Block placeholder
.field-block-editor-only = Only accessible in the block editor
.field-block-reference-property = Block reference property
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
+6
View File
@@ -4092,6 +4092,12 @@ insert =
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-average = Average
.field-sum = Sum
.field-object-name = Object name
.field-block-placeholder = Block placeholder
.field-block-editor-only = Only accessible in the block editor
.field-block-reference-property = Block reference property
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
+6
View File
@@ -4090,6 +4090,12 @@ insert =
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-average = Average
.field-sum = Sum
.field-object-name = Object name
.field-block-placeholder = Block placeholder
.field-block-editor-only = Only accessible in the block editor
.field-block-reference-property = Block reference property
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
+6
View File
@@ -4091,6 +4091,12 @@ insert =
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-average = Average
.field-sum = Sum
.field-object-name = Object name
.field-block-placeholder = Block placeholder
.field-block-editor-only = Only accessible in the block editor
.field-block-reference-property = Block reference property
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
+6
View File
@@ -4093,6 +4093,12 @@ insert =
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-average = Average
.field-sum = Sum
.field-object-name = Object name
.field-block-placeholder = Block placeholder
.field-block-editor-only = Only accessible in the block editor
.field-block-reference-property = Block reference property
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
+6
View File
@@ -4094,6 +4094,12 @@ insert =
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-average = Average
.field-sum = Sum
.field-object-name = Object name
.field-block-placeholder = Block placeholder
.field-block-editor-only = Only accessible in the block editor
.field-block-reference-property = Block reference property
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
+6
View File
@@ -4093,6 +4093,12 @@ insert =
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-average = Average
.field-sum = Sum
.field-object-name = Object name
.field-block-placeholder = Block placeholder
.field-block-editor-only = Only accessible in the block editor
.field-block-reference-property = Block reference property
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
+6
View File
@@ -4092,6 +4092,12 @@ insert =
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-average = Average
.field-sum = Sum
.field-object-name = Object name
.field-block-placeholder = Block placeholder
.field-block-editor-only = Only accessible in the block editor
.field-block-reference-property = Block reference property
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
+6
View File
@@ -4093,6 +4093,12 @@ insert =
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-average = Average
.field-sum = Sum
.field-object-name = Object name
.field-block-placeholder = Block placeholder
.field-block-editor-only = Only accessible in the block editor
.field-block-reference-property = Block reference property
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
+6
View File
@@ -4081,6 +4081,12 @@ insert =
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-average = Average
.field-sum = Sum
.field-object-name = Object name
.field-block-placeholder = Block placeholder
.field-block-editor-only = Only accessible in the block editor
.field-block-reference-property = Block reference property
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
+6
View File
@@ -4092,6 +4092,12 @@ insert =
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-average = Average
.field-sum = Sum
.field-object-name = Object name
.field-block-placeholder = Block placeholder
.field-block-editor-only = Only accessible in the block editor
.field-block-reference-property = Block reference property
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
+6
View File
@@ -4092,6 +4092,12 @@ insert =
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-average = Average
.field-sum = Sum
.field-object-name = Object name
.field-block-placeholder = Block placeholder
.field-block-editor-only = Only accessible in the block editor
.field-block-reference-property = Block reference property
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
+6
View File
@@ -4081,6 +4081,12 @@ insert =
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-average = Average
.field-sum = Sum
.field-object-name = Object name
.field-block-placeholder = Block placeholder
.field-block-editor-only = Only accessible in the block editor
.field-block-reference-property = Block reference property
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
+6
View File
@@ -4092,6 +4092,12 @@ insert =
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-average = Average
.field-sum = Sum
.field-object-name = Object name
.field-block-placeholder = Block placeholder
.field-block-editor-only = Only accessible in the block editor
.field-block-reference-property = Block reference property
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
+6
View File
@@ -4091,6 +4091,12 @@ insert =
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-average = Average
.field-sum = Sum
.field-object-name = Object name
.field-block-placeholder = Block placeholder
.field-block-editor-only = Only accessible in the block editor
.field-block-reference-property = Block reference property
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
+6
View File
@@ -4092,6 +4092,12 @@ insert =
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-average = Average
.field-sum = Sum
.field-object-name = Object name
.field-block-placeholder = Block placeholder
.field-block-editor-only = Only accessible in the block editor
.field-block-reference-property = Block reference property
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
+6
View File
@@ -4093,6 +4093,12 @@ insert =
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-average = Average
.field-sum = Sum
.field-object-name = Object name
.field-block-placeholder = Block placeholder
.field-block-editor-only = Only accessible in the block editor
.field-block-reference-property = Block reference property
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
+6
View File
@@ -4094,6 +4094,12 @@ insert =
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-average = Average
.field-sum = Sum
.field-object-name = Object name
.field-block-placeholder = Block placeholder
.field-block-editor-only = Only accessible in the block editor
.field-block-reference-property = Block reference property
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
+6
View File
@@ -4053,6 +4053,12 @@ insert =
.field-select-object = Nesne seç
.field-expression = Alan ifadesi
.field-help = Bir alan ekler: çizimden, tarihten veya başka bir kaynaktan güncellenen metin.
.field-average = Ortalama
.field-sum = Toplam
.field-object-name = Nesne adı
.field-block-placeholder = Blok yer tutucusu
.field-block-editor-only = Yalnız blok düzenleyicide kullanılabilir
.field-block-reference-property = Blok başvurusu özelliği
.field-cat-linked = Bağlantılı
.field-current-precision = Geçerli hassasiyet
.field-evaluate = Hesapla
+6
View File
@@ -4076,6 +4076,12 @@ insert =
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-average = Average
.field-sum = Sum
.field-object-name = Object name
.field-block-placeholder = Block placeholder
.field-block-editor-only = Only accessible in the block editor
.field-block-reference-property = Block reference property
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
+6
View File
@@ -4088,6 +4088,12 @@ insert =
.field-select-object = Select object
.field-expression = Field expression
.field-help = Inserts a field: text that updates from the drawing, the date or another source.
.field-average = Average
.field-sum = Sum
.field-object-name = Object name
.field-block-placeholder = Block placeholder
.field-block-editor-only = Only accessible in the block editor
.field-block-reference-property = Block reference property
.field-cat-linked = Linked
.field-current-precision = Current precision
.field-evaluate = Evaluate
+20 -2
View File
@@ -231,7 +231,15 @@ impl OpenCADStudio {
// objects are not part of the add delta, so the step is a snapshot.
let text_field = self.tabs[i].active_cmd.as_ref().and_then(|cmd| cmd.text_field());
let label = self.history_label_from_active_cmd(i, "ENTITY");
let delta_safe = text_field.is_none() && self.delta_add_safe(i, &entity);
// A block reference whose attribute definitions carry fields gets
// them copied onto its attributes (field objects: snapshot step).
let attribute_fields = match &entity {
codec::EntityType::Insert(insert) if !insert.attributes.is_empty() => {
crate::entities::field::block_has_attribute_fields(&self.tabs[i].scene.document, &insert.block_name)
}
_ => false,
};
let delta_safe = text_field.is_none() && !attribute_fields && self.delta_add_safe(i, &entity);
let field_value = match &entity {
codec::EntityType::MText(m) => m.value.clone(),
codec::EntityType::AttributeDefinition(a) => a.default_value.clone(),
@@ -264,6 +272,10 @@ impl OpenCADStudio {
&field_value,
);
}
if let (Some(handle), true) = (committed, attribute_fields) {
crate::entities::field::attach_attribute_fields(&mut self.tabs[i].scene.document, handle);
self.tabs[i].scene.bump_entities(&[(handle, crate::scene::ChangeKind::Modified)]);
}
if let Some(handle) = committed {
self.apply_continuous_constraints(i, &[handle]);
}
@@ -972,7 +984,7 @@ impl OpenCADStudio {
let i = self.active_tab;
// Collect the full AttributeDefinitions owned by this block
// record so each created attribute keeps its geometry (#255).
let attdefs: Vec<codec::entities::AttributeDefinition> = {
let mut attdefs: Vec<codec::entities::AttributeDefinition> = {
let doc = &self.tabs[i].scene.document;
if let Some(br) = doc.block_records.get(&block_name) {
br.entity_handles
@@ -992,6 +1004,12 @@ impl OpenCADStudio {
}
};
// ATTREQ 0: attributes take their defaults without prompting.
if !self.tabs[i].scene.document.header.attribute_request {
for attdef in &mut attdefs {
attdef.flags.preset = true;
}
}
if attdefs.is_empty() {
// No attribute definitions — commit the INSERT directly.
let entity = self.tabs[i]
+118 -2
View File
@@ -3,7 +3,7 @@
use crate::app::{Message, OpenCADStudio};
use crate::command::CadCommand;
use crate::modules::annotate::field_cmd::{FieldObjectPickCommand, FieldPlaceCommand};
use crate::modules::annotate::field_cmd::{FieldObjectPickCommand, FieldPlaceCommand, FieldTablePickCommand};
use crate::ui::window::field_dialog::{
fields_of, object_properties, FieldDialogMsg, FieldDialogState, FieldTarget, DATE_FORMATS,
NAMED_TYPES,
@@ -35,13 +35,70 @@ impl OpenCADStudio {
}
Some(Task::none())
}
cmd if cmd == "_FIELD_CELL" || cmd.starts_with("_FIELD_CELL ") => {
let parts: Vec<&str> = cmd.split_whitespace().skip(1).collect();
if let Some(expression) = self.table_cell_expression(i, &parts) {
if let Some(state) = self.field_dialog.as_mut() {
// The reference appends at the end, with no operator.
state.formula.push_str(&expression);
}
self.refresh_field_preview();
// A real result gets the Decimal format, as the reference picks it.
if let Some(state) = self.field_dialog.as_mut() {
if state.formula_format == 0 && state.preview.contains('.') {
state.formula_format = 2;
}
}
}
if self.field_dialog.is_some() {
self.active_modal = Some(crate::app::ModalKind::Field);
self.refresh_field_preview();
}
Some(Task::none())
}
_ => None,
}
}
/// The formula text for a picked table cell or cell range:
/// `Table(%<\_ObjId H>%).B4` or `Table(%<\_ObjId H>%).Evaluate(Sum(A3:B5))`.
fn table_cell_expression(&self, i: usize, parts: &[&str]) -> Option<String> {
let function = *parts.first()?;
let cell = |at: &[&str]| -> Option<(codec::Handle, usize, usize)> {
let handle = codec::Handle::new(u64::from_str_radix(at.first()?, 16).ok()?);
let point = glam::DVec3::new(at.get(1)?.parse().ok()?, at.get(2)?.parse().ok()?, at.get(3)?.parse().ok()?);
let document = &self.tabs[i].scene.document;
let codec::EntityType::Table(table) = document.get_entity(handle)? else {
return None;
};
let hit = crate::modules::annotate::table_cmd::table_cell_at(table, None, point)?;
Some((handle, hit.row, hit.column))
};
let name = |row: usize, column: usize| format!("{}{}", column_letters(column), row + 1);
let (table, row, column) = cell(parts.get(1..5)?)?;
if function == "Cell" {
return Some(format!("Table(%<\\_ObjId {:X}>%).{}", table.value(), name(row, column)));
}
let (second_table, row2, column2) = cell(parts.get(5..9)?)?;
if second_table != table {
return None;
}
Some(format!(
"Table(%<\\_ObjId {:X}>%).Evaluate({function}({}:{}))",
table.value(),
name(row.min(row2), column.min(column2)),
name(row.max(row2), column.max(column2)),
))
}
/// Open the Field dialog for `target`.
pub(in crate::app) fn open_field_dialog(&mut self, target: FieldTarget) {
let mut state = FieldDialogState::new(target);
if !matches!(target, FieldTarget::NewText) {
state.placeholder_block = self.tabs[self.active_tab]
.active_block_edit_session()
.map(|session| session.block_name.clone());
}
state.examples = DATE_FORMATS
.iter()
.map(|f| self.field_value(&format!("\\AcVar Date \\f \"{f}\""), &[]))
@@ -62,7 +119,17 @@ impl OpenCADStudio {
return;
};
let (code, objects) = state.code();
let preview = if state.complete() { self.field_value(&code, &objects) } else { String::new() };
let preview = if state.complete() {
if state.name == "BlockPlaceholder" {
// A placeholder shows its property name until a block reference resolves it.
let (label, _, _) = codec::fields::BLOCK_PLACEHOLDER_PROPERTIES[state.placeholder_property];
text_case(label, state.text_case)
} else {
self.field_value(&code, &objects)
}
} else {
String::new()
};
if let Some(state) = self.field_dialog.as_mut() {
state.preview = preview;
}
@@ -155,6 +222,18 @@ impl OpenCADStudio {
);
}
FieldDialogMsg::PlotScale(k) => state.plot_scale = k,
FieldDialogMsg::PlaceholderProperty(k) => {
state.placeholder_property = k;
state.text_case = 0;
}
FieldDialogMsg::TableFunction(function) => {
// The dialog waits while the cells are picked.
self.active_modal = None;
let command = FieldTablePickCommand::new(function);
self.command_line.push_info(&command.prompt());
self.tabs[i].active_cmd = Some(Box::new(command));
return Task::none();
}
FieldDialogMsg::SelectObject => {
// The dialog waits while one object is picked.
self.active_modal = None;
@@ -226,3 +305,40 @@ impl OpenCADStudio {
};
}
}
/// Spreadsheet column letters: A … Z, AA, AB …
fn column_letters(mut column: usize) -> String {
let mut letters = Vec::new();
loop {
letters.push(b'A' + (column % 26) as u8);
if column < 26 {
break;
}
column = column / 26 - 1;
}
letters.reverse();
String::from_utf8(letters).unwrap_or_default()
}
/// The Field dialog's text cases: (none), Uppercase, Lowercase, First capital, Title case.
fn text_case(value: &str, case: usize) -> String {
match case {
1 => value.to_uppercase(),
2 => value.to_lowercase(),
3 => {
let lower = value.to_lowercase();
let mut chars = lower.chars();
chars.next().map(|c| c.to_uppercase().chain(chars).collect()).unwrap_or_default()
}
4 => value
.split(' ')
.map(|word| {
let lower = word.to_lowercase();
let mut chars = lower.chars();
chars.next().map(|c| c.to_uppercase().chain(chars).collect::<String>()).unwrap_or_default()
})
.collect::<Vec<_>>()
.join(" "),
_ => value.to_string(),
}
}
+8
View File
@@ -475,6 +475,7 @@ impl OpenCADStudio {
if !path.to_ascii_lowercase().ends_with(".pdf") {
return Err(failure("invalid_path", "plot writes .pdf files"));
}
self.stamp_plot_fields();
let i = self.active_tab;
let names = self.tabs[i].scene.layout_names();
let requested = req["layout"].as_str().unwrap_or("Model").to_owned();
@@ -1132,6 +1133,13 @@ impl OpenCADStudio {
"link_text" => F::HyperlinkText(v.into()),
"link_url" => F::HyperlinkUrl(v.into()),
"plot_scale" => F::PlotScale(index(codec::fields::PLOT_SCALE_FORMATS.len())?),
"placeholder" => F::PlaceholderProperty(index(codec::fields::BLOCK_PLACEHOLDER_PROPERTIES.len())?),
"table_function" => F::TableFunction(
["Average", "Sum", "Count", "Cell"]
.into_iter()
.find(|f| f.eq_ignore_ascii_case(v))
.ok_or_else(|| failure("invalid_value", "Average|Sum|Count|Cell"))?,
),
_ => return Err(failure("invalid_value", "Unknown field dialog key")),
})
}
+16
View File
@@ -3764,7 +3764,20 @@ impl OpenCADStudio {
task
}
/// A plot gives its fields (PlotDate) the plot time, as the reference
/// stores it; the hosts are redrawn before the plot reads the scene.
pub(in crate::app) fn stamp_plot_fields(&mut self) {
let i = self.active_tab;
let hosts = crate::entities::field::stamp_plot_fields(&mut self.tabs[i].scene.document);
if !hosts.is_empty() {
let changes: Vec<_> = hosts.into_iter().map(|h| (h, crate::scene::ChangeKind::Modified)).collect();
self.tabs[i].scene.bump_entities(&changes);
self.tabs[i].dirty = true;
}
}
fn print_all_pages(&mut self) -> Result<Vec<crate::io::pdf_export::PdfPageInput>, String> {
self.stamp_plot_fields();
let available = self.tabs[self.active_tab].scene.layout_names();
let selected: Vec<String> = self
.print_all_layouts
@@ -5337,6 +5350,9 @@ impl OpenCADStudio {
}
self.active_modal = None;
self.reset_modal_geometry();
if !preview {
self.stamp_plot_fields();
}
// Extents, Window and Display use one plot path in both spaces. Only
// Paper-space Layout is special: it uses the physical sheet bounds.
+56
View File
@@ -360,3 +360,59 @@ fn display_name() -> Option<String> {
#[cfg(not(target_os = "windows"))]
None
}
/// Copy each attribute definition's field onto the matching attribute of a
/// new block reference (block placeholders resolve against the reference).
/// Returns the attributes that received a field.
pub fn attach_attribute_fields(document: &mut CadDocument, insert: Handle) -> Vec<Handle> {
let context = OcsFieldContext(None);
document.attach_attribute_fields(insert, &context)
}
/// Whether any attribute definition of `block` hosts a field.
pub fn block_has_attribute_fields(document: &CadDocument, block: &str) -> bool {
let Some(record) = document.block_records.iter().find(|r| r.name.eq_ignore_ascii_case(block)) else {
return false;
};
record.entity_handles.iter().filter_map(|h| document.get_entity(*h)).any(|e| {
matches!(e, codec::entities::EntityType::AttributeDefinition(_)) && hosts_field(document, e)
})
}
/// Give plot-time fields (PlotDate) the plot's time; returns the hosts whose
/// text changed.
pub fn stamp_plot_fields(document: &mut CadDocument) -> Vec<Handle> {
let snapshot = document.clone();
let context = PlotContext(OcsFieldContext(Some(&snapshot)));
document.stamp_plot_fields(&context)
}
/// The app context while a plot is produced.
struct PlotContext<'a>(OcsFieldContext<'a>);
impl FieldContext for PlotContext<'_> {
fn now_julian(&self) -> f64 {
self.0.now_julian()
}
fn file_times(&self) -> Option<(f64, f64)> {
self.0.file_times()
}
fn plotting(&self) -> bool {
true
}
fn login(&self) -> Option<String> {
self.0.login()
}
fn getvar(&self, name: &str) -> Option<String> {
self.0.getvar(name)
}
fn file_size(&self) -> Option<u64> {
self.0.file_size()
}
fn date_locale(&self) -> codec::fields::DateLocale {
self.0.date_locale()
}
fn getenv(&self, name: &str) -> Option<String> {
self.0.getenv(name)
}
}
+6
View File
@@ -235,6 +235,12 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
"Select object" => Some(("insert", "field-select-object")),
"Field expression" => Some(("insert", "field-expression")),
"Inserts a field: text that updates from the drawing, the date or another source." => Some(("insert", "field-help")),
"Average" => Some(("insert", "field-average")),
"Sum" => Some(("insert", "field-sum")),
"Object name" => Some(("insert", "field-object-name")),
"Block placeholder" => Some(("insert", "field-block-placeholder")),
"Only accessible in the block editor" => Some(("insert", "field-block-editor-only")),
"Block reference property" => Some(("insert", "field-block-reference-property")),
"Linked" => Some(("insert", "field-cat-linked")),
"Current precision" => Some(("insert", "field-current-precision")),
"Evaluate" => Some(("insert", "field-evaluate")),
+67
View File
@@ -189,4 +189,71 @@ impl CadCommand for FieldObjectPickCommand {
}
}
/// The Field dialog's Average / Sum / Count / Cell buttons: a cell range (or
/// one cell) is picked in a table and handed back as
/// `_FIELD_CELL <function> <table> <x> <y> <z> [<table> <x> <y> <z>]`.
pub struct FieldTablePickCommand {
pub function: &'static str,
first: Option<(Handle, DVec3)>,
}
impl FieldTablePickCommand {
pub fn new(function: &'static str) -> Self {
Self { function, first: None }
}
}
impl CadCommand for FieldTablePickCommand {
fn name(&self) -> &'static str {
"FIELD"
}
fn prompt(&self) -> String {
match (self.function, self.first) {
("Cell", _) => "Select table cell:".into(),
(_, None) => "Select first corner of table cell range:".into(),
_ => "Select second corner of table cell range:".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;
}
let at = |h: Handle, p: DVec3| format!("{:X} {} {} {}", h.value(), p.x, p.y, p.z);
match (self.function, self.first) {
("Cell", _) => CmdResult::Dispatch(format!("_FIELD_CELL Cell {}", at(handle, pt))),
(_, None) => {
self.first = Some((handle, pt));
CmdResult::NeedPoint
}
(function, Some((first, first_pt))) => CmdResult::Dispatch(format!(
"_FIELD_CELL {function} {} {}",
at(first, first_pt),
at(handle, pt)
)),
}
}
fn on_point(&mut self, _pt: DVec3) -> CmdResult {
CmdResult::NeedPoint
}
fn on_enter(&mut self) -> CmdResult {
CmdResult::Dispatch("_FIELD_CELL".into())
}
fn on_escape(&mut self) -> CmdResult {
CmdResult::Dispatch("_FIELD_CELL".into())
}
}
inventory::submit!(crate::command::CommandRegistration { names: &["FIELD"] });
+107 -8
View File
@@ -32,7 +32,7 @@ pub const CATEGORIES: &[(&str, &[&str])] = &[
&["Author", "Comments", "Filename", "Filesize", "HyperlinkBase", "Keywords", "LastSavedBy", "Subject", "Title"],
),
("Linked", &["Hyperlink"]),
("Objects", &["Formula", "NamedObject", "Object"]),
("Objects", &["BlockPlaceholder", "Formula", "NamedObject", "Object"]),
("Other", &["DieselExpression", "SystemVariable"]),
(
"Plot",
@@ -77,6 +77,7 @@ pub enum FieldKind {
Formula,
Hyperlink,
PlotScale,
BlockPlaceholder,
}
/// Formula precisions: the current one, then 0 to 8 decimals.
@@ -95,6 +96,7 @@ pub fn kind_of(name: &str) -> FieldKind {
"Formula" => FieldKind::Formula,
"Hyperlink" => FieldKind::Hyperlink,
"PlotScale" => FieldKind::PlotScale,
"BlockPlaceholder" => FieldKind::BlockPlaceholder,
_ => FieldKind::Text,
}
}
@@ -128,6 +130,11 @@ pub struct FieldDialogState {
pub hyperlink_url: String,
/// Index into codec PLOT_SCALE_FORMATS.
pub plot_scale: usize,
/// The block being edited in the block editor; block placeholders are
/// only offered there.
pub placeholder_block: Option<String>,
/// Index into codec BLOCK_PLACEHOLDER_PROPERTIES.
pub placeholder_property: usize,
pub preview: String,
/// Today in each of DATE_FORMATS, for the Examples list.
pub examples: Vec<String>,
@@ -158,6 +165,8 @@ impl FieldDialogState {
hyperlink_text: String::new(),
hyperlink_url: String::new(),
plot_scale: 0,
placeholder_block: None,
placeholder_property: 7,
preview: String::new(),
examples: Vec::new(),
}
@@ -195,10 +204,11 @@ impl FieldDialogState {
0 => String::new(),
n => format!("%pr{}", n - 1),
};
let body = format!("\\AcExpr ({})", self.formula.trim());
let (formula, objects) = indexed_object_ids(self.formula.trim());
let body = format!("\\AcExpr ({formula})");
match format!("{format}{precision}") {
f if f.is_empty() => (body, vec![]),
f => (format!("{body} \\f \"{f}\""), vec![]),
f if f.is_empty() => (body, objects),
f => (format!("{body} \\f \"{f}\""), objects),
}
}
FieldKind::Hyperlink => (
@@ -211,6 +221,21 @@ impl FieldDialogState {
.map_or_else(|| "\\AcVar PlotScale".to_string(), |f| f.1.to_string()),
vec![],
),
FieldKind::BlockPlaceholder => {
let (_, property, format) = codec::fields::BLOCK_PLACEHOLDER_PROPERTIES[self.placeholder_property];
// Text properties take the chosen case; the others keep their own format.
let format = match (format, self.text_case) {
("%tc4", 0) => String::new(),
("%tc4", n) => format!("%tc{n}"),
(other, _) => other.to_string(),
};
let code = format!("\\AcObjProp.16.2 Object(?BlockRefId,1).{property}");
if format.is_empty() {
(code, vec![])
} else {
(format!("{code} \\f \"{format}\""), vec![])
}
}
FieldKind::SystemVariable => (format!("\\AcVar {}", self.sysvar), vec![]),
FieldKind::Diesel => (format!("\\AcDiesel {}", self.diesel), vec![]),
FieldKind::NamedObject => match self.named.and_then(|i| self.named_names.get(i)) {
@@ -235,11 +260,35 @@ impl FieldDialogState {
FieldKind::Diesel => !self.diesel.trim().is_empty(),
FieldKind::Formula => !self.formula.trim().is_empty(),
FieldKind::Hyperlink => !self.hyperlink_url.trim().is_empty(),
FieldKind::BlockPlaceholder => self.placeholder_block.is_some(),
_ => true,
}
}
}
/// Table references in a formula are typed as `%<\_ObjId HANDLE>%`; the stored
/// code numbers them (`%<\_ObjIdx n>%`) and lists the objects.
fn indexed_object_ids(formula: &str) -> (String, Vec<codec::Handle>) {
let mut out = String::new();
let mut objects: Vec<codec::Handle> = Vec::new();
let mut rest = formula;
while let Some(start) = rest.find("%<\\_ObjId ") {
let after = &rest[start + "%<\\_ObjId ".len()..];
let Some(end) = after.find(">%") else { break };
let Ok(value) = u64::from_str_radix(after[..end].trim(), 16) else { break };
let handle = codec::Handle::new(value);
let index = objects.iter().position(|h| *h == handle).unwrap_or_else(|| {
objects.push(handle);
objects.len() - 1
});
out.push_str(&rest[..start]);
out.push_str(&format!("%<\\_ObjIdx {index}>%"));
rest = &after[end + 2..];
}
out.push_str(rest);
(out, objects)
}
/// The geometric properties a field can show for an object of this type.
pub fn object_properties(entity: &codec::EntityType) -> Vec<&'static str> {
use codec::EntityType as E;
@@ -274,6 +323,9 @@ pub enum FieldDialogMsg {
FormulaFormat(usize),
FormulaPrecision(usize),
Evaluate,
/// Sum / Average / Count of a cell range, or one Cell, picked in a table.
TableFunction(&'static str),
PlaceholderProperty(usize),
HyperlinkText(String),
HyperlinkUrl(String),
BrowseHyperlink,
@@ -334,6 +386,14 @@ impl IntoContainer for text_input::Style {
}
}
fn table_button<'a>(label: String, function: &'static str) -> Element<'a, Message> {
button(text(label).size(12))
.on_press(msg(FieldDialogMsg::TableFunction(function)))
.style(button_style(false))
.padding([5, 10])
.into()
}
fn case_list<'a>(state: &FieldDialogState) -> Element<'a, Message> {
list(
TEXT_CASES
@@ -529,6 +589,36 @@ fn format_panel<'a>(state: &'a FieldDialogState) -> Element<'a, Message> {
]
.spacing(8)
.into(),
FieldKind::BlockPlaceholder => match &state.placeholder_block {
None => column![text(t!("Only accessible in the block editor")).size(12).style(muted_style)].into(),
Some(block) => {
let (_, _, format) = codec::fields::BLOCK_PLACEHOLDER_PROPERTIES[state.placeholder_property];
let mut panel = column![
row![
text(t!("Block name")).size(12).width(Length::Fixed(110.0)),
container(text(block.clone()).size(12).style(muted_style)).padding([5, 8]).width(Fill),
]
.spacing(8)
.align_y(iced::Center),
text(t!("Block reference property")).size(12),
list(
codec::fields::BLOCK_PLACEHOLDER_PROPERTIES
.iter()
.enumerate()
.map(|(i, (label, _, _))| {
(t!(*label).into_owned(), state.placeholder_property == i, FieldDialogMsg::PlaceholderProperty(i))
})
.collect(),
150.0,
),
]
.spacing(8);
if format == "%tc4" {
panel = panel.push(text(t!("Format")).size(12)).push(case_list(state));
}
panel.into()
}
},
FieldKind::PlotScale => column![
text(t!("Format")).size(12),
list(
@@ -617,10 +707,18 @@ pub fn view<'a>(
.font(iced::Font::MONOSPACE)
.style(field_style)
.on_input(|v| msg(FieldDialogMsg::Formula(v))),
button(text(t!("Evaluate")).size(12))
.on_press(msg(FieldDialogMsg::Evaluate))
.style(button_style(false))
.padding([5, 14]),
row![
table_button(t!("Average").into_owned(), "Average"),
table_button(t!("Sum").into_owned(), "Sum"),
table_button(t!("Count").into_owned(), "Count"),
table_button(t!("Cell").into_owned(), "Cell"),
Space::new().width(Fill),
button(text(t!("Evaluate")).size(12))
.on_press(msg(FieldDialogMsg::Evaluate))
.style(button_style(false))
.padding([5, 14]),
]
.spacing(6),
]
.spacing(8),
),
@@ -629,6 +727,7 @@ pub fn view<'a>(
]
.spacing(10),
FieldKind::Hyperlink => column![card(t!("Hyperlink").into_owned(), format_panel(state)), preview].spacing(10),
FieldKind::BlockPlaceholder => column![card(t!("Block placeholder").into_owned(), format_panel(state)), preview].spacing(10),
_ => column![card(t!("Format").into_owned(), format_panel(state)), preview].spacing(10),
};
let (code, _) = state.code();