Merge PR #1599 from yztink/fix/novel-layer-gui-refresh: refresh ribbon and layer panel when entities register novel layers

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-03 14:58:03 +03:00
commit 1b04bda547
6 files changed
+82 -6

No files matched your search

+6 -1
View File
@@ -200,6 +200,7 @@ impl OpenCADStudio {
pub(super) fn handle_commit_and_exit(
&mut self,
entity: codec::EntityType,
preserve_commit_layer: bool,
) -> Option<Task<Message>> {
let i = self.active_tab;
// XATTACH: the definition and its INSERT are created together
@@ -255,7 +256,11 @@ impl OpenCADStudio {
);
let association_enabled =
self.tabs[i].scene.document.header.dimension_associativity == 2;
let committed = self.commit_entity_handle(entity);
let committed = if preserve_commit_layer {
self.commit_entity_handle_preserve_layer(entity)
} else {
self.commit_entity_handle(entity)
};
if is_associative_dimension && association_enabled {
if let Some(handle) = committed {
let sources = self.infer_dimension_sources_guarded(i, handle);
+1 -1
View File
@@ -507,7 +507,7 @@ impl OpenCADStudio {
self.handle_copy_to_clipboard(handles, base);
}
CmdResult::CommitAndExit(entity) => {
if let Some(task) = self.handle_commit_and_exit(entity) {
if let Some(task) = self.handle_commit_and_exit(entity, preserve_commit_layer) {
return task;
}
}
+58
View File
@@ -350,6 +350,11 @@ impl<'a> HostSession<'a> {
return handle;
}
let handle = self.app.tabs[self.tab].scene.add_entity(entity);
if self.app.tabs[self.tab].scene.layer_table_dirty {
self.app.tabs[self.tab].scene.layer_table_dirty = false;
self.app.tabs[self.tab].dirty = true;
self.app.refresh_layer_panel();
}
self.publish_document_view();
handle
}
@@ -362,6 +367,11 @@ impl<'a> HostSession<'a> {
let _ = self.normalize_scripted_entity(None, entity);
}
let handles = self.app.tabs[self.tab].scene.add_entities(entities);
if self.app.tabs[self.tab].scene.layer_table_dirty {
self.app.tabs[self.tab].scene.layer_table_dirty = false;
self.app.tabs[self.tab].dirty = true;
self.app.refresh_layer_panel();
}
self.publish_document_view();
handles
}
@@ -381,6 +391,11 @@ impl<'a> HostSession<'a> {
EntityType::AttributeEntity(attribute) => self.replace_nested_attribute(attribute),
entity => self.app.tabs[self.tab].scene.update_entity(entity),
};
if self.app.tabs[self.tab].scene.layer_table_dirty {
self.app.tabs[self.tab].scene.layer_table_dirty = false;
self.app.tabs[self.tab].dirty = true;
self.app.refresh_layer_panel();
}
if ok {
self.publish_document_view();
}
@@ -439,6 +454,11 @@ impl<'a> HostSession<'a> {
};
assert!(updated);
}
if self.app.tabs[self.tab].scene.layer_table_dirty {
self.app.tabs[self.tab].scene.layer_table_dirty = false;
self.app.tabs[self.tab].dirty = true;
self.app.refresh_layer_panel();
}
self.set_dirty();
self.publish_document_view();
Ok(())
@@ -9697,6 +9717,44 @@ step('undo_move', lambda: M.move([u], (0, 0, 0), (7, 0, 0)))
assert!(!host.modify_layer(non_existent));
}
#[test]
fn test_entity_creation_with_novel_layer_refreshes_gui() {
let mut app = OpenCADStudio::new_for_test();
app.tabs[0].is_start = false;
// Verify COGO_LINES does not exist initially
assert!(!app.tabs[0].scene.document.layers.contains("COGO_LINES"));
assert!(!app.ribbon.layer_names.contains(&"COGO_LINES".to_string()));
// 1. Add entity via HostSession (simulating plugin add_entity / add_line)
{
let mut host = HostSession::new(&mut app, 0);
let mut line = codec::entities::Line::from_coords(0.0, 0.0, 0.0, 10.0, 10.0, 0.0);
line.common.layer = "COGO_LINES".to_string();
let handle = host.add_entity(codec::EntityType::Line(line));
assert_ne!(handle, codec::Handle::NULL);
}
// Verify layer is created in document AND refreshed in ribbon + layer panel
assert!(app.tabs[0].scene.document.layers.contains("COGO_LINES"));
assert!(app.ribbon.layer_names.contains(&"COGO_LINES".to_string()),
"Ribbon layers should contain COGO_LINES after add_entity");
assert!(app.tabs[0].layers.layers.iter().any(|l| l.name == "COGO_LINES"),
"Layer panel should contain COGO_LINES after add_entity");
// 2. Commit entity via command driver (simulating interactive tool / palette commit)
let mut line2 = codec::entities::Line::from_coords(10.0, 10.0, 0.0, 20.0, 20.0, 0.0);
line2.common.layer = "SURVEY_TRAVERSE".to_string();
let _ = app.commit_entity_handle_preserve_layer(codec::EntityType::Line(line2));
// Verify SURVEY_TRAVERSE layer is created and refreshed in ribbon + layer panel
assert!(app.tabs[0].scene.document.layers.contains("SURVEY_TRAVERSE"));
assert!(app.ribbon.layer_names.contains(&"SURVEY_TRAVERSE".to_string()),
"Ribbon layers should contain SURVEY_TRAVERSE after commit_entity");
assert!(app.tabs[0].layers.layers.iter().any(|l| l.name == "SURVEY_TRAVERSE"),
"Layer panel should contain SURVEY_TRAVERSE after commit_entity");
}
#[test]
fn test_plugin_execute_command() {
let mut app = OpenCADStudio::new_for_test();
+5
View File
@@ -3491,6 +3491,11 @@ handles={handles_ms:.1} panel={:.1} ribbon={ribbon_ms:.1} tail={:.1} selected={}
if tracks_dimension_chain {
self.tabs[i].scene.last_created_dimension = new_handle;
}
if self.tabs[i].scene.layer_table_dirty {
self.tabs[i].scene.layer_table_dirty = false;
self.tabs[i].dirty = true;
self.refresh_layer_panel();
}
new_handle
}
}
+8 -4
View File
@@ -143,13 +143,17 @@ impl Scene {
/// layer 0. Auto-registering keeps it on its own layer (#252). Names are
/// registered verbatim so the writer's (case-insensitive) lookup matches;
/// the always-present default layer "0" and empty names are no-ops.
pub fn ensure_layer(&mut self, name: &str) {
if name.trim().is_empty() || self.document.layers.contains(name) {
return;
pub fn ensure_layer(&mut self, name: &str) -> bool {
let trimmed = name.trim();
if trimmed.is_empty() || trimmed == "0" || self.document.layers.contains(trimmed) {
return false;
}
let mut layer = codec::tables::Layer::new(name);
let mut layer = codec::tables::Layer::new(trimmed);
layer.handle = self.document.allocate_handle();
let _ = self.document.layers.add(layer);
self.invalidate_layer_dependencies(&[trimmed.to_string()]);
self.layer_table_dirty = true;
true
}
pub(crate) fn ensure_app_id(&mut self, name: &str) {
+4
View File
@@ -2054,6 +2054,9 @@ pub struct Scene {
pub preview_text: Vec<crate::scene::pipeline::text_gpu::TextVertex>,
/// Committed-segment wire drawn during multi-point commands (normal colour).
pub interim_wire: Option<WireModel>,
/// Set when an entity creation or update automatically registers a novel
/// layer into the document layer table, signalling the UI to refresh.
pub layer_table_dirty: bool,
pub camera_generation: u64,
/// Incremented whenever geometry-affecting state changes (entities, selection,
/// preview wires, layer visibility, layout). The GPU pipeline uses this to
@@ -2626,6 +2629,7 @@ impl Scene {
preview_hatches: Arc::new(Vec::new()),
preview_text: vec![],
interim_wire: None,
layer_table_dirty: false,
camera_generation: 0,
geometry_epoch: GEOMETRY_EPOCH.fetch_add(1, Ordering::Relaxed),
projection_bounds_epoch: std::cell::Cell::new(0),