feat(bpt): add reference-side row selection

This commit is contained in:
Karim Jerbi committed 2026-10-18 19:06:36 +01:00
1 parent d3e64800f6
commit 2797b5b9e1
11 files changed
+408 -32

No files matched your search

+1
View File
@@ -563,6 +563,7 @@ impl OpenCADStudio {
self.grip_hover = None; self.grip_hover = None;
self.grip_popup = None; self.grip_popup = None;
self.visibility_popup = None; self.visibility_popup = None;
self.table_popup = None;
history.checkpoint = self.block_edit_checkpoint(i); history.checkpoint = self.block_edit_checkpoint(i);
self.tabs[i].block_edit_history = Some(history); self.tabs[i].block_edit_history = Some(history);
self.tabs[i].scene.bump_geometry(); self.tabs[i].scene.bump_geometry();
+1
View File
@@ -134,6 +134,7 @@ impl OpenCADStudio {
tab.scene.set_current_layout("Model".into()); tab.scene.set_current_layout("Model".into());
self.discard_block_tests(source_id); self.discard_block_tests(source_id);
self.visibility_popup = None; self.visibility_popup = None;
self.table_popup = None;
self.grip_popup = None; self.grip_popup = None;
self.grip_hover = None; self.grip_hover = None;
self.tabs.push(tab); self.tabs.push(tab);
+1
View File
@@ -105,6 +105,7 @@ impl OpenCADStudio {
self.grip_popup = None; self.grip_popup = None;
self.grip_pending = None; self.grip_pending = None;
self.visibility_popup = None; self.visibility_popup = None;
self.table_popup = None;
self.hover_dwell = None; self.hover_dwell = None;
self.ucs_grip_drag = None; self.ucs_grip_drag = None;
self.ucs_icon_selected = false; self.ucs_icon_selected = false;
+2
View File
@@ -1064,6 +1064,7 @@ impl OpenCADStudio {
self.grip_hover = None; self.grip_hover = None;
self.grip_popup = None; self.grip_popup = None;
self.visibility_popup = None; self.visibility_popup = None;
self.table_popup = None;
// Entering BEDIT changes which block is assembled, not the // Entering BEDIT changes which block is assembled, not the
// cached geometry of that block's entities. // cached geometry of that block's entities.
self.tabs[i].scene.bump_geometry_no_blocks(); self.tabs[i].scene.bump_geometry_no_blocks();
@@ -1098,6 +1099,7 @@ impl OpenCADStudio {
self.grip_hover = None; self.grip_hover = None;
self.grip_popup = None; self.grip_popup = None;
self.visibility_popup = None; self.visibility_popup = None;
self.table_popup = None;
let parent_index = return_block.and_then(|parent_name| { let parent_index = return_block.and_then(|parent_name| {
self.tabs[i] self.tabs[i]
+2
View File
@@ -128,6 +128,7 @@ pub(super) struct DocumentTab {
/// must never target a tab by its transient vector index. /// must never target a tab by its transient vector index.
pub(super) id: u64, pub(super) id: u64,
pub(super) scene: Scene, pub(super) scene: Scene,
pub(super) table_grip: Option<super::visibility::TableGrip>,
pub(super) current_path: Option<PathBuf>, pub(super) current_path: Option<PathBuf>,
/// Persistent native edit lease for the drawing currently at /// Persistent native edit lease for the drawing currently at
/// `current_path`. The sidecar portion survives atomic file replacement. /// `current_path`. The sidecar portion survives atomic file replacement.
@@ -660,6 +661,7 @@ impl DocumentTab {
selected_handle: None, selected_handle: None,
selected_authoring: None, selected_authoring: None,
visibility_grip: None, visibility_grip: None,
table_grip: None,
wireframe: false, wireframe: false,
render_mode: codec::entities::ViewportRenderMode::Wireframe2D, render_mode: codec::entities::ViewportRenderMode::Wireframe2D,
visual_style: "Wireframe 2D".into(), visual_style: "Wireframe 2D".into(),
+8 -5
View File
@@ -8,21 +8,21 @@ pub(crate) mod config;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub use automation::{export_headless, serve}; pub use automation::{export_headless, serve};
mod annotation_data; mod annotation_data;
mod block_actions;
mod block_authoring_edit;
mod block_editor; mod block_editor;
#[cfg(test)] #[cfg(test)]
mod block_editor_tests; mod block_editor_tests;
mod block_save_as;
mod block_visibility;
mod block_lookup; mod block_lookup;
mod block_properties_table;
mod block_actions;
mod block_authoring_edit;
mod block_properties; mod block_properties;
mod block_properties_table;
#[cfg(test)] #[cfg(test)]
mod block_properties_tests; mod block_properties_tests;
mod block_save_as;
mod block_test; mod block_test;
#[cfg(test)] #[cfg(test)]
mod block_test_tests; mod block_test_tests;
mod block_visibility;
mod command_driver; mod command_driver;
pub(crate) use command_driver::{ pub(crate) use command_driver::{
copy_to_clipboard_kernel, define_block_raw_doc, paste_entities_kernel, PasteSpace, copy_to_clipboard_kernel, define_block_raw_doc, paste_entities_kernel, PasteSpace,
@@ -904,6 +904,7 @@ pub(super) struct OpenCADStudio {
grip_pending: Option<GripPendingValue>, grip_pending: Option<GripPendingValue>,
/// Open dynamic-block visibility-state dropdown. /// Open dynamic-block visibility-state dropdown.
visibility_popup: Option<visibility::VisibilityPopup>, visibility_popup: Option<visibility::VisibilityPopup>,
table_popup: Option<visibility::TablePopup>,
/// A leader line just added via the "Add Leader" grip menu whose arrow is /// 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)`. /// being placed (follows the cursor). `(entity handle, new-arrow grip id)`.
/// Esc before the placement click removes it again. /// 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 /// User picked a dynamic-block visibility state — index into the
/// visibility dropdown's items. /// visibility dropdown's items.
VisibilityPick(usize), VisibilityPick(usize),
TableRowPick(usize),
/// Timer pulse while the cursor is dwelling on a grip; drives the /// Timer pulse while the cursor is dwelling on a grip; drives the
/// dwell-to-popup transition without requiring further mouse motion. /// dwell-to-popup transition without requiring further mouse motion.
GripDwellTick, GripDwellTick,
@@ -4894,6 +4896,7 @@ impl OpenCADStudio {
grip_popup: None, grip_popup: None,
grip_pending: None, grip_pending: None,
visibility_popup: None, visibility_popup: None,
table_popup: None,
grip_add_provisional: None, grip_add_provisional: None,
grip_preview_handles: Vec::new(), grip_preview_handles: Vec::new(),
hover_dwell: None, hover_dwell: None,
+8
View File
@@ -194,6 +194,7 @@ impl OpenCADStudio {
self.cancel_active_grip_edit(); self.cancel_active_grip_edit();
self.grip_pending = None; self.grip_pending = None;
self.visibility_popup = None; self.visibility_popup = None;
self.table_popup = None;
self.grip_popup = None; self.grip_popup = None;
self.grip_hover = None; self.grip_hover = None;
} }
@@ -1354,7 +1355,11 @@ impl OpenCADStudio {
self.grip_hover = None; self.grip_hover = None;
return Task::none(); return Task::none();
} }
if self.table_popup.take().is_some() {
return Task::none();
}
if self.visibility_popup.take().is_some() { if self.visibility_popup.take().is_some() {
self.table_popup = None;
return Task::none(); return Task::none();
} }
if self.tabs[self.active_tab].active_grip.is_some() { 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<Message> { pub(super) fn on_prop_geom_commit(&mut self, field: &'static str) -> Task<Message> {
if field == "authoring_name" {
return self.commit_authoring_name();
}
if field.starts_with("bedit_") { if field.starts_with("bedit_") {
return self.commit_block_property(field); return self.commit_block_property(field);
} }
+43 -11
View File
@@ -1,6 +1,6 @@
use crate::io::plot_style::PlotStyleTableExt as _;
use super::{ArrowKey, Message, OpenCADStudio, TextEntryMode}; use super::{ArrowKey, Message, OpenCADStudio, TextEntryMode};
use crate::command::CadCommand; use crate::command::CadCommand;
use crate::io::plot_style::PlotStyleTableExt as _;
use crate::scene::VIEWCUBE_DRAW_PX; use crate::scene::VIEWCUBE_DRAW_PX;
use crate::ui::PropertiesPanel; use crate::ui::PropertiesPanel;
use iced::time::Instant; use iced::time::Instant;
@@ -4575,7 +4575,9 @@ impl OpenCADStudio {
// CATIA navigation: the left button pressed during a middle drag // CATIA navigation: the left button pressed during a middle drag
// turns the drag into an orbit instead of starting a pick. // 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 if self.navigation_style == crate::app::settings::NavigationStyle::Catia
&& self.tabs[self.active_tab] && self.tabs[self.active_tab]
.scene .scene
@@ -4999,6 +5001,12 @@ impl OpenCADStudio {
} }
Task::none() 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), Message::GripMenuPick(idx) => self.on_grip_menu_pick(idx),
@@ -6782,6 +6790,30 @@ impl OpenCADStudio {
self.delete_parametric_constraint(id); self.delete_parametric_constraint(id);
return Task::none(); 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(); let handles: Vec<_> = self.tabs[i].scene.selected.iter().cloned().collect();
if !handles.is_empty() { if !handles.is_empty() {
// Erase is delta-safe unless a target is in a group (group // Erase is delta-safe unless a target is in a group (group
@@ -9392,12 +9424,14 @@ impl OpenCADStudio {
Message::ToggleDynamicUcs => { Message::ToggleDynamicUcs => {
self.dynamic_ucs = !self.dynamic_ucs; self.dynamic_ucs = !self.dynamic_ucs;
self.command_line.push_output(if self.dynamic_ucs { self.command_line.push_output(
crate::t!("Dynamic UCS: on") if self.dynamic_ucs {
} else { crate::t!("Dynamic UCS: on")
crate::t!("Dynamic UCS: off") } else {
} crate::t!("Dynamic UCS: off")
.as_ref()); }
.as_ref(),
);
self.persist_settings_if_changed(); self.persist_settings_if_changed();
Task::none() Task::none()
} }
@@ -9780,9 +9814,7 @@ impl OpenCADStudio {
// Closing the attribute manager is its Cancel: live changes // Closing the attribute manager is its Cancel: live changes
// are taken back. // are taken back.
if self.active_modal == Some(super::ModalKind::AttMan) { if self.active_modal == Some(super::ModalKind::AttMan) {
return self.on_attman( return self.on_attman(crate::ui::window::attman_dialog::AttManMsg::Cancel);
crate::ui::window::attman_dialog::AttManMsg::Cancel,
);
} }
if matches!( if matches!(
self.active_modal, self.active_modal,
+54 -15
View File
@@ -66,26 +66,51 @@ mod selection_gesture_priority_tests {
fn fast_first_click_never_becomes_a_lasso_solely_from_cursor_speed() { 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(25.0 * 25.0, 20, false));
assert!(!selection_drag_should_start(120.0 * 120.0, 80, 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(
assert!(selection_drag_should_start(25.0 * 25.0, POLY_START_DELAY_MS, false)); 500.0 * 500.0,
assert!(!selection_drag_should_start(11.0 * 11.0, POLY_START_DELAY_MS + 500, false)); 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] #[test]
fn pickdrag_rectangle_keeps_its_fast_drag_shortcut() { fn pickdrag_rectangle_keeps_its_fast_drag_shortcut() {
assert!(selection_drag_should_start(25.0 * 25.0, 20, true)); 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(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] #[test]
fn quick_selection_click_anchors_where_the_button_went_down() { fn quick_selection_click_anchors_where_the_button_went_down() {
let start = Point::new(100.0, 100.0); let start = Point::new(100.0, 100.0);
let moved = Point::new(190.0, 135.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. // 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!(
assert_eq!(selection_click_position(moved, Some(start), false, true), moved); 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); 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(|(owner, grip)| self.tabs[i].hot_grips.contains(&(*owner, grip.id)))
.filter(|(_, grip)| { .filter(|(_, grip)| {
grip.id != crate::app::visibility::VIS_GRIP_ID 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_actions::is_test_grip(grip.id)
&& !crate::app::block_authoring_edit::is_editor_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()) .zip(self.tabs[i].selected_grips.iter())
.filter(|(_, grip)| { .filter(|(_, grip)| {
grip.id != crate::app::visibility::VIS_GRIP_ID 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_actions::is_test_grip(grip.id)
&& !crate::app::block_authoring_edit::is_editor_grip(grip.id) && !crate::app::block_authoring_edit::is_editor_grip(grip.id)
}) })
@@ -1163,6 +1190,7 @@ impl OpenCADStudio {
// hover grip-menu. // hover grip-menu.
if self.grip_popup.is_none() if self.grip_popup.is_none()
&& grip_id != crate::app::visibility::VIS_GRIP_ID && 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_actions::is_test_grip(grip_id)
&& !crate::app::block_authoring_edit::is_editor_grip(grip_id) && !crate::app::block_authoring_edit::is_editor_grip(grip_id)
&& self && self
@@ -2018,11 +2046,11 @@ impl OpenCADStudio {
if axis_lock.is_none() if axis_lock.is_none()
&& otrack_hit.is_none() && otrack_hit.is_none()
&& !snap_hit.is_some_and(|s| { && !snap_hit.is_some_and(|s| {
!matches!( !matches!(
s.snap_type, s.snap_type,
crate::snap::SnapType::Grid | crate::snap::SnapType::Extension crate::snap::SnapType::Grid | crate::snap::SnapType::Extension
) )
}) })
{ {
let base = grip.origin_world; let base = grip.origin_world;
let ucs_xf = self.tabs[i].ucs_xform(); let ucs_xf = self.tabs[i].ucs_xform();
@@ -2860,8 +2888,7 @@ impl OpenCADStudio {
!self.tabs[i].snap_result.is_some_and(|s| { !self.tabs[i].snap_result.is_some_and(|s| {
!matches!( !matches!(
s.snap_type, s.snap_type,
crate::snap::SnapType::Grid crate::snap::SnapType::Grid | crate::snap::SnapType::Extension
| crate::snap::SnapType::Extension
) )
}) })
}) { }) {
@@ -3905,6 +3932,9 @@ impl OpenCADStudio {
if self.visibility_popup.take().is_some() { if self.visibility_popup.take().is_some() {
return Task::none(); return Task::none();
} }
if self.table_popup.take().is_some() {
return Task::none();
}
// Same dismiss-on-outside-click for the right-click // Same dismiss-on-outside-click for the right-click
// context menu: its panel is opaque, so a press that // context menu: its panel is opaque, so a press that
// reaches here is outside the menu. // reaches here is outside the menu.
@@ -4151,6 +4181,12 @@ impl OpenCADStudio {
self.grip_popup = None; self.grip_popup = None;
return Task::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) { if self.start_test_action_grip(handle, grip_id) {
return Task::none(); 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(); return Task::none();
} }
@@ -5434,6 +5470,7 @@ impl OpenCADStudio {
self.tabs[i].scene.select_entities(&handles); self.tabs[i].scene.select_entities(&handles);
self.tabs[i].scene.expand_selection_for_groups(&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 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); let t_props = crate::perf::enabled().then(Instant::now);
self.refresh_properties(); 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 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); let t_props = crate::perf::enabled().then(Instant::now);
self.pick_authoring_box(a, p, crossing);
self.refresh_properties(); self.refresh_properties();
if crate::perf::enabled() { if crate::perf::enabled() {
crate::perf_record!( crate::perf_record!(
@@ -6897,6 +6935,7 @@ properties={:.1}ms picked={}",
self.grip_hover = None; self.grip_hover = None;
self.grip_popup = None; self.grip_popup = None;
self.visibility_popup = None; self.visibility_popup = None;
self.table_popup = None;
self.tabs[i].refresh_active_ucs(); self.tabs[i].refresh_active_ucs();
self.tabs[i].scene.bump_geometry_no_blocks(); self.tabs[i].scene.bump_geometry_no_blocks();
self.refresh_properties(); self.refresh_properties();
+192 -1
View File
@@ -22,6 +22,7 @@ use super::OpenCADStudio;
/// Sentinel grip id for the visibility (lookup) grip, distinct from any /// 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. /// 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 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. /// Resolved visibility info for the active tab's selected dynamic-block INSERT.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@@ -40,6 +41,20 @@ pub struct VisibilityPopup {
pub current: Option<usize>, pub current: Option<usize>,
} }
#[derive(Clone, Debug)]
pub(super) struct TableGrip {
pub insert_handle: Handle,
pub current: Option<usize>,
}
#[derive(Clone, Debug)]
pub struct TablePopup {
pub insert_handle: Handle,
pub anchor: iced::Point,
pub header: String,
pub rows: Vec<String>,
pub current: Option<usize>,
}
/// Block-local entity-handle lists for a dynamic definition and the anonymous /// Block-local entity-handle lists for a dynamic definition and the anonymous
/// block actually inserted, plus the state's visible-by-index set. /// block actually inserted, plus the state's visible-by-index set.
struct StateMapping { struct StateMapping {
@@ -50,6 +65,180 @@ struct StateMapping {
} }
impl OpenCADStudio { 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::<Vec<_>>()
.join(", ");
let rows = view
.rows
.iter()
.map(|r| {
r.iter()
.map(|v| format!("{v:.4}"))
.collect::<Vec<_>>()
.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 /// Compute the index sets needed to apply `state` of `param` (governing the
/// dynamic definition `def_block`) to the anonymous block `anon_name`. /// dynamic definition `def_block`) to the anonymous block `anon_name`.
/// ///
@@ -139,7 +328,9 @@ impl OpenCADStudio {
label, label,
field: "dyn_visibility", field: "dyn_visibility",
value: crate::scene::model::object::PropValue::Choice { 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, options,
}, },
}) })
+96
View File
@@ -306,6 +306,102 @@ pub(crate) fn evaluate_value(
Ok(Some(result)) Ok(Some(result))
} }
pub(crate) fn evaluate_table_row(
doc: &CadDocument,
insert: Handle,
columns: &[crate::scene::block_properties_table::Column],
row: usize,
) -> Result<Option<CadDocument>, &'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. /// Refresh existing evaluated references after an authoring change.
pub(crate) fn refresh(doc: &CadDocument, def: Handle) -> Result<CadDocument, &'static str> { pub(crate) fn refresh(doc: &CadDocument, def: Handle) -> Result<CadDocument, &'static str> {
refresh_after_edit(doc, doc, def) refresh_after_edit(doc, doc, def)