Files
OneCAD-Draft/src/app/block_test.rs
T
Karim Jerbi 10c5a9d382 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.
2026-10-16 15:29:08 +01:00

163 lines
6.6 KiB
Rust

use super::{document::DocumentTab, Message, OpenCADStudio};
use codec::entities::{AttributeEntity, Entity, Insert};
use codec::EntityType;
use iced::Task;
pub(super) struct BlockTestSession {
pub source_id: u64,
}
impl OpenCADStudio {
pub(super) fn refuse_block_test_save(&mut self, i: usize) -> bool {
if self.tabs[i].block_test.is_none() {
return false;
}
self.command_line.push_error(
crate::t!("Test Block is temporary. Close it and save from the Block Editor.").as_ref(),
);
true
}
pub(super) fn open_block_test(&mut self, i: usize) -> Task<Message> {
let source_id = self.tabs[i]
.block_test
.as_ref()
.map_or(self.tabs[i].id, |test| test.source_id);
let Some(i) = self.tabs.iter().position(|tab| tab.id == source_id) else {
return Task::none();
};
let Some(session) = self.tabs[i].active_block_edit_session() else {
self.command_line
.push_error(crate::t!("No block editor is open.").as_ref());
return Task::none();
};
let name = session.block_name.clone();
let definition = session.br_handle;
self.finish_pending_history(i);
let mut document = self.tabs[i].scene.document.clone();
// Keep definitions and their native relationships in the clone. Only
// drawing-space contents are replaced by the temporary reference.
let spaces: Vec<_> = document
.block_records
.iter()
.filter(|record| record.is_model_space() || record.is_paper_space())
.map(|record| record.handle)
.collect();
let removed: Vec<_> = document
.entities()
.filter(|entity| {
spaces.contains(&entity.common().owner_handle)
&& !matches!(entity, EntityType::Block(_) | EntityType::BlockEnd(_))
})
.map(|entity| entity.common().handle)
.collect();
for handle in removed {
document.remove_entity(handle);
}
for record in document
.block_records
.iter_mut()
.filter(|record| spaces.contains(&record.handle))
{
record.entity_handles.retain(|handle| {
*handle == record.block_entity_handle || *handle == record.block_end_handle
});
record.insert_handles.clear();
record.insert_count_bytes.clear();
}
document.header.show_model_space = true;
crate::scene::block_visibility::finish_editing(&mut document, definition);
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();
insert.common.owner_handle = document.header.model_space_block_handle;
let transform = crate::scene::render_graph::insert_transform_with_base(&insert, base);
for entity in document.entities_in_block(&name) {
if let EntityType::AttributeDefinition(definition) = entity {
if !definition.flags.constant {
let mut attribute = AttributeEntity::from_definition(definition, None);
attribute.attdef_handle = definition.common.handle;
attribute.apply_transform(&transform);
insert.attributes.push(attribute);
}
}
}
for attribute in &mut insert.attributes {
attribute.common.handle = document.allocate_handle();
attribute.common.owner_handle = insert.common.handle;
if let Some(text) = &mut attribute.embedded_mtext {
text.common.handle = document.allocate_handle();
text.common.owner_handle = attribute.common.handle;
text.common.raw_record = None;
text.common.xdictionary_handle = None;
text.common.reactors.clear();
}
}
if !insert.attributes.is_empty() {
insert.seqend_handle = Some(document.allocate_handle());
}
if let Err(error) = document.add_entity(EntityType::Insert(insert)) {
self.command_line.push_error(&error.to_string());
return Task::none();
}
let mut tab = DocumentTab::new_drawing(0);
tab.tab_title = crate::tf!("Test Block Window - {}", name).into_owned();
tab.block_test = Some(BlockTestSession { source_id });
tab.active_layer = self.tabs[i].active_layer.clone();
tab.scene.document = document;
tab.scene.named_parameters = self.tabs[i].scene.named_parameters.clone();
tab.scene.parametric_constraints = self.tabs[i]
.scene
.parametric_constraints
.iter()
.filter(|set| {
matches!(
set.scope,
crate::scene::parametric_constraints::ParametricScope::Block(_)
)
})
.cloned()
.collect();
tab.scene.material_base_dir = self.tabs[i].scene.material_base_dir.clone();
tab.scene.sync_annotation_scale_from_header();
tab.scene.rebuild_derived_caches();
tab.scene.set_current_layout("Model".into());
self.discard_block_tests(source_id);
self.visibility_popup = None;
self.grip_popup = None;
self.grip_hover = None;
self.tabs.push(tab);
self.active_tab = self.tabs.len() - 1;
self.sync_ribbon_layers();
self.sync_ribbon_styles();
self.sync_ribbon_from_selection();
self.refresh_block_palette();
self.refresh_properties();
self.ribbon.set_block_editing(false);
self.ribbon.set_block_testing(true);
self.dispatch_command("ZOOM EXTENTS")
}
pub(super) fn close_block_test(&mut self, i: usize) -> Task<Message> {
if self.tabs[i].block_test.is_none() {
self.command_line
.push_error(crate::t!("No Test Block is open.").as_ref());
return Task::none();
}
self.on_tab_close(i)
}
pub(super) fn discard_block_tests(&mut self, source_id: u64) {
for index in (0..self.tabs.len()).rev() {
if self.tabs[index]
.block_test
.as_ref()
.is_some_and(|test| test.source_id == source_id)
{
let _ = self.on_tab_close(index);
}
}
}
}