feat(block-editor): add functional Visibility ribbon controls

Add the current-state selector, Visibility States dialog and mode/show/hide
controls to the contextual ribbon, using shared icons, tooltips, modal UI
and adaptive flyouts. Disable the controls until a Visibility parameter exists.

Stage state creation, rename, deletion, current-state changes and reordering
until OK. Support copied, show-all and hide-all new states in one undo step,
preserve native graph metadata, and restore current-state identity on undo/redo.

Implement nonsaved BVMODE and display-only reveal during BVSHOW selection.
Cancelling selection leaves saved visibility flags and history unchanged.
Keep the existing command-line state workflows available.

Validation: 6 targeted tests; 277 block, 31 app dock, 35 ribbon and 12 history
checks passed (overlapping filters). All 17 local DWG fixtures passed in-memory
DWG/DXF preservation checks with unchanged original checksums.
This commit is contained in:
Karim Jerbi committed 2026-10-15 15:08:21 +01:00
1 parent 308990e672
commit daa26007f9
24 files changed
+1341 -34

No files matched your search

+58 -20
View File
@@ -97,12 +97,16 @@ impl OpenCADStudio {
}
/// The state the block shows in the editor.
fn visibility_current(&self, i: usize, block: Handle) -> usize {
pub(in crate::app) fn visibility_current(&self, i: usize, block: Handle) -> usize {
let scene = &self.tabs[i].scene;
scene
.visibility_states
.get(&block)
.copied()
.filter(|index| {
visibility::parameter(&scene.document, block)
.is_some_and(|parameter| *index < parameter.states.len())
})
.unwrap_or_else(|| {
visibility::parameter(&scene.document, block).map_or(0, |parameter| {
visibility::shown_state(&scene.document, parameter.handle)
@@ -110,7 +114,13 @@ impl OpenCADStudio {
})
}
fn show_visibility_state(&mut self, i: usize, block: Handle, parameter: Handle, state: usize) {
pub(in crate::app) fn show_visibility_state(
&mut self,
i: usize,
block: Handle,
parameter: Handle,
state: usize,
) {
visibility::apply_state(&mut self.tabs[i].scene.document, parameter, state);
self.tabs[i].scene.visibility_states.insert(block, state);
self.tabs[i].scene.bump_geometry();
@@ -143,6 +153,40 @@ impl OpenCADStudio {
}
}
}
let variable = cmd.strip_prefix("SETVAR ").unwrap_or(cmd);
if variable == "BVMODE" || variable.starts_with("BVMODE ") {
let value = variable.strip_prefix("BVMODE").unwrap().trim();
// Inline command probing may have opened the bare variable prompt.
if !value.is_empty() && self.pending_setvar.as_deref() == Some("BVMODE") {
self.pending_setvar = None;
}
match value {
"" => {
self.command_line.push_output(
crate::tf!(
"BVMODE: enter 0 (hide) or 1 (show dimmed) <{}>:",
u8::from(self.block_visibility_mode)
)
.as_ref(),
);
self.pending_setvar = Some("BVMODE".into());
}
"TOGGLE" => {
self.block_visibility_mode = !self.block_visibility_mode;
self.sync_block_visibility_display();
}
value @ ("0" | "1") => {
self.block_visibility_mode = value == "1";
self.sync_block_visibility_display();
self.command_line
.push_output(crate::tf!("BVMODE = {}", value).as_ref());
}
_ => self
.command_line
.push_error(crate::t!("BVMODE: only 0 or 1 is allowed.").as_ref()),
}
return Some(Task::none());
}
match cmd {
cmd if cmd == "BPARAMETER" || cmd.starts_with("BPARAMETER ") => {
let Some(block) = self.visibility_block(i, "BPARAMETER") else {
@@ -165,6 +209,17 @@ impl OpenCADStudio {
self.tabs[i].active_cmd = Some(Box::new(command));
Some(Task::none())
}
"BVSTATE DIALOG" => {
if let Some(context) = self.block_visibility_context() {
return Some(self.handle_block_visibility(
crate::ui::window::block_visibility::Action::Open(context.target),
));
}
self.command_line.push_error(
crate::t!("Place a Visibility parameter in the Block Editor first.").as_ref(),
);
Some(Task::none())
}
"BVSTATE" => {
let block = self.visibility_block(i, "BVSTATE")?;
let document = &self.tabs[i].scene.document;
@@ -221,25 +276,8 @@ impl OpenCADStudio {
Some(Task::none())
}
"BVHIDE" | "BVSHOW" => {
let block = self.visibility_block(i, cmd)?;
self.visibility_block(i, cmd)?;
let hide = cmd == "BVHIDE";
if !hide {
// Every object of the block shows while they are picked;
// the state is put back once the pick ends.
let document = &mut self.tabs[i].scene.document;
let members: Vec<Handle> = document
.block_records
.iter()
.find(|record| record.handle == block)
.map(|record| record.entity_handles.clone())
.unwrap_or_default();
for handle in members {
if let Some(entity) = document.get_entity_mut(handle) {
entity.common_mut().invisible = false;
}
}
self.tabs[i].scene.bump_geometry();
}
let pending = if hide { "_BVHIDE" } else { "_BVSHOW" };
if !self.tabs[i].scene.selected.is_empty() {
return Some(self.dispatch_command(pending));