forked from 1b-agent/opencadstudio-upstream
feat(bpt): add reference-side row selection
This commit is contained in:
1 parent
d3e64800f6
commit
2797b5b9e1
11 files changed
+399
-23
No files matched your search
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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<super::visibility::TableGrip>,
|
||||
pub(super) current_path: Option<PathBuf>,
|
||||
/// 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(),
|
||||
|
||||
+8
-5
@@ -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<GripPendingValue>,
|
||||
/// Open dynamic-block visibility-state dropdown.
|
||||
visibility_popup: Option<visibility::VisibilityPopup>,
|
||||
table_popup: Option<visibility::TablePopup>,
|
||||
/// 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,
|
||||
|
||||
@@ -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<Message> {
|
||||
if field == "authoring_name" {
|
||||
return self.commit_authoring_name();
|
||||
}
|
||||
if field.starts_with("bedit_") {
|
||||
return self.commit_block_property(field);
|
||||
}
|
||||
|
||||
+39
-7
@@ -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 {
|
||||
self.command_line.push_output(
|
||||
if self.dynamic_ucs {
|
||||
crate::t!("Dynamic UCS: on")
|
||||
} else {
|
||||
crate::t!("Dynamic UCS: off")
|
||||
}
|
||||
.as_ref());
|
||||
.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,
|
||||
|
||||
+49
-10
@@ -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
|
||||
@@ -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();
|
||||
|
||||
+192
-1
@@ -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<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 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::<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
|
||||
/// 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,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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<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.
|
||||
pub(crate) fn refresh(doc: &CadDocument, def: Handle) -> Result<CadDocument, &'static str> {
|
||||
refresh_after_edit(doc, doc, def)
|
||||
|
||||
Reference in new issue
Block a user