diff --git a/src/app/block_editor.rs b/src/app/block_editor.rs index cf5a56be1..7b4359103 100644 --- a/src/app/block_editor.rs +++ b/src/app/block_editor.rs @@ -563,6 +563,7 @@ impl OpenCADStudio { self.grip_hover = None; self.grip_popup = None; self.visibility_popup = None; + self.table_popup = None; history.checkpoint = self.block_edit_checkpoint(i); self.tabs[i].block_edit_history = Some(history); self.tabs[i].scene.bump_geometry(); diff --git a/src/app/block_test.rs b/src/app/block_test.rs index 2ac5f2456..39a940d62 100644 --- a/src/app/block_test.rs +++ b/src/app/block_test.rs @@ -134,6 +134,7 @@ impl OpenCADStudio { tab.scene.set_current_layout("Model".into()); self.discard_block_tests(source_id); self.visibility_popup = None; + self.table_popup = None; self.grip_popup = None; self.grip_hover = None; self.tabs.push(tab); diff --git a/src/app/command_driver/utilities.rs b/src/app/command_driver/utilities.rs index a850e3283..52fe9ec47 100644 --- a/src/app/command_driver/utilities.rs +++ b/src/app/command_driver/utilities.rs @@ -105,6 +105,7 @@ impl OpenCADStudio { self.grip_popup = None; self.grip_pending = None; self.visibility_popup = None; + self.table_popup = None; self.hover_dwell = None; self.ucs_grip_drag = None; self.ucs_icon_selected = false; diff --git a/src/app/commands/inquiry.rs b/src/app/commands/inquiry.rs index 9adf0b41e..292d993ab 100644 --- a/src/app/commands/inquiry.rs +++ b/src/app/commands/inquiry.rs @@ -1064,6 +1064,7 @@ impl OpenCADStudio { self.grip_hover = None; self.grip_popup = None; self.visibility_popup = None; + self.table_popup = None; // Entering BEDIT changes which block is assembled, not the // cached geometry of that block's entities. self.tabs[i].scene.bump_geometry_no_blocks(); @@ -1098,6 +1099,7 @@ impl OpenCADStudio { self.grip_hover = None; self.grip_popup = None; self.visibility_popup = None; + self.table_popup = None; let parent_index = return_block.and_then(|parent_name| { self.tabs[i] diff --git a/src/app/document.rs b/src/app/document.rs index 7ce08dc83..1aa93dcc3 100644 --- a/src/app/document.rs +++ b/src/app/document.rs @@ -128,6 +128,7 @@ pub(super) struct DocumentTab { /// must never target a tab by its transient vector index. pub(super) id: u64, pub(super) scene: Scene, + pub(super) table_grip: Option, pub(super) current_path: Option, /// Persistent native edit lease for the drawing currently at /// `current_path`. The sidecar portion survives atomic file replacement. @@ -660,6 +661,7 @@ impl DocumentTab { selected_handle: None, selected_authoring: None, visibility_grip: None, + table_grip: None, wireframe: false, render_mode: codec::entities::ViewportRenderMode::Wireframe2D, visual_style: "Wireframe 2D".into(), diff --git a/src/app/mod.rs b/src/app/mod.rs index 1b1839b23..95ea8adf0 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -8,21 +8,21 @@ pub(crate) mod config; #[cfg(not(target_arch = "wasm32"))] pub use automation::{export_headless, serve}; mod annotation_data; +mod block_actions; +mod block_authoring_edit; mod block_editor; #[cfg(test)] 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; +mod block_properties_table; #[cfg(test)] mod block_properties_tests; +mod block_save_as; mod block_test; #[cfg(test)] mod block_test_tests; +mod block_visibility; mod command_driver; pub(crate) use command_driver::{ copy_to_clipboard_kernel, define_block_raw_doc, paste_entities_kernel, PasteSpace, @@ -904,6 +904,7 @@ pub(super) struct OpenCADStudio { grip_pending: Option, /// Open dynamic-block visibility-state dropdown. visibility_popup: Option, + table_popup: Option, /// A leader line just added via the "Add Leader" grip menu whose arrow is /// being placed (follows the cursor). `(entity handle, new-arrow grip id)`. /// Esc before the placement click removes it again. @@ -3376,6 +3377,7 @@ pub enum Message { /// User picked a dynamic-block visibility state — index into the /// visibility dropdown's items. VisibilityPick(usize), + TableRowPick(usize), /// Timer pulse while the cursor is dwelling on a grip; drives the /// dwell-to-popup transition without requiring further mouse motion. GripDwellTick, @@ -4894,6 +4896,7 @@ impl OpenCADStudio { grip_popup: None, grip_pending: None, visibility_popup: None, + table_popup: None, grip_add_provisional: None, grip_preview_handles: Vec::new(), hover_dwell: None, diff --git a/src/app/update/command.rs b/src/app/update/command.rs index 77ecfaf1a..e9bb7520e 100644 --- a/src/app/update/command.rs +++ b/src/app/update/command.rs @@ -194,6 +194,7 @@ impl OpenCADStudio { self.cancel_active_grip_edit(); self.grip_pending = None; self.visibility_popup = None; + self.table_popup = None; self.grip_popup = None; self.grip_hover = None; } @@ -1354,7 +1355,11 @@ impl OpenCADStudio { self.grip_hover = None; return Task::none(); } + if self.table_popup.take().is_some() { + return Task::none(); + } if self.visibility_popup.take().is_some() { + self.table_popup = None; return Task::none(); } if self.tabs[self.active_tab].active_grip.is_some() { @@ -3939,6 +3944,9 @@ impl OpenCADStudio { } pub(super) fn on_prop_geom_commit(&mut self, field: &'static str) -> Task { + if field == "authoring_name" { + return self.commit_authoring_name(); + } if field.starts_with("bedit_") { return self.commit_block_property(field); } diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index 76155dd87..c4157b530 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -1,6 +1,6 @@ -use crate::io::plot_style::PlotStyleTableExt as _; use super::{ArrowKey, Message, OpenCADStudio, TextEntryMode}; use crate::command::CadCommand; +use crate::io::plot_style::PlotStyleTableExt as _; use crate::scene::VIEWCUBE_DRAW_PX; use crate::ui::PropertiesPanel; use iced::time::Instant; @@ -4575,7 +4575,9 @@ impl OpenCADStudio { // CATIA navigation: the left button pressed during a middle drag // turns the drag into an orbit instead of starting a pick. - Message::ViewportLeftPressAt(_) | Message::ViewportLeftPress | Message::PanePress(_) + Message::ViewportLeftPressAt(_) + | Message::ViewportLeftPress + | Message::PanePress(_) if self.navigation_style == crate::app::settings::NavigationStyle::Catia && self.tabs[self.active_tab] .scene @@ -4999,6 +5001,12 @@ impl OpenCADStudio { } Task::none() } + Message::TableRowPick(idx) => { + if let Some(popup) = self.table_popup.take() { + self.apply_table_row(popup.insert_handle, idx); + } + Task::none() + } Message::GripMenuPick(idx) => self.on_grip_menu_pick(idx), @@ -6782,6 +6790,30 @@ impl OpenCADStudio { self.delete_parametric_constraint(id); return Task::none(); } + if let (Some(handle), Some(session)) = ( + self.tabs[i].selected_authoring, + self.tabs[i].active_block_edit_session(), + ) { + let definition = session.br_handle; + let mut staged = self.tabs[i].scene.document.clone(); + match crate::scene::block_authoring::edit::remove_parameter( + &mut staged, + definition, + handle, + ) { + Ok(()) => { + self.push_undo_snapshot(i, "Delete block parameter"); + self.tabs[i].scene.document = staged; + self.tabs[i].selected_authoring = None; + self.tabs[i].scene.bump_geometry(); + self.tabs[i].dirty = true; + self.finish_pending_history(i); + self.refresh_properties(); + } + Err(message) => self.command_line.push_error(crate::t!(message).as_ref()), + } + return Task::none(); + } let handles: Vec<_> = self.tabs[i].scene.selected.iter().cloned().collect(); if !handles.is_empty() { // Erase is delta-safe unless a target is in a group (group @@ -9392,12 +9424,14 @@ impl OpenCADStudio { Message::ToggleDynamicUcs => { self.dynamic_ucs = !self.dynamic_ucs; - self.command_line.push_output(if self.dynamic_ucs { - crate::t!("Dynamic UCS: on") - } else { - crate::t!("Dynamic UCS: off") - } - .as_ref()); + self.command_line.push_output( + if self.dynamic_ucs { + crate::t!("Dynamic UCS: on") + } else { + crate::t!("Dynamic UCS: off") + } + .as_ref(), + ); self.persist_settings_if_changed(); Task::none() } @@ -9780,9 +9814,7 @@ impl OpenCADStudio { // Closing the attribute manager is its Cancel: live changes // are taken back. if self.active_modal == Some(super::ModalKind::AttMan) { - return self.on_attman( - crate::ui::window::attman_dialog::AttManMsg::Cancel, - ); + return self.on_attman(crate::ui::window::attman_dialog::AttManMsg::Cancel); } if matches!( self.active_modal, diff --git a/src/app/update/viewport.rs b/src/app/update/viewport.rs index fd0e3f725..88b807665 100644 --- a/src/app/update/viewport.rs +++ b/src/app/update/viewport.rs @@ -66,26 +66,51 @@ mod selection_gesture_priority_tests { fn fast_first_click_never_becomes_a_lasso_solely_from_cursor_speed() { assert!(!selection_drag_should_start(25.0 * 25.0, 20, false)); assert!(!selection_drag_should_start(120.0 * 120.0, 80, false)); - assert!(!selection_drag_should_start(500.0 * 500.0, POLY_START_DELAY_MS - 1, false)); - assert!(selection_drag_should_start(25.0 * 25.0, POLY_START_DELAY_MS, false)); - assert!(!selection_drag_should_start(11.0 * 11.0, POLY_START_DELAY_MS + 500, false)); + assert!(!selection_drag_should_start( + 500.0 * 500.0, + POLY_START_DELAY_MS - 1, + false + )); + assert!(selection_drag_should_start( + 25.0 * 25.0, + POLY_START_DELAY_MS, + false + )); + assert!(!selection_drag_should_start( + 11.0 * 11.0, + POLY_START_DELAY_MS + 500, + false + )); } #[test] fn pickdrag_rectangle_keeps_its_fast_drag_shortcut() { assert!(selection_drag_should_start(25.0 * 25.0, 20, true)); assert!(!selection_drag_should_start(23.0 * 23.0, 20, true)); - assert!(selection_drag_should_start(13.0 * 13.0, POLY_START_DELAY_MS, true)); + assert!(selection_drag_should_start( + 13.0 * 13.0, + POLY_START_DELAY_MS, + true + )); } #[test] fn quick_selection_click_anchors_where_the_button_went_down() { let start = Point::new(100.0, 100.0); let moved = Point::new(190.0, 135.0); - assert_eq!(selection_click_position(moved, Some(start), true, true), start); + assert_eq!( + selection_click_position(moved, Some(start), true, true), + start + ); // During a command or a genuine press-drag, use the moving cursor. - assert_eq!(selection_click_position(moved, Some(start), true, false), moved); - assert_eq!(selection_click_position(moved, Some(start), false, true), moved); + assert_eq!( + selection_click_position(moved, Some(start), true, false), + moved + ); + assert_eq!( + selection_click_position(moved, Some(start), false, true), + moved + ); assert_eq!(selection_click_position(moved, None, true, true), moved); } } @@ -993,6 +1018,7 @@ impl OpenCADStudio { .filter(|(owner, grip)| self.tabs[i].hot_grips.contains(&(*owner, grip.id))) .filter(|(_, grip)| { grip.id != crate::app::visibility::VIS_GRIP_ID + && grip.id != crate::app::visibility::TABLE_GRIP_ID && !crate::app::block_actions::is_test_grip(grip.id) && !crate::app::block_authoring_edit::is_editor_grip(grip.id) }) @@ -1019,6 +1045,7 @@ impl OpenCADStudio { .zip(self.tabs[i].selected_grips.iter()) .filter(|(_, grip)| { grip.id != crate::app::visibility::VIS_GRIP_ID + && grip.id != crate::app::visibility::TABLE_GRIP_ID && !crate::app::block_actions::is_test_grip(grip.id) && !crate::app::block_authoring_edit::is_editor_grip(grip.id) }) @@ -1163,6 +1190,7 @@ impl OpenCADStudio { // hover grip-menu. if self.grip_popup.is_none() && grip_id != crate::app::visibility::VIS_GRIP_ID + && grip_id != crate::app::visibility::TABLE_GRIP_ID && !crate::app::block_actions::is_test_grip(grip_id) && !crate::app::block_authoring_edit::is_editor_grip(grip_id) && self @@ -2018,11 +2046,11 @@ impl OpenCADStudio { if axis_lock.is_none() && otrack_hit.is_none() && !snap_hit.is_some_and(|s| { - !matches!( - s.snap_type, - crate::snap::SnapType::Grid | crate::snap::SnapType::Extension - ) - }) + !matches!( + s.snap_type, + crate::snap::SnapType::Grid | crate::snap::SnapType::Extension + ) + }) { let base = grip.origin_world; let ucs_xf = self.tabs[i].ucs_xform(); @@ -2860,8 +2888,7 @@ impl OpenCADStudio { !self.tabs[i].snap_result.is_some_and(|s| { !matches!( s.snap_type, - crate::snap::SnapType::Grid - | crate::snap::SnapType::Extension + crate::snap::SnapType::Grid | crate::snap::SnapType::Extension ) }) }) { @@ -3905,6 +3932,9 @@ impl OpenCADStudio { if self.visibility_popup.take().is_some() { return Task::none(); } + if self.table_popup.take().is_some() { + return Task::none(); + } // Same dismiss-on-outside-click for the right-click // context menu: its panel is opaque, so a press that // reaches here is outside the menu. @@ -4151,6 +4181,12 @@ impl OpenCADStudio { self.grip_popup = None; return Task::none(); } + if grip_id == crate::app::visibility::TABLE_GRIP_ID { + self.open_table_popup(p); + self.grip_hover = None; + self.grip_popup = None; + return Task::none(); + } if self.start_test_action_grip(handle, grip_id) { return Task::none(); } @@ -4275,7 +4311,7 @@ impl OpenCADStudio { } } - if edit_cam.is_none() && self.pick_authoring_object(p, bounds) { + if self.pick_authoring_object(p, bounds) { return Task::none(); } @@ -5434,6 +5470,7 @@ impl OpenCADStudio { self.tabs[i].scene.select_entities(&handles); self.tabs[i].scene.expand_selection_for_groups(&handles); } + self.pick_authoring_box(a, p, crossing); let apply_ms = t_apply.map_or(0.0, |t| t.elapsed().as_secs_f64() * 1000.0); let t_props = crate::perf::enabled().then(Instant::now); self.refresh_properties(); @@ -5777,6 +5814,7 @@ was_selected={}", } let apply_ms = t_apply.map_or(0.0, |t| t.elapsed().as_secs_f64() * 1000.0); let t_props = crate::perf::enabled().then(Instant::now); + self.pick_authoring_box(a, p, crossing); self.refresh_properties(); if crate::perf::enabled() { crate::perf_record!( @@ -6897,6 +6935,7 @@ properties={:.1}ms picked={}", self.grip_hover = None; self.grip_popup = None; self.visibility_popup = None; + self.table_popup = None; self.tabs[i].refresh_active_ucs(); self.tabs[i].scene.bump_geometry_no_blocks(); self.refresh_properties(); diff --git a/src/app/visibility.rs b/src/app/visibility.rs index 15755d39a..7854f9e8e 100644 --- a/src/app/visibility.rs +++ b/src/app/visibility.rs @@ -22,6 +22,7 @@ use super::OpenCADStudio; /// Sentinel grip id for the visibility (lookup) grip, distinct from any /// entity's own grip ids so the click handler routes it to the dropdown. pub(super) const VIS_GRIP_ID: usize = usize::MAX; +pub(super) const TABLE_GRIP_ID: usize = usize::MAX - 1; /// Resolved visibility info for the active tab's selected dynamic-block INSERT. #[derive(Clone, Debug)] @@ -40,6 +41,20 @@ pub struct VisibilityPopup { pub current: Option, } +#[derive(Clone, Debug)] +pub(super) struct TableGrip { + pub insert_handle: Handle, + pub current: Option, +} +#[derive(Clone, Debug)] +pub struct TablePopup { + pub insert_handle: Handle, + pub anchor: iced::Point, + pub header: String, + pub rows: Vec, + pub current: Option, +} + /// Block-local entity-handle lists for a dynamic definition and the anonymous /// block actually inserted, plus the state's visible-by-index set. struct StateMapping { @@ -50,6 +65,180 @@ struct StateMapping { } impl OpenCADStudio { + pub(super) fn apply_table_row(&mut self, insert: Handle, row: usize) { + let i = self.active_tab; + if self.reject_locked_edit(i, insert) { + return; + } + let result = { + let doc = &self.tabs[i].scene.document; + let Some(def) = doc.dynamic_definition_for_insert(insert) else { + return; + }; + let Ok(Some(view)) = crate::scene::block_properties_table::table(doc, def) else { + return; + }; + crate::scene::block_actions::instance::evaluate_table_row( + doc, + insert, + &view.columns, + row, + ) + }; + match result { + Ok(Some(staged)) => { + self.push_undo_snapshot(i, "Block Properties Table row"); + self.apply_action_document(i, staged); + self.tabs[i].scene.bump_geometry(); + self.tabs[i].dirty = true; + self.finish_pending_history(i); + self.refresh_selected_grips(); + } + Ok(None) => {} + Err(error) => self.command_line.push_error(crate::t!(error).as_ref()), + } + } + pub(super) fn open_table_popup(&mut self, anchor: iced::Point) { + if let Some(grip) = &self.tabs[self.active_tab].table_grip { + let tab = &self.tabs[self.active_tab]; + let doc = &tab.scene.document; + let Some(def) = doc.dynamic_definition_for_insert(grip.insert_handle) else { + return; + }; + let Ok(Some(view)) = crate::scene::block_properties_table::table(doc, def) else { + return; + }; + let parameters = crate::scene::block_actions::all_parameters(doc, def); + let header = view + .columns + .iter() + .map(|c| { + let display = if c.connection_name.eq_ignore_ascii_case("UpdatedAngle") { + crate::scene::block_orientation_parameter::parameters(doc, def) + .into_iter() + .find(|p| p.handle == c.handle) + .map(|p| p.label) + } else { + crate::scene::block_distance_parameter::parameters(doc, def) + .into_iter() + .find(|p| p.handle == c.handle) + .and_then(|p| p.labels.first().cloned()) + }; + display + .filter(|n| !n.trim().is_empty()) + .or_else(|| { + parameters + .iter() + .find(|p| p.handle == c.handle) + .map(|p| p.name.clone()) + }) + .filter(|n| !n.trim().is_empty()) + .unwrap_or_else(|| c.connection_name.clone()) + }) + .collect::>() + .join(", "); + let rows = view + .rows + .iter() + .map(|r| { + r.iter() + .map(|v| format!("{v:.4}")) + .collect::>() + .join(", ") + }) + .collect(); + self.table_popup = Some(TablePopup { + insert_handle: grip.insert_handle, + anchor, + header, + rows, + current: grip.current, + }); + } + } + pub(super) fn refresh_table_grip(&mut self, wo: [f64; 3]) { + let i = self.active_tab; + self.tabs[i].table_grip = None; + let Some(handle) = self.tabs[i].selected_handle else { + return; + }; + if self.tabs[i].active_block_edit_session().is_some() + || self.tabs[i].scene.selected.len() != 1 + { + return; + } + let doc = &self.tabs[i].scene.document; + let Some(def) = doc.dynamic_definition_for_insert(handle) else { + return; + }; + let Ok(Some(view)) = crate::scene::block_properties_table::table(doc, def) else { + return; + }; + let Some(EntityType::Insert(ins)) = doc.get_entity(handle) else { + return; + }; + let graph = + crate::scene::block_properties_table::graph_handle(doc, def).unwrap_or(Handle::NULL); + let point = doc + .objects + .values() + .find_map(|value| match value { + codec::objects::ObjectType::DynamicBlock(obj) if obj.owner == graph => match &obj + .data + { + codec::objects::DynamicBlockData::PropertiesTableGrip(g) => Some(g.location), + _ => None, + }, + _ => None, + }) + .unwrap_or(view.table.parameter.definition_point); + let wp = crate::scene::render_graph::insert_transform(doc, ins).apply(point); + let mut values = Vec::new(); + let mut valid = true; + for column in &view.columns { + let params = crate::scene::block_actions::all_parameters(doc, def); + let Some(p) = params.iter().find(|p| p.handle == column.handle) else { + valid = false; + continue; + }; + match crate::scene::block_actions::instance::value(doc, handle, p) { + Ok(crate::scene::block_actions::Value::Point(v)) => values.push( + if column.connection_name.eq_ignore_ascii_case("UpdatedAngle") { + (v.y - p.base.y) + .atan2(v.x - p.base.x) + .rem_euclid(std::f64::consts::TAU) + } else { + v.distance(p.base) + }, + ), + _ => { + values.push(f64::NAN); + valid = false; + } + } + } + let current = valid + .then(|| { + view.rows + .iter() + .position(|r| r.iter().zip(&values).all(|(a, b)| (a - b).abs() <= 1e-9)) + }) + .flatten(); + self.tabs[i].selected_grips.push(GripDef { + id: TABLE_GRIP_ID, + world: glam::DVec3::new(wp.x - wo[0], wp.y - wo[1], wp.z - wo[2]), + is_midpoint: false, + shape: GripShape::Triangle, + dir: None, + axis: None, + }); + self.tabs[i].selected_grip_handles.push(handle); + self.tabs[i].table_grip = Some(TableGrip { + insert_handle: handle, + current, + }); + } + /// Compute the index sets needed to apply `state` of `param` (governing the /// dynamic definition `def_block`) to the anonymous block `anon_name`. /// @@ -139,7 +328,9 @@ impl OpenCADStudio { label, field: "dyn_visibility", value: crate::scene::model::object::PropValue::Choice { - selected: current.and_then(|c| options.get(c).cloned()).unwrap_or_default(), + selected: current + .and_then(|c| options.get(c).cloned()) + .unwrap_or_default(), options, }, }) diff --git a/src/scene/block_actions/instance.rs b/src/scene/block_actions/instance.rs index 91f26b1eb..435362358 100644 --- a/src/scene/block_actions/instance.rs +++ b/src/scene/block_actions/instance.rs @@ -306,6 +306,102 @@ pub(crate) fn evaluate_value( Ok(Some(result)) } +pub(crate) fn evaluate_table_row( + doc: &CadDocument, + insert: Handle, + columns: &[crate::scene::block_properties_table::Column], + row: usize, +) -> Result, &'static str> { + let def = + definition(doc, insert).ok_or("Select a uniformly scaled dynamic block reference.")?; + let view = crate::scene::block_properties_table::table(doc, def)? + .ok_or("The Block Properties Table is unavailable.")?; + let values = view + .rows + .get(row) + .ok_or("The selected table row is unavailable.")?; + let mut staged = doc.clone(); + for (column, value) in columns.iter().zip(values) { + let p = all_parameters(&staged, def) + .into_iter() + .find(|p| p.handle == column.handle) + .ok_or("The table parameter is unavailable.")?; + let input = if column.connection_name.eq_ignore_ascii_case("UpdatedAngle") { + Value::Point( + p.base + DVec3::new(value.cos(), value.sin(), 0.0) * p.end.distance(p.base), + ) + } else { + let axis = (p.end - p.base).normalize(); + Value::Point(p.base + axis * *value) + }; + if let Some(next) = evaluate_value(&staged, insert, p.handle, input)? { + staged = next; + } + } + write_table_history(&mut staged, insert, &view, row)?; + Ok(Some(staged)) +} + +fn write_table_history( + doc: &mut CadDocument, + insert: Handle, + view: &crate::scene::block_properties_table::TableView, + row: usize, +) -> Result<(), &'static str> { + let mut owner = doc.ensure_extension_dictionary(insert); + for key in ["AcDbBlockRepresentation", "AppDataCache"] { + owner = ensure_dictionary(doc, owner, key)?; + } + let def = doc + .dynamic_definition_for_insert(insert) + .ok_or("The block definition is unavailable.")?; + let table_eval = view.table.parameter.parameter.element.eval.node_id; + let mut entries = vec![ + XRecordEntry::int16(1070, 3), + XRecordEntry::int32(1071, table_eval), + XRecordEntry::new(300, XRecordValue::String("Block Table1".into())), + XRecordEntry::int16(70, row as i16), + XRecordEntry::int16(1070, -1), + ]; + let params = all_parameters(doc, def); + for (column, value) in view.columns.iter().zip( + view.rows + .get(row) + .ok_or("The selected table row is unavailable.")?, + ) { + let p = params + .iter() + .find(|p| p.handle == column.handle) + .ok_or("The table parameter is unavailable.")?; + entries.push(XRecordEntry::int32(1071, p.id)); + entries.push(XRecordEntry::new(300, XRecordValue::String(p.name.clone()))); + entries.push(XRecordEntry::double(40, *value)); + } + entries.push(XRecordEntry::int16(1070, -2)); + let h = if let Some(h) = entry(doc, owner, "ACAD_ENHANCEDBLOCKHISTORY")? { + h + } else { + doc.allocate_handle() + }; + let mut rec = XRecord::named("ACAD_ENHANCEDBLOCKHISTORY"); + rec.handle = h; + rec.owner = owner; + rec.reactors.push(owner); + rec.cloning_flags = codec::objects::DictionaryCloningFlags::KeepExisting; + rec.entries = entries; + doc.objects.insert(h, ObjectType::XRecord(rec)); + if let Some(ObjectType::Dictionary(d)) = doc.objects.get_mut(&owner) { + if !d + .entries + .iter() + .any(|(k, _)| k.eq_ignore_ascii_case("ACAD_ENHANCEDBLOCKHISTORY")) + { + d.add_entry("ACAD_ENHANCEDBLOCKHISTORY", h); + } + } + Ok(()) +} + /// Refresh existing evaluated references after an authoring change. pub(crate) fn refresh(doc: &CadDocument, def: Handle) -> Result { refresh_after_edit(doc, doc, def)