forked from 1b-agent/opencadstudio-upstream
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:
1 parent
308990e672
commit
daa26007f9
24 files changed
+1341
-34
No files matched your search
@@ -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]
|
||||
|
||||
+13
-1
@@ -1510,7 +1510,7 @@ impl Scene {
|
||||
return false;
|
||||
}
|
||||
let c = entity.common();
|
||||
if c.invisible
|
||||
if self.display_entity_invisible(c)
|
||||
|| self.entity_temporarily_hidden(handle)
|
||||
|| layer_hidden(&c.layer)
|
||||
|| crate::scene::annotative::annotative_offscale_for(
|
||||
@@ -1811,6 +1811,11 @@ impl Scene {
|
||||
plot_only: bool,
|
||||
) -> Vec<HatchModel> {
|
||||
let depth_map = self.draw_depth_map();
|
||||
let visibility_reveal = if plot_only {
|
||||
rustc_hash::FxHashSet::default()
|
||||
} else {
|
||||
self.visibility_reveal_handles()
|
||||
};
|
||||
let graph = crate::scene::render_graph::RenderSceneGraph::new(
|
||||
&self.document,
|
||||
frozen,
|
||||
@@ -1818,6 +1823,7 @@ impl Scene {
|
||||
all_visible,
|
||||
depth_map.as_ref(),
|
||||
)
|
||||
.with_visibility_reveal(&visibility_reveal)
|
||||
.with_annotation_scale(self.annotation_scale)
|
||||
.with_viewport(viewport);
|
||||
let mut hatch_block_memo = std::collections::HashMap::new();
|
||||
@@ -2022,6 +2028,11 @@ impl Scene {
|
||||
plot_only: bool,
|
||||
) -> Vec<HatchModel> {
|
||||
let depth_map = self.draw_depth_map();
|
||||
let visibility_reveal = if plot_only {
|
||||
rustc_hash::FxHashSet::default()
|
||||
} else {
|
||||
self.visibility_reveal_handles()
|
||||
};
|
||||
let graph = crate::scene::render_graph::RenderSceneGraph::new(
|
||||
&self.document,
|
||||
frozen,
|
||||
@@ -2029,6 +2040,7 @@ impl Scene {
|
||||
all_visible,
|
||||
depth_map.as_ref(),
|
||||
)
|
||||
.with_visibility_reveal(&visibility_reveal)
|
||||
.with_annotation_scale(self.annotation_scale);
|
||||
let mut models = Vec::new();
|
||||
let mut wipeout_sources = rustc_hash::FxHashMap::default();
|
||||
|
||||
+67
-8
@@ -2149,6 +2149,7 @@ pub struct Scene {
|
||||
/// The visibility state each dynamic block shows in the Block Editor,
|
||||
/// by block record.
|
||||
pub visibility_states: HashMap<Handle, usize>,
|
||||
pub(crate) block_visibility_reveal: bool,
|
||||
/// Currently selected entity handles.
|
||||
pub selected: HashSet<Handle>,
|
||||
/// Polylines only part of which is selected (Ctrl+click): the picked
|
||||
@@ -2871,6 +2872,7 @@ impl Scene {
|
||||
paper_projected_cache: RefCell::new(HashMap::default()),
|
||||
current_layout: "Model".to_string(),
|
||||
block_edit_block: None,
|
||||
block_visibility_reveal: false,
|
||||
viewcube_ucs: glam::Mat4::IDENTITY,
|
||||
hatches: HashMap::default(),
|
||||
meshes: HashMap::default(),
|
||||
@@ -4558,6 +4560,11 @@ impl Scene {
|
||||
/// top-level entity): the count colours, else faded; unchanged outside
|
||||
/// count mode.
|
||||
pub(crate) fn count_color(&self, handle: Handle, color: [f32; 4], bg: [f32; 4]) -> [f32; 4] {
|
||||
let color = if self.visibility_revealed(handle) {
|
||||
crate::scene::cache::block_cache::fade_toward_bg(color, bg)
|
||||
} else {
|
||||
color
|
||||
};
|
||||
let Some(count) = &self.count_display else {
|
||||
return color;
|
||||
};
|
||||
@@ -4578,7 +4585,9 @@ impl Scene {
|
||||
fn apply_refedit_fade(&self, wires: &mut Vec<WireModel>, bg: [f32; 4]) {
|
||||
// Block copies kept as stubs draw in their prototype's colour; while
|
||||
// copies are recoloured one by one they are expanded instead.
|
||||
if (self.count_display.is_some() || self.refedit_keep.is_some())
|
||||
if (self.count_display.is_some()
|
||||
|| self.refedit_keep.is_some()
|
||||
|| self.block_visibility_reveal)
|
||||
&& wires.iter().any(|wire| wire.stub.is_some())
|
||||
{
|
||||
*wires = crate::scene::model::instance_model::expand_stubs(wires).into_owned();
|
||||
@@ -4603,6 +4612,19 @@ impl Scene {
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.block_visibility_reveal {
|
||||
for wire in wires.iter_mut() {
|
||||
if Self::handle_from_wire_name(&wire.name)
|
||||
.is_some_and(|handle| self.visibility_revealed(handle))
|
||||
{
|
||||
wire.color = crate::scene::cache::block_cache::fade_toward_bg(wire.color, bg);
|
||||
for vertex in &mut wire.text_verts {
|
||||
vertex.color =
|
||||
crate::scene::cache::block_cache::fade_toward_bg(vertex.color, bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let Some(keep) = &self.refedit_keep else {
|
||||
return;
|
||||
};
|
||||
@@ -6665,6 +6687,7 @@ impl Scene {
|
||||
/// Persisted `EntityCommon::invisible` values stay untouched and continue
|
||||
/// to serve file/dynamic-block visibility.
|
||||
pub fn reset_transient_visibility(&mut self) {
|
||||
self.block_visibility_reveal = false;
|
||||
self.object_isolation = ObjectIsolationState::default();
|
||||
self.preview_hidden.clear();
|
||||
self.command_preview_hidden.clear();
|
||||
@@ -7398,6 +7421,7 @@ impl Scene {
|
||||
fn resident_runs_match_memo(&self, block: Handle) -> bool {
|
||||
self.count_display.is_none()
|
||||
&& self.refedit_keep.is_none()
|
||||
&& !self.block_visibility_reveal
|
||||
&& self.active_live_section(block).is_none()
|
||||
}
|
||||
|
||||
@@ -8597,6 +8621,11 @@ impl Scene {
|
||||
return Vec::new();
|
||||
}
|
||||
let depth_map = self.draw_depth_map();
|
||||
let visibility_reveal = if plotting {
|
||||
HashSet::default()
|
||||
} else {
|
||||
self.visibility_reveal_handles()
|
||||
};
|
||||
let graph = render_graph::RenderSceneGraph::new(
|
||||
&self.document,
|
||||
frozen,
|
||||
@@ -8604,6 +8633,7 @@ impl Scene {
|
||||
all_visible,
|
||||
depth_map.as_ref(),
|
||||
)
|
||||
.with_visibility_reveal(&visibility_reveal)
|
||||
.with_annotation_scale(self.annotation_scale)
|
||||
.with_viewport(viewport);
|
||||
let mut models = Vec::new();
|
||||
@@ -8686,6 +8716,15 @@ impl Scene {
|
||||
translation: [matrix[0][3], matrix[1][3], matrix[2][3]],
|
||||
});
|
||||
}
|
||||
if !plotting
|
||||
&& self.visibility_revealed(if context.is_instanced() {
|
||||
context.root_handle
|
||||
} else {
|
||||
handle
|
||||
})
|
||||
{
|
||||
placed.opacity *= 0.3;
|
||||
}
|
||||
placed.draw_depth = context.draw_depth(handle, depth_map.as_ref());
|
||||
models.push(collect(placed, context));
|
||||
},
|
||||
@@ -8968,6 +9007,20 @@ impl Scene {
|
||||
material.diffuse[3] = style.0[3];
|
||||
}
|
||||
}
|
||||
if self.visibility_revealed(handle) {
|
||||
for mesh in &mut set.lods {
|
||||
mesh.color = crate::scene::cache::block_cache::fade_toward_bg(
|
||||
mesh.color,
|
||||
self.current_bg(),
|
||||
);
|
||||
}
|
||||
if let Some(material) = set.material.as_mut() {
|
||||
material.diffuse = crate::scene::cache::block_cache::fade_toward_bg(
|
||||
material.diffuse,
|
||||
self.current_bg(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Some(set)
|
||||
})
|
||||
.collect();
|
||||
@@ -9390,13 +9443,15 @@ impl Scene {
|
||||
}
|
||||
let arc = if point_cloud::any_definition(&self.document) {
|
||||
let depths = rustc_hash::FxHashMap::default();
|
||||
let visibility_reveal = self.visibility_reveal_handles();
|
||||
let graph = render_graph::RenderSceneGraph::new(
|
||||
&self.document,
|
||||
(!frozen.is_empty()).then_some(frozen),
|
||||
None,
|
||||
true,
|
||||
&depths,
|
||||
);
|
||||
)
|
||||
.with_visibility_reveal(&visibility_reveal);
|
||||
let mut clouds = Vec::new();
|
||||
graph.walk_root(
|
||||
self.render_scene_root(target_block),
|
||||
@@ -9473,7 +9528,7 @@ impl Scene {
|
||||
continue;
|
||||
};
|
||||
let common = entity.common();
|
||||
if common.invisible
|
||||
if self.display_entity_invisible(common)
|
||||
|| self.entity_temporarily_hidden(common.handle)
|
||||
|| self
|
||||
.document
|
||||
@@ -9639,7 +9694,7 @@ impl Scene {
|
||||
let Some(c) = self.document.get_entity(handle).map(|e| e.common()) else {
|
||||
return false;
|
||||
};
|
||||
if c.invisible {
|
||||
if self.display_entity_invisible(c) {
|
||||
return false;
|
||||
}
|
||||
!self.layer_hidden(&c.layer)
|
||||
@@ -9673,6 +9728,7 @@ impl Scene {
|
||||
return Vec::new();
|
||||
}
|
||||
let depth_map = self.draw_depth_map();
|
||||
let visibility_reveal = self.visibility_reveal_handles();
|
||||
let graph = render_graph::RenderSceneGraph::new(
|
||||
&self.document,
|
||||
frozen,
|
||||
@@ -9680,6 +9736,7 @@ impl Scene {
|
||||
all_visible,
|
||||
depth_map.as_ref(),
|
||||
)
|
||||
.with_visibility_reveal(&visibility_reveal)
|
||||
.with_annotation_scale(self.annotation_scale)
|
||||
.with_viewport(viewport);
|
||||
let mut out = Vec::new();
|
||||
@@ -9744,7 +9801,7 @@ impl Scene {
|
||||
self.material_base_dir.as_deref(),
|
||||
);
|
||||
}
|
||||
if self.count_display.is_some() {
|
||||
if self.count_display.is_some() || self.visibility_revealed(context.root_handle) {
|
||||
let own = transformed
|
||||
.instance_color
|
||||
.or(set.display_color())
|
||||
@@ -9973,7 +10030,7 @@ impl Scene {
|
||||
return false;
|
||||
}
|
||||
let common = entity.common();
|
||||
if common.invisible
|
||||
if self.display_entity_invisible(common)
|
||||
|| self.entity_temporarily_hidden(handle)
|
||||
|| self.layer_hidden(&common.layer)
|
||||
|| self.interaction_layer_frozen(&common.layer)
|
||||
@@ -10037,6 +10094,7 @@ impl Scene {
|
||||
.flatten()
|
||||
.copied()
|
||||
.collect();
|
||||
let visibility_reveal = self.visibility_reveal_handles();
|
||||
let graph = render_graph::RenderSceneGraph::new(
|
||||
&self.document,
|
||||
(!frozen.is_empty()).then_some(&frozen),
|
||||
@@ -10044,6 +10102,7 @@ impl Scene {
|
||||
all_visible,
|
||||
depth_map.as_ref(),
|
||||
)
|
||||
.with_visibility_reveal(&visibility_reveal)
|
||||
.with_annotation_scale(self.annotation_scale);
|
||||
for entity in self.document.entities() {
|
||||
let contextual = crate::scene::annotative::entity_for_annotation_context(
|
||||
@@ -10053,7 +10112,7 @@ impl Scene {
|
||||
);
|
||||
let host = contextual.as_ref();
|
||||
let common = host.common();
|
||||
if common.invisible
|
||||
if self.display_entity_invisible(common)
|
||||
|| self.entity_temporarily_hidden(common.handle)
|
||||
|| layer_hidden(&common.layer)
|
||||
|| crate::scene::annotative::annotative_offscale_for(
|
||||
@@ -11638,7 +11697,7 @@ impl Scene {
|
||||
layer: Option<&codec::tables::layer::Layer>,
|
||||
) -> bool {
|
||||
let c = e.common();
|
||||
if c.invisible {
|
||||
if self.display_entity_invisible(c) {
|
||||
return false;
|
||||
}
|
||||
// Session-only Isolate / Hide and interactive replacement previews.
|
||||
|
||||
@@ -280,6 +280,7 @@ pub struct RenderSceneGraph<'a> {
|
||||
all_visible: bool,
|
||||
depths: &'a FxHashMap<u64, [f32; 2]>,
|
||||
viewport: Option<Handle>,
|
||||
visibility_reveal: Option<&'a FxHashSet<Handle>>,
|
||||
}
|
||||
|
||||
impl<'a> RenderSceneGraph<'a> {
|
||||
@@ -298,9 +299,15 @@ impl<'a> RenderSceneGraph<'a> {
|
||||
all_visible,
|
||||
depths,
|
||||
viewport: None,
|
||||
visibility_reveal: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_visibility_reveal(mut self, handles: &'a FxHashSet<Handle>) -> Self {
|
||||
self.visibility_reveal = Some(handles);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_viewport(mut self, viewport: Option<Handle>) -> Self {
|
||||
self.viewport = viewport.filter(|handle| handle.is_valid());
|
||||
self
|
||||
@@ -579,7 +586,11 @@ impl<'a> RenderSceneGraph<'a> {
|
||||
|
||||
fn document_visible(&self, entity: &EntityType) -> bool {
|
||||
let common = entity.common();
|
||||
if common.invisible {
|
||||
if common.invisible
|
||||
&& !self
|
||||
.visibility_reveal
|
||||
.is_some_and(|handles| handles.contains(&common.handle))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
let layer = self.document.layers.get(&common.layer);
|
||||
|
||||
Reference in new issue
Block a user