fix(wipeout): mask block-internal wipeouts once inserted
A wipeout defined inside a block never masked the geometry once the block was inserted — wipeout_models only scanned top-level entities, so the wipeout was invisible unless you entered block-edit (where it is top-level). It also drew a stray mask at its raw block-local coordinates because the top-level scan lacked the block-defn reject the hatch path uses. Descend visible inserts and place each block-internal wipeout in world space, applying the accumulated insert transform via apply / apply_rotation. Add the belongs_to_visible_block filter so block-defn wipeouts only appear through the descent, in world space. Bumps acadrust for the matching transform_wipeout fix: its u/v basis was double-scaled (apply_rotation then another scale-factor multiply), so a scaled insert grew the mask; the descent sidesteps that path but the explode/copy commands need the crate fix. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
d54fb9e2aa
commit
de5c35d44a
2 changed files with 138 additions and 1 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -74,7 +74,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "acadrust"
|
name = "acadrust"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
source = "git+https://github.com/OpenAEC-Foundation/acadifc?branch=main#022290a3a2a548c54c8fdf580ef5e44de36b4520"
|
source = "git+https://github.com/OpenAEC-Foundation/acadifc?branch=main#824270e9c0463b8fb926424c9ac61eda5c09d752"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.8.12",
|
"ahash 0.8.12",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
|
|
||||||
|
|
@ -1101,6 +1101,7 @@ impl Scene {
|
||||||
// pipeline's `wipeout_skip_flags` (compute_wipeout_lod) does
|
// pipeline's `wipeout_skip_flags` (compute_wipeout_lod) does
|
||||||
// the per-frame skip at draw time instead.
|
// the per-frame skip at draw time instead.
|
||||||
let depth_map = self.draw_depth_map();
|
let depth_map = self.draw_depth_map();
|
||||||
|
let layout_block = self.current_layout_block_handle();
|
||||||
let mut models = Vec::new();
|
let mut models = Vec::new();
|
||||||
for entity in self.document.entities() {
|
for entity in self.document.entities() {
|
||||||
let EntityType::Wipeout(wo) = entity else {
|
let EntityType::Wipeout(wo) = entity else {
|
||||||
|
|
@ -1109,6 +1110,17 @@ impl Scene {
|
||||||
if entity.common().invisible {
|
if entity.common().invisible {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// Reject block-defn-only wipeouts (owned by a BLOCK record that is
|
||||||
|
// neither model nor a paper layout block): those are placed via
|
||||||
|
// insert descent below, in world space. Without this they also draw
|
||||||
|
// once at their raw block-local coordinates. Mirrors the hatch path.
|
||||||
|
if !self.belongs_to_visible_block(
|
||||||
|
wo.common.handle,
|
||||||
|
wo.common.owner_handle,
|
||||||
|
layout_block,
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if self
|
if self
|
||||||
.document
|
.document
|
||||||
.layers
|
.layers
|
||||||
|
|
@ -1150,9 +1162,134 @@ impl Scene {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Wipeouts nested inside block inserts: the loop above only sees
|
||||||
|
// top-level wipeouts, so a wipeout defined inside a block never masked
|
||||||
|
// once the block was inserted (it did in block-edit, where it is
|
||||||
|
// top-level). Descend visible inserts and place each block-internal
|
||||||
|
// wipeout in world space. The insert transform is applied here (apply /
|
||||||
|
// apply_rotation) rather than through Insert::explode — the latter
|
||||||
|
// double-scales the u/v basis.
|
||||||
|
for entity in self.document.entities() {
|
||||||
|
let EntityType::Insert(ins) = entity else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let c = &ins.common;
|
||||||
|
if c.invisible
|
||||||
|
|| self
|
||||||
|
.document
|
||||||
|
.layers
|
||||||
|
.get(&c.layer)
|
||||||
|
.map(|l| l.flags.off || l.flags.frozen)
|
||||||
|
.unwrap_or(false)
|
||||||
|
|| self.layer_frozen_in(&c.layer, frozen)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !self.belongs_to_visible_block(c.handle, c.owner_handle, layout_block) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if frozen.is_none()
|
||||||
|
&& crate::scene::annotative::annotative_offscale(&self.document, c)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
self.collect_block_wipeouts(
|
||||||
|
&ins.get_transform(),
|
||||||
|
&ins.block_name,
|
||||||
|
0,
|
||||||
|
frozen,
|
||||||
|
bg_color,
|
||||||
|
&depth_map,
|
||||||
|
&mut models,
|
||||||
|
);
|
||||||
|
}
|
||||||
models
|
models
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Recursively collect wipeout masks defined inside a block, transformed to
|
||||||
|
/// world space by the accumulated insert transform. See `wipeout_models`.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn collect_block_wipeouts(
|
||||||
|
&self,
|
||||||
|
xform: &acadrust::types::Transform,
|
||||||
|
block_name: &str,
|
||||||
|
depth: usize,
|
||||||
|
frozen: Option<&rustc_hash::FxHashSet<Handle>>,
|
||||||
|
bg_color: [f32; 4],
|
||||||
|
depth_map: &HashMap<u64, [f32; 2]>,
|
||||||
|
models: &mut Vec<HatchModel>,
|
||||||
|
) {
|
||||||
|
if depth > 32 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some(br) = self
|
||||||
|
.document
|
||||||
|
.block_records
|
||||||
|
.iter()
|
||||||
|
.find(|b| b.name.eq_ignore_ascii_case(block_name))
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
for &eh in &br.entity_handles {
|
||||||
|
let Some(e) = self.document.get_entity(eh) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let c = e.common();
|
||||||
|
if c.invisible
|
||||||
|
|| self
|
||||||
|
.document
|
||||||
|
.layers
|
||||||
|
.get(&c.layer)
|
||||||
|
.map(|l| l.flags.off || l.flags.frozen)
|
||||||
|
.unwrap_or(false)
|
||||||
|
|| self.layer_frozen_in(&c.layer, frozen)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
match e {
|
||||||
|
EntityType::Wipeout(wo) => {
|
||||||
|
let mut w = wo.clone();
|
||||||
|
w.insertion_point = xform.apply(wo.insertion_point);
|
||||||
|
w.u_vector = xform.apply_rotation(wo.u_vector);
|
||||||
|
w.v_vector = xform.apply_rotation(wo.v_vector);
|
||||||
|
let (fill_origin, boundary) = Self::wipeout_boundary_2d(&w);
|
||||||
|
if boundary.len() >= 3 {
|
||||||
|
let mut fill_color = bg_color;
|
||||||
|
if self.selected.contains(&c.handle) {
|
||||||
|
fill_color = [0.15, 0.55, 1.00, 0.35];
|
||||||
|
}
|
||||||
|
models.push(HatchModel {
|
||||||
|
boundary: Arc::new(boundary),
|
||||||
|
boundary_wcs: None,
|
||||||
|
pattern: model::hatch_model::HatchPattern::Solid,
|
||||||
|
name: "WIPEOUT_FILL".into(),
|
||||||
|
color: fill_color,
|
||||||
|
angle_offset: 0.0,
|
||||||
|
scale: 1.0,
|
||||||
|
world_origin: fill_origin,
|
||||||
|
draw_depth: depth_map.get(&c.handle.value()).map_or(0.0, |d| d[0]),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EntityType::Insert(ni) => {
|
||||||
|
// `compose` applies the nested insert's transform first,
|
||||||
|
// then the accumulated parent transform.
|
||||||
|
let child = xform.compose(&ni.get_transform());
|
||||||
|
self.collect_block_wipeouts(
|
||||||
|
&child,
|
||||||
|
&ni.block_name,
|
||||||
|
depth + 1,
|
||||||
|
frozen,
|
||||||
|
bg_color,
|
||||||
|
depth_map,
|
||||||
|
models,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Compute the 2D (XY) boundary polygon for a Wipeout entity.
|
/// Compute the 2D (XY) boundary polygon for a Wipeout entity.
|
||||||
/// Wipeout fill boundary as small f32 offsets from the returned `world_origin`
|
/// Wipeout fill boundary as small f32 offsets from the returned `world_origin`
|
||||||
/// (the insertion point, kept in f64). Building it in absolute WCS f32
|
/// (the insertion point, kept in f64). Building it in absolute WCS f32
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue