forked from 1b-agent/opencadstudio-upstream
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:
commit
29ed1cae9b
29 files changed
+524
-12
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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(),
|
||||
}
|
||||
}
|
||||
@@ -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")),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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")),
|
||||
|
||||
@@ -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"] });
|
||||
@@ -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();
|
||||
|
||||
Reference in new issue
Block a user