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

+127
View File
@@ -32,6 +32,46 @@ const MINOR: i32 = 329;
/// Value code of an expression that holds no value.
const NO_VALUE: i16 = -9999;
impl Scene {
/// BVMODE and BVSHOW picking reveal only state-controlled members of BEDIT.
pub(crate) fn visibility_revealed(&self, handle: Handle) -> bool {
self.block_visibility_reveal
&& self.block_edit_block.is_some_and(|definition| {
self.document.get_entity(handle).is_some_and(|entity| {
entity.common().owner_handle == definition && entity.common().invisible
}) && parameter(&self.document, definition)
.is_some_and(|parameter| parameter.all_blocks.contains(&handle))
})
}
pub(crate) fn visibility_reveal_handles(&self) -> rustc_hash::FxHashSet<Handle> {
self.block_edit_block
.and_then(|definition| parameter(&self.document, definition))
.filter(|_| self.block_visibility_reveal)
.map(|parameter| {
parameter
.all_blocks
.iter()
.copied()
.filter(|handle| self.visibility_revealed(*handle))
.collect()
})
.unwrap_or_default()
}
pub(crate) fn display_entity_invisible(&self, common: &codec::entities::EntityCommon) -> bool {
common.invisible && !self.visibility_revealed(common.handle)
}
pub(crate) fn set_block_visibility_reveal(&mut self, reveal: bool) {
if self.block_visibility_reveal != reveal {
self.block_visibility_reveal = reveal;
self.recolor_meshes();
self.bump_geometry();
}
}
}
/// (DXF name, class, DWG maintenance version) of the classes used here.
const CLASSES: &[(&str, &str, i32)] = &[
("ACAD_EVALUATION_GRAPH", "AcDbEvalGraph", 55),
@@ -131,6 +171,20 @@ fn store(document: &mut CadDocument, parameter: BlockVisibilityParameter) {
);
}
/// Commit a staged state list while retaining the parameter's graph and identity.
pub(crate) fn replace_states(
document: &mut CadDocument,
definition: Handle,
edited: BlockVisibilityParameter,
) {
debug_assert_eq!(
parameter(document, definition).map(|p| p.handle),
Some(edited.handle)
);
store(document, edited);
tag_members(document, definition);
}
/// The objects of a definition, block begin and end left out.
fn members(document: &CadDocument, definition: Handle) -> Vec<Handle> {
document
@@ -784,6 +838,79 @@ mod tests {
use super::*;
use codec::entities::{Circle, Insert, Line};
#[test]
fn visibility_ribbon_reveal_respects_layers_uncontrolled_flags_and_nested_states() {
let mut scene = Scene::new();
scene.define_block_raw(
"nested",
Vector3::ZERO,
vec![EntityType::Line(Line::from_points(
Vector3::ZERO,
Vector3::UNIT_X,
))],
);
let nested = scene.document.block_records.get("nested").unwrap().handle;
let child = members(&scene.document, nested)[0];
scene
.document
.get_entity_mut(child)
.unwrap()
.common_mut()
.invisible = true;
scene.define_block_raw(
"editor",
Vector3::ZERO,
vec![
EntityType::Line(Line::from_points(Vector3::ZERO, Vector3::UNIT_X)),
EntityType::Line(Line::from_points(
Vector3::UNIT_Y,
Vector3::UNIT_X + Vector3::UNIT_Y,
)),
EntityType::Insert(Insert::new("nested", Vector3::ZERO)),
],
);
let definition = scene.document.block_records.get("editor").unwrap().handle;
let controlled = members(&scene.document, definition)[0];
let independent = members(&scene.document, definition)[1];
scene
.document
.get_entity_mut(independent)
.unwrap()
.common_mut()
.invisible = true;
let parameter = create(&mut scene.document, definition, Vector3::ZERO).unwrap();
set_visible(&mut scene.document, parameter, 0, &[controlled], false);
apply_state(&mut scene.document, parameter, 0);
scene.block_edit_block = Some(definition);
let before = scene.document.clone();
scene.set_block_visibility_reveal(true);
let wires = scene.entity_wires();
assert!(wires
.iter()
.any(|wire| Scene::handle_from_wire_name(&wire.name) == Some(controlled)));
assert!(!scene.visibility_revealed(independent));
assert!(!scene.visibility_revealed(child));
assert_eq!(scene.visibility_reveal_handles().len(), 1);
assert_eq!(
scene.select_all_visible(),
2,
"only revealed line and the insert can be selected"
);
let color = scene.count_color(controlled, [1.0; 4], [0.0, 0.0, 0.0, 1.0]);
assert_eq!(color, [0.5, 0.5, 0.5, 1.0]);
scene.document.layers.get_mut("0").unwrap().flags.off = true;
scene.bump_geometry();
assert_eq!(
scene.select_all_visible(),
0,
"mode never overrides a hidden layer"
);
scene.document.layers.get_mut("0").unwrap().flags.off = false;
scene.set_block_visibility_reveal(false);
assert_eq!(scene.document, before);
assert!(!scene.visibility_revealed(controlled));
}
/// A block given visibility states round-trips them, and a reference
/// given its own copy still finds the definition and its states.
#[test]