forked from 1b-agent/opencadstudio-upstream
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 <noreply@anthropic.com>
This commit is contained in:
commit
1b04bda547
6 files changed
+82
-6
No files matched your search
@@ -200,6 +200,7 @@ impl OpenCADStudio {
|
|||||||
pub(super) fn handle_commit_and_exit(
|
pub(super) fn handle_commit_and_exit(
|
||||||
&mut self,
|
&mut self,
|
||||||
entity: codec::EntityType,
|
entity: codec::EntityType,
|
||||||
|
preserve_commit_layer: bool,
|
||||||
) -> Option<Task<Message>> {
|
) -> Option<Task<Message>> {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
// XATTACH: the definition and its INSERT are created together
|
// XATTACH: the definition and its INSERT are created together
|
||||||
@@ -255,7 +256,11 @@ impl OpenCADStudio {
|
|||||||
);
|
);
|
||||||
let association_enabled =
|
let association_enabled =
|
||||||
self.tabs[i].scene.document.header.dimension_associativity == 2;
|
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 is_associative_dimension && association_enabled {
|
||||||
if let Some(handle) = committed {
|
if let Some(handle) = committed {
|
||||||
let sources = self.infer_dimension_sources_guarded(i, handle);
|
let sources = self.infer_dimension_sources_guarded(i, handle);
|
||||||
|
|||||||
@@ -507,7 +507,7 @@ impl OpenCADStudio {
|
|||||||
self.handle_copy_to_clipboard(handles, base);
|
self.handle_copy_to_clipboard(handles, base);
|
||||||
}
|
}
|
||||||
CmdResult::CommitAndExit(entity) => {
|
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;
|
return task;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -350,6 +350,11 @@ impl<'a> HostSession<'a> {
|
|||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
let handle = self.app.tabs[self.tab].scene.add_entity(entity);
|
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();
|
self.publish_document_view();
|
||||||
handle
|
handle
|
||||||
}
|
}
|
||||||
@@ -362,6 +367,11 @@ impl<'a> HostSession<'a> {
|
|||||||
let _ = self.normalize_scripted_entity(None, entity);
|
let _ = self.normalize_scripted_entity(None, entity);
|
||||||
}
|
}
|
||||||
let handles = self.app.tabs[self.tab].scene.add_entities(entities);
|
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();
|
self.publish_document_view();
|
||||||
handles
|
handles
|
||||||
}
|
}
|
||||||
@@ -381,6 +391,11 @@ impl<'a> HostSession<'a> {
|
|||||||
EntityType::AttributeEntity(attribute) => self.replace_nested_attribute(attribute),
|
EntityType::AttributeEntity(attribute) => self.replace_nested_attribute(attribute),
|
||||||
entity => self.app.tabs[self.tab].scene.update_entity(entity),
|
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 {
|
if ok {
|
||||||
self.publish_document_view();
|
self.publish_document_view();
|
||||||
}
|
}
|
||||||
@@ -439,6 +454,11 @@ impl<'a> HostSession<'a> {
|
|||||||
};
|
};
|
||||||
assert!(updated);
|
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.set_dirty();
|
||||||
self.publish_document_view();
|
self.publish_document_view();
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -9697,6 +9717,44 @@ step('undo_move', lambda: M.move([u], (0, 0, 0), (7, 0, 0)))
|
|||||||
assert!(!host.modify_layer(non_existent));
|
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]
|
#[test]
|
||||||
fn test_plugin_execute_command() {
|
fn test_plugin_execute_command() {
|
||||||
let mut app = OpenCADStudio::new_for_test();
|
let mut app = OpenCADStudio::new_for_test();
|
||||||
|
|||||||
@@ -3491,6 +3491,11 @@ handles={handles_ms:.1} panel={:.1} ribbon={ribbon_ms:.1} tail={:.1} selected={}
|
|||||||
if tracks_dimension_chain {
|
if tracks_dimension_chain {
|
||||||
self.tabs[i].scene.last_created_dimension = new_handle;
|
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
|
new_handle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-4
@@ -143,13 +143,17 @@ impl Scene {
|
|||||||
/// layer 0. Auto-registering keeps it on its own layer (#252). Names are
|
/// layer 0. Auto-registering keeps it on its own layer (#252). Names are
|
||||||
/// registered verbatim so the writer's (case-insensitive) lookup matches;
|
/// registered verbatim so the writer's (case-insensitive) lookup matches;
|
||||||
/// the always-present default layer "0" and empty names are no-ops.
|
/// the always-present default layer "0" and empty names are no-ops.
|
||||||
pub fn ensure_layer(&mut self, name: &str) {
|
pub fn ensure_layer(&mut self, name: &str) -> bool {
|
||||||
if name.trim().is_empty() || self.document.layers.contains(name) {
|
let trimmed = name.trim();
|
||||||
return;
|
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();
|
layer.handle = self.document.allocate_handle();
|
||||||
let _ = self.document.layers.add(layer);
|
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) {
|
pub(crate) fn ensure_app_id(&mut self, name: &str) {
|
||||||
|
|||||||
@@ -2054,6 +2054,9 @@ pub struct Scene {
|
|||||||
pub preview_text: Vec<crate::scene::pipeline::text_gpu::TextVertex>,
|
pub preview_text: Vec<crate::scene::pipeline::text_gpu::TextVertex>,
|
||||||
/// Committed-segment wire drawn during multi-point commands (normal colour).
|
/// Committed-segment wire drawn during multi-point commands (normal colour).
|
||||||
pub interim_wire: Option<WireModel>,
|
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,
|
pub camera_generation: u64,
|
||||||
/// Incremented whenever geometry-affecting state changes (entities, selection,
|
/// Incremented whenever geometry-affecting state changes (entities, selection,
|
||||||
/// preview wires, layer visibility, layout). The GPU pipeline uses this to
|
/// preview wires, layer visibility, layout). The GPU pipeline uses this to
|
||||||
@@ -2626,6 +2629,7 @@ impl Scene {
|
|||||||
preview_hatches: Arc::new(Vec::new()),
|
preview_hatches: Arc::new(Vec::new()),
|
||||||
preview_text: vec![],
|
preview_text: vec![],
|
||||||
interim_wire: None,
|
interim_wire: None,
|
||||||
|
layer_table_dirty: false,
|
||||||
camera_generation: 0,
|
camera_generation: 0,
|
||||||
geometry_epoch: GEOMETRY_EPOCH.fetch_add(1, Ordering::Relaxed),
|
geometry_epoch: GEOMETRY_EPOCH.fetch_add(1, Ordering::Relaxed),
|
||||||
projection_bounds_epoch: std::cell::Cell::new(0),
|
projection_bounds_epoch: std::cell::Cell::new(0),
|
||||||
|
|||||||
Reference in new issue
Block a user