feat(block-editor): author Lookup tables and wire Basepoint insertion

Add native Basepoint placement and relocation, shared insertion and Test Block anchor behavior, and attribute-safe coordinate reframing. Wire native Lookup parameter placement and the Property Lookup Table editor through the existing palettes and BLOOKUPTABLE.

Stage input columns, rows, reverse lookup and audit across multiple tables; apply all drafts atomically as one undo entry. Persist native Lookup actions and graph dependencies while preserving unrelated objects and IDs. New inputs cover Linear and Polar distances; reference Lookup evaluation and geometry actions remain separate work.

Pin codec c418d92, kernel 0f6f225 and graph dcb9eda for correct R2010 inline constraint-group decoding. The codec regression uses a checked-in synthetic DWG and reproducible generator, with normal self-contained loading and edited DWG/DXF tests. Keep dock control tests independent of scene adapter warning dialogs in software snapshots.

Validation covers native round trips, all 17 research fixtures, graph preservation, insertion and attributes, staged editing, undo/cancel, and real palette/dialog controls.
This commit is contained in:
Karim Jerbi committed 2026-10-16 15:29:08 +01:00
1 parent 04b2c07a38
commit 10c5a9d382
37 files changed
+2248 -33

No files matched your search

+195
View File
@@ -98,6 +98,201 @@ fn orientation_parameter_count(app: &OpenCADStudio) -> usize {
crate::scene::block_orientation_parameter::parameters(&tab.scene.document, definition).len()
}
#[test]
fn lookup_basepoint_placement_history_test_block_and_saved_parent_undo() {
use crate::scene::{block_basepoint, block_lookup, render_graph};
let (mut app, reference) = fixture();
begin(&mut app, reference);
let i = app.active_tab;
let definition = app.tabs[i].active_block_edit_session().unwrap().br_handle;
let members_before = members(&app);
let _ = app.run_command_line("BPARAMETER BASEPOINT 3,4");
let (handle, p) = block_basepoint::parameter(&app.tabs[i].scene.document, definition).unwrap();
assert_eq!(p.point, Vector3::new(3.0, 4.0, 0.0));
let depth = app.tabs[i].history.undo_stack.len();
assert_eq!(depth, 1);
let _ = app.run_command_line("BPARAMETER BASEPOINT 3,4");
assert_eq!(
app.tabs[i].history.undo_stack.len(),
depth,
"same anchor is a no-op"
);
let _ = app.run_command_line("BPARAMETER BASEPOINT 5,6");
assert_eq!(
block_basepoint::parameter(&app.tabs[i].scene.document, definition)
.unwrap()
.0,
handle
);
app.undo_steps(1);
assert_eq!(
block_basepoint::parameter(&app.tabs[i].scene.document, definition)
.unwrap()
.1
.point,
Vector3::new(3.0, 4.0, 0.0)
);
app.redo_steps(1);
let _ = app.dispatch_command("BPARAMETER");
feed(&mut app, "K");
feed(&mut app, "2,2");
assert_eq!(
block_lookup::parameters(&app.tabs[i].scene.document, definition).len(),
1
);
assert_eq!(
members(&app),
members_before,
"authoring objects are not drawing entities"
);
let before_test = app.tabs[i].scene.document.clone();
let _ = app.dispatch_command("BTESTBLOCK");
let doc = &app.tabs[app.active_tab].scene.document;
let insert = doc
.entities()
.find_map(|e| match e {
EntityType::Insert(i) => Some(i),
_ => None,
})
.unwrap();
assert!(
(render_graph::insert_transform(doc, insert).apply(Vector3::new(5.0, 6.0, 0.0))
- insert.insert_point)
.length()
< 1e-9
);
let _ = app.dispatch_command("BCLOSETESTBLOCK");
assert_eq!(app.tabs[i].scene.document, before_test);
let _ = app.dispatch_command("BSAVE");
let _ = app.dispatch_command("BEDIT_DISCARD");
assert!(block_basepoint::parameter(&app.tabs[i].scene.document, definition).is_some());
app.undo_steps(1);
assert!(block_basepoint::parameter(&app.tabs[i].scene.document, definition).is_none());
app.redo_steps(1);
assert!(block_basepoint::parameter(&app.tabs[i].scene.document, definition).is_some());
}
fn lookup_editor_fixture() -> (OpenCADStudio, Handle, Handle) {
let (mut app, reference) = fixture();
begin(&mut app, reference);
let _ = app.run_command_line("BPARAMETER LINEAR 0,0 2,0 0,1");
let _ = app.run_command_line("BPARAMETER LOOKUP 1,1");
let i = app.active_tab;
let definition = app.tabs[i].active_block_edit_session().unwrap().br_handle;
let parameter =
crate::scene::block_lookup::parameters(&app.tabs[i].scene.document, definition)[0].0;
(app, definition, parameter)
}
fn edit_lookup(app: &mut OpenCADStudio) {
use crate::ui::window::block_lookup::Action as A;
let _ = app.update(Message::BlockLookup(A::Property(0)));
let _ = app.update(Message::BlockLookup(A::AddProperty));
let _ = app.update(Message::BlockLookup(A::AddRow));
let _ = app.update(Message::BlockLookup(A::Cell(0, 1, "Small".into())));
}
#[test]
fn lookup_editor_cancel_apply_one_undo_and_stale_guard() {
use crate::ui::window::block_lookup::Action as A;
use crate::scene::block_lookup;
let (mut app, definition, parameter) = lookup_editor_fixture();
let i = app.active_tab;
let before = app.tabs[i].scene.document.clone();
let depth = app.tabs[i].history.undo_stack.len();
for cancel in [Message::CloseModal, Message::CommandEscape] {
let _ = app.dispatch_command("BLOOKUPTABLE");
edit_lookup(&mut app);
assert_eq!(app.tabs[i].scene.document, before, "draft is isolated");
let _ = app.update(cancel);
assert!(app.block_lookup.is_none());
assert_eq!(app.tabs[i].scene.document, before);
assert_eq!(app.tabs[i].history.undo_stack.len(), depth);
}
let _ = app.dispatch_command("BLOOKUPTABLE");
let _ = app.update(Message::BlockLookup(A::Apply));
assert_eq!(
app.tabs[i].history.undo_stack.len(),
depth,
"unchanged OK is a no-op"
);
let _ = app.dispatch_command("BLOOKUPTABLE");
edit_lookup(&mut app);
let _ = app.update(Message::BlockLookup(A::Audit));
assert!(app.block_lookup.as_ref().unwrap().error.is_none());
let _ = app.update(Message::BlockLookup(A::Apply));
assert!(app.block_lookup.is_none());
assert_eq!(app.tabs[i].history.undo_stack.len(), depth + 1);
assert_eq!(
block_lookup::table(&app.tabs[i].scene.document, definition, parameter)
.unwrap()
.unwrap()
.1
.expressions,
vec!["2", "Small"]
);
let saved = app.tabs[i].scene.document.clone();
app.undo_steps(1);
assert_eq!(app.tabs[i].scene.document, before);
app.redo_steps(1);
assert_eq!(app.tabs[i].scene.document, saved);
let _ = app.dispatch_command("BLOOKUPTABLE Lookup1");
let _ = app.update(Message::BlockLookup(A::Cell(0, 1, "Changed".into())));
app.tabs[i].scene.bump_geometry();
let _ = app.update(Message::BlockLookup(A::Apply));
assert!(app
.block_lookup
.as_ref()
.unwrap()
.error
.as_ref()
.unwrap()
.contains("changed"));
assert_eq!(app.tabs[i].scene.document, saved);
let _ = app.update(Message::CloseModal);
}
#[test]
fn lookup_editor_multiple_tables_apply_atomically_and_parameter_cancel_is_clean() {
use crate::ui::window::block_lookup::Action as A;
let (mut app, definition, _) = lookup_editor_fixture();
let i = app.active_tab;
let _ = app.run_command_line("BPARAMETER LOOKUP 3,3");
let before = app.tabs[i].scene.document.clone();
let depth = app.tabs[i].history.undo_stack.len();
for kind in ["BASEPOINT", "LOOKUP"] {
let _ = app.dispatch_command(&format!("BPARAMETER {kind}"));
let _ = app.update(Message::CommandEscape);
assert_eq!(app.tabs[i].scene.document, before);
assert_eq!(app.tabs[i].history.undo_stack.len(), depth);
}
let _ = app.dispatch_command("BLOOKUPTABLE");
edit_lookup(&mut app);
let _ = app.update(Message::BlockLookup(A::Select(1)));
edit_lookup(&mut app);
let _ = app.update(Message::BlockLookup(A::Cell(0, 0, "NaN".into())));
let _ = app.update(Message::BlockLookup(A::Apply));
assert!(app.block_lookup.as_ref().unwrap().error.is_some());
assert_eq!(app.tabs[i].scene.document, before);
let _ = app.update(Message::BlockLookup(A::Cell(0, 0, "5".into())));
let _ = app.update(Message::BlockLookup(A::Apply));
assert!(app.block_lookup.is_none());
assert_eq!(app.tabs[i].history.undo_stack.len(), depth + 1);
for (h, _) in crate::scene::block_lookup::parameters(&app.tabs[i].scene.document, definition) {
assert!(
crate::scene::block_lookup::table(&app.tabs[i].scene.document, definition, h)
.unwrap()
.is_some()
);
}
app.undo_steps(1);
assert_eq!(app.tabs[i].scene.document, before);
let _ = app.dispatch_command("BEDIT_DISCARD");
let _ = app.dispatch_command("LINE");
for cmd in ["BPARAMETER BASEPOINT", "BPARAMETER LOOKUP", "BLOOKUPTABLE"] {
let _ = app.dispatch_command(cmd);
assert_eq!(app.tabs[i].active_cmd.as_ref().unwrap().name(), "LINE");
}
}
#[test]
fn orientation_parameter_batch_numeric_rotation_local_history_save_and_parent_undo() {
let (mut app, reference) = fixture();
+219
View File
@@ -0,0 +1,219 @@
use super::{Message, ModalKind, OpenCADStudio};
use crate::scene::{block_basepoint, block_lookup as lookup};
use crate::ui::window::block_lookup::{Action, Draft, State};
use iced::Task;
impl OpenCADStudio {
pub(in crate::app) fn handle_place_block_single_parameter(
&mut self,
result: crate::command::CmdResult,
) -> Task<Message> {
use crate::modules::block_editor::single_parameter::Kind;
let crate::command::CmdResult::PlaceBlockSingleParameter {
document_id,
definition,
kind,
location,
} = result
else {
unreachable!()
};
let i = self.active_tab;
if self.tabs[i].id != document_id
|| self.tabs[i]
.active_block_edit_session()
.map(|s| s.br_handle)
!= Some(definition)
{
self.command_line.push_error(
crate::t!("The parameter's Block Editor session is no longer active.").as_ref(),
);
return Task::none();
}
if let Some(error) = self.parameter_placement_error(kind.name()) {
self.command_line.push_error(crate::t!(error).as_ref());
return Task::none();
}
if !location.is_finite() {
self.command_line
.push_error(crate::t!("Specify a finite parameter location.").as_ref());
return Task::none();
}
let location = codec::types::Vector3::new(location.x, location.y, location.z);
if kind == Kind::Basepoint
&& block_basepoint::parameter(&self.tabs[i].scene.document, definition).is_some_and(
|(_, p)| {
p.point == location
&& p.base_point == location
&& p.parameter.definition_point == location
},
)
{
self.tabs[i].active_cmd = None;
self.tabs[i].scene.clear_preview_wire();
return Task::none();
}
self.push_undo_snapshot(i, &format!("{} parameter", kind.name()));
match kind {
Kind::Basepoint => {
block_basepoint::create(&mut self.tabs[i].scene.document, definition, location)
}
Kind::Lookup => lookup::create(&mut self.tabs[i].scene.document, definition, location),
}
.expect("validated parameter placement");
self.tabs[i].scene.bump_geometry();
self.tabs[i].dirty = true;
self.tabs[i].active_cmd = None;
self.tabs[i].scene.clear_preview_wire();
self.finish_pending_history(i);
self.refresh_properties();
self.command_line.push_output(
crate::t!(if kind == Kind::Lookup {
"Lookup parameter placed. Use BLOOKUPTABLE to edit its property table."
} else {
"Basepoint parameter placed."
})
.as_ref(),
);
Task::none()
}
pub(in crate::app) fn lookup_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 a Lookup table.");
};
if lookup::parameters(&tab.scene.document, session.br_handle).is_empty() {
Some("Place a Lookup parameter before editing its property table.")
} else {
lookup::placement_error(&tab.scene.document, session.br_handle)
}
}
pub(in crate::app) fn open_lookup_table(&mut self, name: &str) -> Task<Message> {
if let Some(error) = self.lookup_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 mut drafts = vec![];
for (handle, parameter) in lookup::parameters(&tab.scene.document, definition) {
let original = match lookup::table(&tab.scene.document, definition, handle) {
Ok(value) => value,
Err(error) => {
self.command_line.push_error(crate::t!(error).as_ref());
return Task::none();
}
};
let edited = original
.as_ref()
.map(|(_, t)| t.clone())
.unwrap_or_else(|| lookup::empty_table(parameter));
if edited.row_count < 0
|| edited.row_count > 1000
|| edited.columns.is_empty()
|| edited.columns.len() > 64
|| edited.column_count != edited.columns.len() as i32
|| edited.expressions.len() != edited.columns.len() * edited.row_count as usize
{
self.command_line.push_error(
crate::t!("This Lookup table has inconsistent or unsupported dimensions.")
.as_ref(),
);
return Task::none();
}
drafts.push(Draft {
parameter: handle,
original_parameter: parameter.clone(),
original,
edited,
});
}
let selected = if name.is_empty() {
0
} else {
let Some(index) = drafts
.iter()
.position(|d| d.original_parameter.lookup_name.eq_ignore_ascii_case(name))
else {
self.command_line
.push_error(crate::t!("No Lookup parameter has that name.").as_ref());
return Task::none();
};
index
};
self.block_lookup = Some(State {
document: tab.id,
definition,
epoch: tab.scene.geometry_epoch,
drafts,
selected,
properties: lookup::properties(&tab.scene.document, definition),
selected_property: None,
error: None,
notice: None,
});
self.active_modal = Some(ModalKind::BlockLookup);
Task::none()
}
pub(super) fn handle_block_lookup(&mut self, action: Action) -> Task<Message> {
if !matches!(action, Action::Apply) {
if let Some(state) = self.block_lookup.as_mut() {
state.edit(action);
}
return Task::none();
}
let Some(state) = self.block_lookup.as_ref() else {
return Task::none();
};
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
|| state.drafts.iter().any(|d| {
lookup::parameters(&tab.scene.document, state.definition)
.into_iter()
.find(|(h, _)| *h == d.parameter)
.map(|(_, p)| p)
!= Some(&d.original_parameter)
|| lookup::table(&tab.scene.document, state.definition, d.parameter)
.ok()
.as_ref()
!= Some(&d.original)
});
if stale {
self.block_lookup.as_mut().unwrap().error = Some(
crate::t!("The block changed. Cancel and reopen the Lookup table.").into_owned(),
);
return Task::none();
}
let mut working = tab.scene.document.clone();
let mut changed = false;
for draft in &state.drafts {
if draft.changed() {
if let Err(error) = lookup::apply_table(
&mut working,
state.definition,
draft.parameter,
draft.edited.clone(),
) {
self.block_lookup.as_mut().unwrap().error =
Some(crate::t!(error.as_str()).into_owned());
return Task::none();
}
changed = true;
}
}
if changed {
self.push_undo_snapshot(i, "Lookup tables");
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_lookup = None;
self.active_modal = None;
Task::none()
}
}
+1 -2
View File
@@ -67,8 +67,7 @@ impl OpenCADStudio {
}
document.header.show_model_space = true;
crate::scene::block_visibility::finish_editing(&mut document, definition);
let record = document.block_records.get(&name).unwrap();
let base = record.base_point;
let base = crate::scene::render_graph::block_base_point(&document, &name);
let mut insert = Insert::new(&name, codec::types::Vector3::ZERO);
insert.common.layer = self.tabs[i].active_layer.clone();
insert.common.handle = document.allocate_handle();
+3
View File
@@ -575,6 +575,9 @@ impl OpenCADStudio {
return task;
}
}
result @ CmdResult::PlaceBlockSingleParameter { .. } => {
return self.handle_place_block_single_parameter(result);
}
result @ CmdResult::PlaceBlockOrientationParameter { .. } => {
return self.handle_place_block_orientation_parameter(result);
}
+20 -2
View File
@@ -137,6 +137,8 @@ impl OpenCADStudio {
return Some("Open a block in the Block Editor before placing a parameter.");
};
match kind.to_ascii_uppercase().as_str() {
"BASEPOINT"|"BASE"|"B" => crate::scene::block_basepoint::placement_error(&tab.scene.document,session.br_handle),
"LOOKUP"|"K" => crate::scene::block_lookup::placement_error(&tab.scene.document,session.br_handle),
"POINT" | "P" | "" => crate::scene::block_point_parameter::placement_error(
&tab.scene.document,
session.br_handle,
@@ -151,7 +153,7 @@ impl OpenCADStudio {
"VISIBILITY" | "V" => {
visibility::placement_error(&tab.scene.document, session.br_handle)
}
_ => Some("Supported parameter types: Point, Linear, Polar, XY, Rotation, Alignment, Flip, Visibility."),
_ => Some("Supported parameter types: Point, Linear, Polar, XY, Rotation, Alignment, Flip, Visibility, Lookup, Basepoint."),
}
}
@@ -389,11 +391,27 @@ impl OpenCADStudio {
return Some(Task::none());
}
match cmd {
cmd if cmd == "BLOOKUPTABLE" || cmd.starts_with("BLOOKUPTABLE ") => {
Some(self.open_lookup_table(cmd.strip_prefix("BLOOKUPTABLE").unwrap().trim()))
}
cmd if cmd == "BPARAMETER" || cmd.starts_with("BPARAMETER ") => {
let Some(block) = self.visibility_block(i, "BPARAMETER") else {
return Some(Task::none());
};
let parameter_type = cmd.strip_prefix("BPARAMETER").unwrap().trim();
if let Some(kind) =
crate::modules::block_editor::single_parameter::Kind::parse(parameter_type)
{
let command =
crate::modules::block_editor::single_parameter::SingleParameterCommand::new(
self.tabs[i].id,
block,
kind,
);
self.command_line.push_info(&command.prompt());
self.tabs[i].active_cmd = Some(Box::new(command));
return Some(Task::none());
}
if let Some(kind) =
crate::scene::block_orientation_parameter::Kind::parse(parameter_type)
{
@@ -428,7 +446,7 @@ impl OpenCADStudio {
{
self.command_line.push_error(
crate::t!(
"Supported parameter types: Point, Linear, Polar, XY, Rotation, Alignment, Flip, Visibility."
"Supported parameter types: Point, Linear, Polar, XY, Rotation, Alignment, Flip, Visibility, Lookup, Basepoint."
)
.as_ref(),
);
+6
View File
@@ -182,6 +182,12 @@ impl OpenCADStudio {
return Task::none();
}
}
if cmd == "BLOOKUPTABLE" || cmd.starts_with("BLOOKUPTABLE ") {
if let Some(error) = self.lookup_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!(
+78 -3
View File
@@ -5,6 +5,17 @@ use crate::app::{Message, OpenCADStudio};
fn fresh() -> OpenCADStudio {
let mut app = OpenCADStudio::new_for_test();
app.automation_op(r#"{"op":"new"}"#);
// These tests drive dock controls, not the hardware warning dialog.
// Software snapshots intentionally have no scene GPU adapter.
app.gpu_warning_silenced = crate::scene::pipeline::gpu_status()
.identity()
.or_else(|| crate::scene::pipeline::GpuStatus::NoRenderer.identity())
.unwrap();
app.pending_startup_modals
.retain(|modal| *modal != crate::app::ModalKind::GpuWarning);
if app.active_modal == Some(crate::app::ModalKind::GpuWarning) {
app.active_modal = None;
}
app
}
@@ -1095,6 +1106,65 @@ fn authoring_app() -> OpenCADStudio {
app
}
#[test]
fn lookup_basepoint_palette_and_table_dialog_real_controls() {
with_stack(|| {
use crate::ui::window::{authoring_palettes::Tab, block_lookup::Action as A};
let mut app = authoring_app();
let _ = app.dispatch_command("BAUTHORPALETTE");
for (id, point_text) in [
("authoring-tool-basepoint", "0.2,0.1"),
("authoring-tool-lookup", "0.5,0.2"),
] {
let point = authoring_control(&app, id);
authoring_click(&mut app, point);
assert_eq!(
app.tabs[app.active_tab].active_cmd.as_ref().unwrap().name(),
"BPARAMETER"
);
let _ = app.update(Message::CommandInput(point_text.into()));
let _ = app.update(Message::CommandSubmit);
}
let _ = app.run_command_line("BPARAMETER LINEAR 0,0 1,0 0,0.3");
authoring_capture(&app, "lookup-basepoint-placed");
let point = authoring_control(&app, Tab::Actions.widget_id());
authoring_click(&mut app, point);
let point = authoring_control(&app, "authoring-tool-lookup-table");
authoring_click(&mut app, point);
assert_eq!(app.active_modal, Some(crate::app::ModalKind::BlockLookup));
let _ = app.update(Message::BlockLookup(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_lookup.as_ref().unwrap().drafts[0]
.edited
.row_count,
1
);
let _ = app.update(Message::BlockLookup(A::Cell(0, 1, "Small".into())));
let point = find_in_view(&app, "Audit");
authoring_click(&mut app, point);
assert!(app.block_lookup.as_ref().unwrap().notice.is_some());
authoring_capture(&app, "lookup-property-table");
let point = find_in_view(&app, "OK");
authoring_click(&mut app, point);
assert!(app.block_lookup.is_none());
let i = app.active_tab;
let definition = app.tabs[i].active_block_edit_session().unwrap().br_handle;
let parameter =
crate::scene::block_lookup::parameters(&app.tabs[i].scene.document, definition)[0].0;
assert!(crate::scene::block_lookup::table(
&app.tabs[i].scene.document,
definition,
parameter
)
.unwrap()
.is_some());
});
}
#[test]
fn authoring_palettes_ribbon_toggle_and_dock_close_leave_document_and_history_unchanged() {
use crate::ui::dock::{DockMsg, PanelId};
@@ -1166,11 +1236,12 @@ fn authoring_palettes_toggle_keeps_the_running_drawing_command() {
}
#[test]
fn authoring_palettes_catalog_in_real_view_has_no_parameter_placement_action() {
fn authoring_palettes_unwired_move_action_does_not_start_a_command() {
with_stack(|| {
let mut app = authoring_app();
let _ = app.dispatch_command("BAUTHORPALETTE");
let point = find_in_view(&app, "Basepoint");
app.authoring_palette_tab = crate::ui::window::authoring_palettes::Tab::Actions;
let point = find_in_view(&app, "Move");
let before = app.tabs[app.active_tab].scene.document.clone();
drive_view(
&mut app,
@@ -1262,7 +1333,11 @@ fn authoring_palettes_real_tabs_switch_catalogs_and_unavailable_rows_stay_inert(
.active_cmd
.as_ref()
.map(|command| command.name()),
Some("LINE")
Some(if tab == Tab::Parameters {
"BPARAMETER"
} else {
"LINE"
})
);
}
assert_eq!(app.tabs[i].scene.document, before);
+1
View File
@@ -410,6 +410,7 @@ impl OpenCADStudio {
self.visibility_placement_error(),
self.point_parameter_placement_error(),
self.parameter_placement_error("FLIP"),
self.lookup_table_error(),
)
}
crate::ui::dock::PanelId::BlockPalette => {
+5
View File
@@ -13,6 +13,7 @@ mod block_editor;
mod block_editor_tests;
mod block_save_as;
mod block_visibility;
mod block_lookup;
mod block_properties;
#[cfg(test)]
mod block_properties_tests;
@@ -593,6 +594,7 @@ pub(super) struct OpenCADStudio {
block_edit_picker: Option<crate::ui::window::block_edit_picker::State>,
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_visibility_mode: bool,
/// PDF dialogs' working copies; `None` while closed.
pdf_attach: Option<crate::ui::window::pdf_dialogs::PdfAttachState>,
@@ -2295,6 +2297,7 @@ pub enum ModalKind {
BlockEditPicker,
BlockSaveAs,
BlockVisibility,
BlockLookup,
SaveDialog,
Recovery,
/// Fonts referenced by the drawing are missing on this machine.
@@ -2947,6 +2950,7 @@ pub enum Message {
BlockSaveAsName(String),
BlockSaveAsApply,
BlockVisibility(crate::ui::window::block_visibility::Action),
BlockLookup(crate::ui::window::block_lookup::Action),
BlockEditPickerName(String),
BlockEditPickerSelect(String),
BlockEditPickerApply,
@@ -4730,6 +4734,7 @@ impl OpenCADStudio {
block_edit_picker: None,
block_save_as: None,
block_visibility: None,
block_lookup: None,
block_visibility_mode: false,
pdf_attach: None,
point_cloud_attach: None,
+5
View File
@@ -237,6 +237,7 @@ impl OpenCADStudio {
Some(BlockEditPicker) => self.block_edit_picker = None,
Some(BlockSaveAs) => self.block_save_as = None,
Some(BlockVisibility) => self.block_visibility = None,
Some(BlockLookup) => self.block_lookup = None,
Some(PdfAttach) => self.pdf_attach = None,
Some(UnderlayLayers) => self.underlay_layers = None,
Some(PdfImportSettings) => self.pdf_import_settings = None,
@@ -9728,6 +9729,9 @@ impl OpenCADStudio {
}
Message::CloseModal => {
if self.active_modal == Some(super::ModalKind::BlockLookup) {
self.block_lookup = None;
}
if self.active_modal == Some(super::ModalKind::BlockVisibility) {
self.block_visibility = None;
}
@@ -10752,6 +10756,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::BlockEditPickerName(name) => {
if let Some(state) = self.block_edit_picker.as_mut() {
state.name = name.clone();
+35
View File
@@ -1119,6 +1119,31 @@ bg={bg_ms:.1}ms n={view_count}"
)
}),
)
.chain(
crate::scene::block_basepoint::parameter(
&tab.scene.document,
session.br_handle,
)
.map(|(_, p)| {
let mut g = crate::scene::block_distance_parameter::Graphic::default();
g.base_grips.push(p.parameter.definition_point);
(g, p.parameter.definition_point)
}),
)
.chain(
crate::scene::block_lookup::parameters(
&tab.scene.document,
session.br_handle,
)
.into_iter()
.map(|(_, p)| {
let mut g = crate::scene::block_distance_parameter::Graphic::default();
g.lookup_grips.push(p.parameter.definition_point);
g.labels
.push((p.parameter.definition_point, p.lookup_name.clone()));
(g, p.parameter.definition_point)
}),
)
.filter_map(|(graphic, marker_tail)| {
let project = |p: codec::types::Vector3| {
let p = tab
@@ -1155,6 +1180,16 @@ bg={bg_ms:.1}ms n={view_count}"
.collect::<Option<Vec<_>>>()?;
Some(crate::ui::overlay::AuthoringDimension {
lines,
base_grips: graphic
.base_grips
.into_iter()
.map(project)
.collect::<Option<Vec<_>>>()?,
lookup_grips: graphic
.lookup_grips
.into_iter()
.map(project)
.collect::<Option<Vec<_>>>()?,
round_grips: graphic
.round_grips
.into_iter()
+7
View File
@@ -88,6 +88,7 @@ impl OpenCADStudio {
Some(K::BlockEditChanges) => crate::t!("Block Editor").into_owned(),
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::BlockSaveAs) => crate::t!("Save Block As").into_owned(),
Some(K::PointStyle) => crate::tr!("modal", "point-style"),
Some(K::AttributeEditor) => crate::tr!("modal", "attribute-editor"),
@@ -522,6 +523,12 @@ impl OpenCADStudio {
crate::ui::window::block_edit_picker::view(state, flow)
})
}
super::super::ModalKind::BlockLookup => {
let state = self.block_lookup.as_ref()?;
sized_flow(ex, 940, 600, |flow| {
crate::ui::window::block_lookup::view(state, flow)
})
}
super::super::ModalKind::BlockVisibility => {
let state = self.block_visibility.as_ref()?;
sized_flow(ex, 580, 470, |flow| {