forked from 1b-agent/opencadstudio-upstream
feat(blocks): author Block Properties Table from the block editor
Add the BPROPERTIESTABLE command, the Block Authoring palette row and the grid dialog. The row needs an open block edit session with a Linear or Polar parameter column; OK parses cells to finite numbers, re-checks the table against the session it was opened from, writes it in one undo step, and no-op edits close without an undo.
This commit is contained in:
1 parent
2b0c1a6b58
commit
a1de7e4ecd
13 files changed
+865
-4
No files matched your search
@@ -0,0 +1,127 @@
|
||||
use super::{Message, ModalKind, OpenCADStudio};
|
||||
use crate::scene::block_properties_table as table;
|
||||
use crate::ui::window::block_properties_table::{Action, State};
|
||||
use iced::Task;
|
||||
|
||||
impl OpenCADStudio {
|
||||
pub(in crate::app) fn properties_table_error(&self) -> Option<&'static str> {
|
||||
let tab = &self.tabs[self.active_tab];
|
||||
let Some(session) = tab.active_block_edit_session() else {
|
||||
return Some("Open a block in the Block Editor before editing its Block Properties Table.");
|
||||
};
|
||||
let definition = session.br_handle;
|
||||
if table::columns(&tab.scene.document, definition).is_empty() {
|
||||
return Some(
|
||||
"Place a Linear or Polar parameter before editing the Block Properties Table.",
|
||||
);
|
||||
}
|
||||
table::table(&tab.scene.document, definition).err()
|
||||
}
|
||||
pub(in crate::app) fn open_block_properties_table(&mut self) -> Task<Message> {
|
||||
if let Some(error) = self.properties_table_error() {
|
||||
self.command_line.push_error(crate::t!(error).as_ref());
|
||||
return Task::none();
|
||||
}
|
||||
let tab = &self.tabs[self.active_tab];
|
||||
let definition = tab.active_block_edit_session().unwrap().br_handle;
|
||||
let original = match table::table(&tab.scene.document, definition) {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
self.command_line.push_error(crate::t!(error).as_ref());
|
||||
return Task::none();
|
||||
}
|
||||
};
|
||||
let (_, columns, rows) = match table::grid_table(&tab.scene.document, definition) {
|
||||
Ok(Some(value)) => value,
|
||||
Ok(None) => (codec::Handle::NULL, vec![], vec![]),
|
||||
Err(error) => {
|
||||
self.command_line.push_error(crate::t!(error).as_ref());
|
||||
return Task::none();
|
||||
}
|
||||
};
|
||||
self.block_properties_table = Some(State {
|
||||
document: tab.id,
|
||||
definition,
|
||||
epoch: tab.scene.geometry_epoch,
|
||||
original,
|
||||
columns,
|
||||
properties: table::columns(&tab.scene.document, definition),
|
||||
selected_property: None,
|
||||
rows,
|
||||
error: None,
|
||||
notice: None,
|
||||
});
|
||||
self.active_modal = Some(ModalKind::BlockPropertiesTable);
|
||||
Task::none()
|
||||
}
|
||||
pub(super) fn handle_block_properties_table(&mut self, action: Action) -> Task<Message> {
|
||||
if !matches!(action, Action::Apply) {
|
||||
if let Some(state) = self.block_properties_table.as_mut() {
|
||||
state.edit(action);
|
||||
}
|
||||
return Task::none();
|
||||
}
|
||||
let Some(state) = self.block_properties_table.as_ref() else {
|
||||
return Task::none();
|
||||
};
|
||||
let mut cells = Vec::with_capacity(state.rows.len());
|
||||
for row in &state.rows {
|
||||
let mut parsed = Vec::with_capacity(row.len());
|
||||
for value in row {
|
||||
match value.trim().parse::<f64>() {
|
||||
Ok(value) if value.is_finite() => parsed.push(value),
|
||||
_ => {
|
||||
self.block_properties_table.as_mut().unwrap().error = Some(
|
||||
crate::t!("Each cell must be a finite number.").into_owned(),
|
||||
);
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
}
|
||||
cells.push(parsed);
|
||||
}
|
||||
let i = self.active_tab;
|
||||
let tab = &self.tabs[i];
|
||||
let stale = tab.id != state.document
|
||||
|| tab.active_block_edit_session().map(|s| s.br_handle) != Some(state.definition)
|
||||
|| tab.scene.geometry_epoch != state.epoch
|
||||
|| table::table(&tab.scene.document, state.definition)
|
||||
.ok()
|
||||
.flatten()
|
||||
.as_ref()
|
||||
!= state.original.as_ref();
|
||||
if stale {
|
||||
self.block_properties_table.as_mut().unwrap().error = Some(
|
||||
crate::t!("The block changed. Cancel and reopen the Block Properties Table.")
|
||||
.into_owned(),
|
||||
);
|
||||
return Task::none();
|
||||
}
|
||||
let unchanged = state.original.as_ref().is_some_and(|view| {
|
||||
view.columns == state.columns
|
||||
&& view.rows.iter().eq(cells.iter())
|
||||
});
|
||||
if unchanged {
|
||||
self.block_properties_table = None;
|
||||
self.active_modal = None;
|
||||
return Task::none();
|
||||
}
|
||||
let mut working = tab.scene.document.clone();
|
||||
if let Err(error) =
|
||||
table::apply(&mut working, state.definition, &state.columns, &cells)
|
||||
{
|
||||
self.block_properties_table.as_mut().unwrap().error =
|
||||
Some(crate::t!(error).into_owned());
|
||||
return Task::none();
|
||||
}
|
||||
self.push_undo_snapshot(i, "Block Properties Table");
|
||||
self.tabs[i].scene.document = working;
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].dirty = true;
|
||||
self.finish_pending_history(i);
|
||||
self.refresh_properties();
|
||||
self.block_properties_table = None;
|
||||
self.active_modal = None;
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
@@ -412,6 +412,9 @@ impl OpenCADStudio {
|
||||
cmd if cmd == "BLOOKUPTABLE" || cmd.starts_with("BLOOKUPTABLE ") => {
|
||||
Some(self.open_lookup_table(cmd.strip_prefix("BLOOKUPTABLE").unwrap().trim()))
|
||||
}
|
||||
cmd if cmd == "BPROPERTIESTABLE" || cmd.starts_with("BPROPERTIESTABLE ") => {
|
||||
Some(self.open_block_properties_table())
|
||||
}
|
||||
cmd if cmd == "BPARAMETER" || cmd.starts_with("BPARAMETER ") => {
|
||||
let Some(block) = self.visibility_block(i, "BPARAMETER") else {
|
||||
return Some(Task::none());
|
||||
|
||||
@@ -218,6 +218,12 @@ impl OpenCADStudio {
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
if cmd == "BPROPERTIESTABLE" || cmd.starts_with("BPROPERTIESTABLE ") {
|
||||
if let Some(error) = self.properties_table_error() {
|
||||
self.command_line.push_error(crate::t!(error).as_ref());
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
if self.tabs[i].block_test.is_some() {
|
||||
let name = cmd.split([' ', ':']).next().unwrap_or(cmd);
|
||||
if matches!(
|
||||
|
||||
@@ -1456,6 +1456,83 @@ fn lookup_basepoint_palette_and_table_dialog_real_controls() {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_properties_table_palette_dialog_authors_rows() {
|
||||
with_stack(|| {
|
||||
use crate::ui::window::{authoring_palettes::Tab, block_properties_table::Action as A};
|
||||
let mut app = authoring_app();
|
||||
let _ = app.dispatch_command("BAUTHORPALETTE");
|
||||
let _ = app.run_command_line("BPARAMETER LINEAR 0,0 1,0 0,0.3");
|
||||
let point = authoring_control(&app, Tab::Actions.widget_id());
|
||||
authoring_click(&mut app, point);
|
||||
let point = authoring_control(&app, "authoring-tool-properties-table");
|
||||
authoring_click(&mut app, point);
|
||||
assert_eq!(
|
||||
app.active_modal,
|
||||
Some(crate::app::ModalKind::BlockPropertiesTable)
|
||||
);
|
||||
let _ = app.update(Message::BlockPropertiesTable(A::Property(0)));
|
||||
let point = find_in_view(&app, "Add Property");
|
||||
authoring_click(&mut app, point);
|
||||
let point = find_in_view(&app, "Add Row");
|
||||
authoring_click(&mut app, point);
|
||||
assert_eq!(
|
||||
app.block_properties_table.as_ref().unwrap().rows.len(),
|
||||
1
|
||||
);
|
||||
let _ = app.update(Message::BlockPropertiesTable(A::Cell(0, 0, "2.5".into())));
|
||||
let point = find_in_view(&app, "OK");
|
||||
authoring_click(&mut app, point);
|
||||
assert!(app.block_properties_table.is_none());
|
||||
assert!(app.active_modal.is_none());
|
||||
let i = app.active_tab;
|
||||
let definition = app.tabs[i].active_block_edit_session().unwrap().br_handle;
|
||||
let view =
|
||||
crate::scene::block_properties_table::table(&app.tabs[i].scene.document, definition)
|
||||
.unwrap()
|
||||
.expect("the table was written");
|
||||
assert_eq!(view.columns.len(), 1);
|
||||
assert_eq!(view.rows, vec![vec![2.5]]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_properties_table_apply_writes_undo_once_and_stale_edits_stay_open() {
|
||||
with_stack(|| {
|
||||
use crate::ui::window::block_properties_table::Action as A;
|
||||
let mut app = authoring_app();
|
||||
let _ = app.dispatch_command("BAUTHORPALETTE");
|
||||
let _ = app.run_command_line("BPARAMETER LINEAR 0,0 1,0 0,0.3");
|
||||
let i = app.active_tab;
|
||||
let history = app.tabs[i].history.undo_stack.len();
|
||||
let _ = app.dispatch_command("BPROPERTIESTABLE");
|
||||
assert_eq!(
|
||||
app.active_modal,
|
||||
Some(crate::app::ModalKind::BlockPropertiesTable)
|
||||
);
|
||||
let _ = app.update(Message::BlockPropertiesTable(A::Property(0)));
|
||||
let _ = app.update(Message::BlockPropertiesTable(A::AddProperty));
|
||||
let _ = app.update(Message::BlockPropertiesTable(A::AddRow));
|
||||
let _ = app.update(Message::BlockPropertiesTable(A::Apply));
|
||||
assert!(app.block_properties_table.is_none());
|
||||
assert_eq!(app.tabs[i].history.undo_stack.len(), history + 1);
|
||||
let _ = app.dispatch_command("BPROPERTIESTABLE");
|
||||
assert!(app.block_properties_table.is_some());
|
||||
let _ = app.update(Message::BlockPropertiesTable(A::Apply));
|
||||
assert!(app.block_properties_table.is_none());
|
||||
assert_eq!(app.tabs[i].history.undo_stack.len(), history + 1);
|
||||
let _ = app.dispatch_command("BPROPERTIESTABLE");
|
||||
app.tabs[i].scene.bump_geometry();
|
||||
let _ = app.update(Message::BlockPropertiesTable(A::AddRow));
|
||||
let _ = app.update(Message::BlockPropertiesTable(A::Apply));
|
||||
assert!(app.block_properties_table.is_some());
|
||||
assert!(app.block_properties_table.as_ref().unwrap().error.is_some());
|
||||
let _ = app.update(Message::CloseModal);
|
||||
assert!(app.block_properties_table.is_none());
|
||||
assert!(app.active_modal.is_none());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn authoring_palettes_ribbon_toggle_and_dock_close_leave_document_and_history_unchanged() {
|
||||
use crate::ui::dock::{DockMsg, PanelId};
|
||||
|
||||
@@ -411,6 +411,7 @@ impl OpenCADStudio {
|
||||
self.point_parameter_placement_error(),
|
||||
self.parameter_placement_error("FLIP"),
|
||||
self.lookup_table_error(),
|
||||
self.properties_table_error(),
|
||||
self.action_error(Some(crate::scene::block_actions::Kind::Move)),
|
||||
self.action_error(Some(crate::scene::block_actions::Kind::Scale)),
|
||||
self.action_error(Some(crate::scene::block_actions::Kind::Rotate)),
|
||||
|
||||
@@ -14,6 +14,7 @@ mod block_editor_tests;
|
||||
mod block_save_as;
|
||||
mod block_visibility;
|
||||
mod block_lookup;
|
||||
mod block_properties_table;
|
||||
mod block_actions;
|
||||
mod block_authoring_edit;
|
||||
mod block_properties;
|
||||
@@ -597,6 +598,7 @@ pub(super) struct OpenCADStudio {
|
||||
block_save_as: Option<crate::ui::window::block_save_as::State>,
|
||||
block_visibility: Option<crate::ui::window::block_visibility::State>,
|
||||
block_lookup: Option<crate::ui::window::block_lookup::State>,
|
||||
block_properties_table: Option<crate::ui::window::block_properties_table::State>,
|
||||
block_visibility_mode: bool,
|
||||
/// PDF dialogs' working copies; `None` while closed.
|
||||
pdf_attach: Option<crate::ui::window::pdf_dialogs::PdfAttachState>,
|
||||
@@ -2300,6 +2302,7 @@ pub enum ModalKind {
|
||||
BlockSaveAs,
|
||||
BlockVisibility,
|
||||
BlockLookup,
|
||||
BlockPropertiesTable,
|
||||
SaveDialog,
|
||||
Recovery,
|
||||
/// Fonts referenced by the drawing are missing on this machine.
|
||||
@@ -2955,6 +2958,7 @@ pub enum Message {
|
||||
BlockSaveAsApply,
|
||||
BlockVisibility(crate::ui::window::block_visibility::Action),
|
||||
BlockLookup(crate::ui::window::block_lookup::Action),
|
||||
BlockPropertiesTable(crate::ui::window::block_properties_table::Action),
|
||||
BlockEditPickerName(String),
|
||||
BlockEditPickerSelect(String),
|
||||
BlockEditPickerApply,
|
||||
@@ -4739,6 +4743,7 @@ impl OpenCADStudio {
|
||||
block_save_as: None,
|
||||
block_visibility: None,
|
||||
block_lookup: None,
|
||||
block_properties_table: None,
|
||||
block_visibility_mode: false,
|
||||
pdf_attach: None,
|
||||
point_cloud_attach: None,
|
||||
|
||||
@@ -238,6 +238,7 @@ impl OpenCADStudio {
|
||||
Some(BlockSaveAs) => self.block_save_as = None,
|
||||
Some(BlockVisibility) => self.block_visibility = None,
|
||||
Some(BlockLookup) => self.block_lookup = None,
|
||||
Some(BlockPropertiesTable) => self.block_properties_table = None,
|
||||
Some(PdfAttach) => self.pdf_attach = None,
|
||||
Some(UnderlayLayers) => self.underlay_layers = None,
|
||||
Some(PdfImportSettings) => self.pdf_import_settings = None,
|
||||
@@ -9739,6 +9740,9 @@ impl OpenCADStudio {
|
||||
if self.active_modal == Some(super::ModalKind::BlockLookup) {
|
||||
self.block_lookup = None;
|
||||
}
|
||||
if self.active_modal == Some(super::ModalKind::BlockPropertiesTable) {
|
||||
self.block_properties_table = None;
|
||||
}
|
||||
if self.active_modal == Some(super::ModalKind::BlockVisibility) {
|
||||
self.block_visibility = None;
|
||||
}
|
||||
@@ -10764,6 +10768,7 @@ impl OpenCADStudio {
|
||||
Message::BlockSaveAsApply => self.apply_block_save_as_dialog(),
|
||||
Message::BlockVisibility(action) => self.handle_block_visibility(action),
|
||||
Message::BlockLookup(action) => self.handle_block_lookup(action),
|
||||
Message::BlockPropertiesTable(action) => self.handle_block_properties_table(action),
|
||||
Message::BlockEditPickerName(name) => {
|
||||
if let Some(state) = self.block_edit_picker.as_mut() {
|
||||
state.name = name.clone();
|
||||
|
||||
@@ -90,6 +90,7 @@ impl OpenCADStudio {
|
||||
Some(K::BlockEditPicker) => crate::t!("Edit Block Definition").into_owned(),
|
||||
Some(K::BlockVisibility) => crate::t!("Visibility States").into_owned(),
|
||||
Some(K::BlockLookup) => crate::t!("Property Lookup Table").into_owned(),
|
||||
Some(K::BlockPropertiesTable) => crate::t!("Block Properties Table").into_owned(),
|
||||
Some(K::BlockSaveAs) => crate::t!("Save Block As").into_owned(),
|
||||
Some(K::PointStyle) => crate::tr!("modal", "point-style"),
|
||||
Some(K::AttributeEditor) => crate::tr!("modal", "attribute-editor"),
|
||||
@@ -530,6 +531,12 @@ impl OpenCADStudio {
|
||||
crate::ui::window::block_lookup::view(state, flow)
|
||||
})
|
||||
}
|
||||
super::super::ModalKind::BlockPropertiesTable => {
|
||||
let state = self.block_properties_table.as_ref()?;
|
||||
sized_flow(ex, 940, 600, |flow| {
|
||||
crate::ui::window::block_properties_table::view(state, flow)
|
||||
})
|
||||
}
|
||||
super::super::ModalKind::BlockVisibility => {
|
||||
let state = self.block_visibility.as_ref()?;
|
||||
sized_flow(ex, 580, 470, |flow| {
|
||||
|
||||
Reference in new issue
Block a user