forked from 1b-agent/opencadstudio-upstream
fix(blocks): complete block-backed instance rendering
This commit is contained in:
1 parent
6cc4e0f63d
commit
6606da5029
11 files changed
+554
-253
No files matched your search
+75
-56
@@ -189,16 +189,19 @@ pub(crate) fn insert_attribute_entities(
|
||||
document: &acadrust::CadDocument,
|
||||
insert: &acadrust::entities::Insert,
|
||||
annotation_scale: f32,
|
||||
scale_policy: crate::scene::BlockScalePolicy,
|
||||
) -> Vec<EntityType> {
|
||||
let visibility = document.header.attribute_visibility;
|
||||
if visibility == 0 || insert.attributes.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
let block_scale = if insert
|
||||
.common
|
||||
.extended_data
|
||||
.get_record("AcAnnotativeData")
|
||||
.is_some()
|
||||
let block_scale = if scale_policy
|
||||
== crate::scene::BlockScalePolicy::FromInsert
|
||||
&& insert
|
||||
.common
|
||||
.extended_data
|
||||
.get_record("AcAnnotativeData")
|
||||
.is_some()
|
||||
{
|
||||
annotation_scale as f64
|
||||
} else {
|
||||
@@ -253,59 +256,75 @@ pub(crate) fn append_insert_attribute_wires(
|
||||
pslt_factor: f32,
|
||||
anno_scale: f32,
|
||||
) {
|
||||
for attr_entity in insert_attribute_entities(document, ins, anno_scale) {
|
||||
let attr = attr_entity.common();
|
||||
let (sub_color, sub_plen, sub_pat, sub_lw_px, sub_aci) = render::render_style_for_block_sub(
|
||||
let attributes = insert_attribute_entities(
|
||||
document,
|
||||
ins,
|
||||
anno_scale,
|
||||
crate::scene::BlockScalePolicy::FromInsert,
|
||||
);
|
||||
for offset in crate::scene::render_graph::array_offsets(ins) {
|
||||
let delta = crate::scene::render_graph::insert_instance_translation_delta(
|
||||
document,
|
||||
&attr_entity,
|
||||
ins_color,
|
||||
ins_pat_len,
|
||||
ins_pat,
|
||||
ins_lw_px,
|
||||
ins_layer,
|
||||
ins,
|
||||
offset,
|
||||
anno_scale,
|
||||
crate::scene::BlockScalePolicy::FromInsert,
|
||||
);
|
||||
let sub_color = render::adapt_to_bg(sub_color, bg_color);
|
||||
let sub_color = if is_xref && !sel {
|
||||
block_cache::fade_toward_bg(sub_color, bg_color)
|
||||
} else {
|
||||
sub_color
|
||||
};
|
||||
let attr_plottable = if render::is_effective_layer_zero(&attr.layer) {
|
||||
ins_layer_plottable
|
||||
} else {
|
||||
document
|
||||
.layers
|
||||
.get(&attr.layer)
|
||||
.map(|layer| layer.is_plottable)
|
||||
.unwrap_or(true)
|
||||
};
|
||||
let attr_plottable = ins_layer_plottable && attr_plottable;
|
||||
let sub_aabb = crate::scene::entity_aabb(&attr_entity);
|
||||
let mut attr_wires = tessellate::tessellate(
|
||||
document,
|
||||
insert_handle,
|
||||
&attr_entity,
|
||||
sel,
|
||||
sub_color,
|
||||
sub_plen * pslt_factor,
|
||||
sub_pat.map(|v| v * pslt_factor),
|
||||
sub_lw_px,
|
||||
// Neutral: the attribute is already scaled as part of the block unit
|
||||
// above, so the text path must not scale it a second time.
|
||||
1.0,
|
||||
None,
|
||||
None,
|
||||
bg_color,
|
||||
false,
|
||||
);
|
||||
// Use the INSERT's handle so selection / picking groups attribute
|
||||
// text with the parent insert instead of treating it as a stray text.
|
||||
for w in &mut attr_wires {
|
||||
w.name = insert_handle.value().to_string();
|
||||
w.aci = sub_aci;
|
||||
w.aabb = sub_aabb;
|
||||
w.plot_visible &= attr_plottable;
|
||||
for source in &attributes {
|
||||
let mut attr_entity = source.clone();
|
||||
if delta != Vector3::ZERO {
|
||||
attr_entity.apply_transform(&Transform::from_translation(delta));
|
||||
}
|
||||
let attr = attr_entity.common();
|
||||
let (sub_color, sub_plen, sub_pat, sub_lw_px, sub_aci) =
|
||||
render::render_style_for_block_sub(
|
||||
document,
|
||||
&attr_entity,
|
||||
ins_color,
|
||||
ins_pat_len,
|
||||
ins_pat,
|
||||
ins_lw_px,
|
||||
ins_layer,
|
||||
);
|
||||
let sub_color = render::adapt_to_bg(sub_color, bg_color);
|
||||
let sub_color = if is_xref && !sel {
|
||||
block_cache::fade_toward_bg(sub_color, bg_color)
|
||||
} else {
|
||||
sub_color
|
||||
};
|
||||
let attr_plottable = if render::is_effective_layer_zero(&attr.layer) {
|
||||
ins_layer_plottable
|
||||
} else {
|
||||
document
|
||||
.layers
|
||||
.get(&attr.layer)
|
||||
.map(|layer| layer.is_plottable)
|
||||
.unwrap_or(true)
|
||||
};
|
||||
let attr_plottable = ins_layer_plottable && attr_plottable;
|
||||
let sub_aabb = crate::scene::entity_aabb(&attr_entity);
|
||||
let mut attr_wires = tessellate::tessellate(
|
||||
document,
|
||||
insert_handle,
|
||||
&attr_entity,
|
||||
sel,
|
||||
sub_color,
|
||||
sub_plen * pslt_factor,
|
||||
sub_pat.map(|v| v * pslt_factor),
|
||||
sub_lw_px,
|
||||
1.0,
|
||||
None,
|
||||
None,
|
||||
bg_color,
|
||||
false,
|
||||
);
|
||||
for w in &mut attr_wires {
|
||||
w.name = insert_handle.value().to_string();
|
||||
w.aci = sub_aci;
|
||||
w.aabb = sub_aabb;
|
||||
w.plot_visible &= attr_plottable;
|
||||
}
|
||||
wires.extend(attr_wires);
|
||||
}
|
||||
wires.extend(attr_wires);
|
||||
}
|
||||
}
|
||||
@@ -2129,7 +2129,10 @@ impl MultiLeaderTess for MultiLeader {
|
||||
view_aabb,
|
||||
world_per_pixel,
|
||||
1.0,
|
||||
crate::scene::convert::tess::BlockObjectOptions::default(),
|
||||
crate::scene::convert::tess::BlockObjectOptions {
|
||||
scale_policy: block_use.scale_policy,
|
||||
..crate::scene::convert::tess::BlockObjectOptions::default()
|
||||
},
|
||||
)
|
||||
.wires,
|
||||
);
|
||||
|
||||
Vendored
+118
-116
@@ -140,12 +140,16 @@ pub enum LocalSub {
|
||||
Nested(NestedRef),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct BlockMetrics {
|
||||
pub aabb_local: [f32; 4],
|
||||
pub has_point_marker: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct BlockDefn {
|
||||
pub subs: Vec<LocalSub>,
|
||||
pub has_point_marker: bool,
|
||||
/// Union of direct and nested geometry in definition coordinates.
|
||||
pub aabb_local: [f32; 4],
|
||||
pub metrics: BlockMetrics,
|
||||
/// Raw entity count of the source block record (`entity_handles.len()`).
|
||||
/// Divisor for nested depth composition: a nested insert's children get a
|
||||
/// sub-range of `parent_scale / (child_count + 1)`, shared with the scene
|
||||
@@ -247,8 +251,8 @@ impl BlockCache {
|
||||
// by-name references (`LocalSub::Nested`), never expanded here, so a
|
||||
// block's build never depends on another block's defn. That makes the
|
||||
// builds embarrassingly parallel over the read-only `doc` — no
|
||||
// topological ordering required. `compute_block_aabbs` stays a serial
|
||||
// post-pass (it resolves nested references and is comparatively cheap).
|
||||
// topological ordering required. Metrics are resolved after all
|
||||
// definitions exist.
|
||||
cache.defns = referenced
|
||||
.par_iter()
|
||||
.map(|name| {
|
||||
@@ -267,7 +271,7 @@ impl BlockCache {
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
cache.compute_block_aabbs(&referenced);
|
||||
cache.compute_block_metrics(&referenced);
|
||||
cache
|
||||
}
|
||||
|
||||
@@ -304,106 +308,73 @@ impl BlockCache {
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
cache.compute_block_aabbs(&referenced);
|
||||
cache.compute_block_metrics(&referenced);
|
||||
cache
|
||||
}
|
||||
|
||||
/// Compute and store the `aabb_local` for every cached defn. Direct wires
|
||||
/// contribute their own aabb_local; nested INSERT references look up the
|
||||
/// nested defn (already cached) and transform its aabb_local by the
|
||||
/// nested Insert's transform before unioning.
|
||||
///
|
||||
/// Run as a post-pass so it doesn't matter which order build_defn was
|
||||
/// called in. Cycle guard: a self-referential block keeps an empty AABB
|
||||
/// (will fail every frustum test → not emitted, which is correct).
|
||||
fn compute_block_aabbs(&mut self, names: &[String]) {
|
||||
/// Resolve recursive bounds and point-marker presence.
|
||||
fn compute_block_metrics(&mut self, names: &[String]) {
|
||||
use crate::par::prelude::*;
|
||||
// Phase 1 (parallel, read-only): each defn's union AABB is resolved
|
||||
// by `defn_aabb_recursive`, which only *reads* `self.defns` (the map
|
||||
// is fully built by now). There's no memoization, so a defn shared by
|
||||
// many parents is re-walked per parent — real work on block-heavy
|
||||
// drawings — and the per-name walks are independent, so they fan out.
|
||||
// Definitions are complete, so each recursive read can run independently.
|
||||
let this: &Self = self;
|
||||
let resolved: Vec<(&String, [f32; 4], bool)> = names
|
||||
let resolved: Vec<(&String, BlockMetrics)> = names
|
||||
.par_iter()
|
||||
.map(|name| {
|
||||
let mut visited: Vec<String> = Vec::new();
|
||||
let aabb = this.defn_aabb_recursive(name, &mut visited);
|
||||
visited.clear();
|
||||
let has_marker = this.defn_has_point_marker_recursive(name, &mut visited);
|
||||
(name, aabb, has_marker)
|
||||
(name, this.defn_metrics_recursive(name, &mut visited))
|
||||
})
|
||||
.collect();
|
||||
// Phase 2 (serial): store the AABB back into each defn.
|
||||
for (name, aabb, has_marker) in resolved {
|
||||
for (name, metrics) in resolved {
|
||||
if let Some(defn_arc) = self.defns.get_mut(name) {
|
||||
let mut defn = (**defn_arc).clone();
|
||||
defn.aabb_local = aabb;
|
||||
defn.has_point_marker = has_marker;
|
||||
defn.metrics = metrics;
|
||||
*defn_arc = Arc::new(defn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn defn_has_point_marker_recursive(
|
||||
fn defn_metrics_recursive(
|
||||
&self,
|
||||
block_name: &str,
|
||||
visited: &mut Vec<String>,
|
||||
) -> bool {
|
||||
) -> BlockMetrics {
|
||||
if visited.iter().any(|name| name == block_name) {
|
||||
return false;
|
||||
return BlockMetrics::default();
|
||||
}
|
||||
let Some(defn) = self.defns.get(block_name) else {
|
||||
return false;
|
||||
return BlockMetrics::default();
|
||||
};
|
||||
visited.push(block_name.to_string());
|
||||
let found = defn.subs.iter().any(|sub| match sub {
|
||||
LocalSub::Wire(wire) => wire.point_marker.is_some(),
|
||||
LocalSub::Nested(insert) => {
|
||||
insert
|
||||
.attachments
|
||||
.iter()
|
||||
.any(|wire| wire.point_marker.is_some())
|
||||
|| self.defn_has_point_marker_recursive(&insert.block_name, visited)
|
||||
}
|
||||
});
|
||||
visited.pop();
|
||||
found
|
||||
}
|
||||
|
||||
/// Returns the union AABB for `block_name`'s defn, expressed in **that
|
||||
/// defn's offset frame** (so its caller can store it in
|
||||
/// `BlockDefn.aabb_local` without a coordinate-frame mismatch).
|
||||
///
|
||||
/// LocalWire contributions are already in the parent defn's offset
|
||||
/// frame. Nested-INSERT contributions live in the *child* defn's offset
|
||||
/// frame, so we re-add `child.local_offset` (f64), apply the nested
|
||||
/// Insert's transform to get parent-native coordinates, then subtract
|
||||
/// `parent.local_offset` to land back in the parent's offset frame.
|
||||
fn defn_aabb_recursive(&self, block_name: &str, visited: &mut Vec<String>) -> [f32; 4] {
|
||||
if visited.iter().any(|n| n == block_name) {
|
||||
return [0.0, 0.0, 0.0, 0.0];
|
||||
}
|
||||
let Some(defn) = self.defns.get(block_name) else {
|
||||
return [0.0, 0.0, 0.0, 0.0];
|
||||
};
|
||||
visited.push(block_name.to_string());
|
||||
let mut acc = [0.0_f32, 0.0, 0.0, 0.0];
|
||||
let mut metrics = BlockMetrics::default();
|
||||
for sub in &defn.subs {
|
||||
let aabb = match sub {
|
||||
LocalSub::Wire(lw) => lw.aabb_local,
|
||||
LocalSub::Nested(nref) => {
|
||||
let nested = self.defn_aabb_recursive(&nref.block_name, visited);
|
||||
nref.attachments.iter().fold(
|
||||
transform_aabb_xy(nested, &nref.xform),
|
||||
|aabb, wire| aabb_union(aabb, wire.aabb_local),
|
||||
)
|
||||
match sub {
|
||||
LocalSub::Wire(wire) => {
|
||||
metrics.aabb_local = aabb_union(metrics.aabb_local, wire.aabb_local);
|
||||
metrics.has_point_marker |= wire.point_marker.is_some();
|
||||
}
|
||||
};
|
||||
acc = aabb_union(acc, aabb);
|
||||
LocalSub::Nested(nref) => {
|
||||
let nested = self.defn_metrics_recursive(&nref.block_name, visited);
|
||||
metrics.has_point_marker |= nested.has_point_marker;
|
||||
for offset in &nref.instance_offsets {
|
||||
let transform = nested_instance_transform(nref, *offset);
|
||||
metrics.aabb_local = aabb_union(
|
||||
metrics.aabb_local,
|
||||
transform_aabb_xy(nested.aabb_local, &transform),
|
||||
);
|
||||
let attachment_transform = nested_attachment_transform(nref, *offset);
|
||||
for wire in &nref.attachments {
|
||||
metrics.aabb_local = aabb_union(
|
||||
metrics.aabb_local,
|
||||
transform_aabb_xy(wire.aabb_local, &attachment_transform),
|
||||
);
|
||||
metrics.has_point_marker |= wire.point_marker.is_some();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
visited.pop();
|
||||
acc
|
||||
metrics
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,6 +450,21 @@ fn layer_hidden(doc: &CadDocument, layer: &str) -> bool {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn nested_instance_transform(nref: &NestedRef, offset: [f64; 3]) -> Transform {
|
||||
if offset == [0.0; 3] {
|
||||
nref.xform.clone()
|
||||
} else {
|
||||
Transform::from_translation(Vector3::new(offset[0], offset[1], offset[2]))
|
||||
.then(&nref.xform)
|
||||
}
|
||||
}
|
||||
|
||||
fn nested_attachment_transform(nref: &NestedRef, offset: [f64; 3]) -> Transform {
|
||||
let base = nref.xform.apply(Vector3::ZERO);
|
||||
let instance = nested_instance_transform(nref, offset).apply(Vector3::ZERO);
|
||||
Transform::from_translation(instance - base)
|
||||
}
|
||||
|
||||
fn build_defn(
|
||||
doc: &CadDocument,
|
||||
block_name: &str,
|
||||
@@ -549,6 +535,7 @@ fn build_defn(
|
||||
for block_use in &block_uses {
|
||||
let mut nested = build_nested_ref(
|
||||
&block_use.insert,
|
||||
block_use.scale_policy,
|
||||
doc,
|
||||
anno_scale,
|
||||
bg_color,
|
||||
@@ -597,14 +584,14 @@ fn build_defn(
|
||||
}
|
||||
BlockDefn {
|
||||
subs,
|
||||
has_point_marker: false,
|
||||
aabb_local: [0.0; 4],
|
||||
metrics: BlockMetrics::default(),
|
||||
child_count: br.entity_handles.len(),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_nested_ref(
|
||||
nested_ins: &acadrust::entities::Insert,
|
||||
scale_policy: crate::scene::BlockScalePolicy,
|
||||
doc: &CadDocument,
|
||||
anno_scale: f32,
|
||||
bg_color: [f32; 4],
|
||||
@@ -614,10 +601,11 @@ fn build_nested_ref(
|
||||
// Bake the XCLIP boundary (parent-defn-local) so the nested insert keeps
|
||||
// its clip when the parent block is expanded — the spatial filter object
|
||||
// isn't reachable at expand time.
|
||||
let xform = crate::scene::render_graph::insert_transform_at_scale(
|
||||
let xform = crate::scene::render_graph::insert_transform_with_policy(
|
||||
doc,
|
||||
nested_ins,
|
||||
anno_scale,
|
||||
scale_policy,
|
||||
);
|
||||
let clip_poly = crate::scene::pick::xclip::insert_spatial_filter(doc, nested_ins)
|
||||
.map(|filter| {
|
||||
@@ -635,6 +623,7 @@ fn build_nested_ref(
|
||||
doc,
|
||||
nested_ins,
|
||||
anno_scale,
|
||||
scale_policy,
|
||||
)
|
||||
.into_iter()
|
||||
.flat_map(|attribute| {
|
||||
@@ -977,12 +966,18 @@ pub fn expand_insert(
|
||||
// Current annotation scale. An annotative block scales as one uniform unit
|
||||
// about its insertion point; a non-annotative block is unaffected.
|
||||
anno_scale: f32,
|
||||
scale_policy: crate::scene::BlockScalePolicy,
|
||||
// Dimension picture blocks store definition POINTs at their root. They are
|
||||
// metadata, not visible geometry; nested POINTs remain regular block data.
|
||||
suppress_root_points: bool,
|
||||
) -> Option<Vec<WireModel>> {
|
||||
let defn = cache.defn(&ins.block_name)?;
|
||||
let xform = crate::scene::render_graph::insert_transform_at_scale(doc, ins, anno_scale);
|
||||
let xform = crate::scene::render_graph::insert_transform_with_policy(
|
||||
doc,
|
||||
ins,
|
||||
anno_scale,
|
||||
scale_policy,
|
||||
);
|
||||
let name = ins_handle.value().to_string();
|
||||
let prototype_key = if !ins.is_array()
|
||||
&& cache.prototype_blocks.contains(&ins.block_name)
|
||||
@@ -1042,10 +1037,22 @@ pub fn expand_insert(
|
||||
let mut batches = Batches::default();
|
||||
let mut visited: Vec<String> = Vec::with_capacity(8);
|
||||
|
||||
// `defn.aabb_local` is in the defn's offset frame — re-add
|
||||
// `defn.local_offset` (f64) before transforming so the world AABB is
|
||||
// accurate for distant content.
|
||||
let insert_world = transform_aabb_xy(defn.aabb_local, &xform);
|
||||
let offsets = crate::scene::render_graph::array_offsets(ins);
|
||||
let cell_xform = |offset: &[f64; 3]| {
|
||||
crate::scene::render_graph::insert_instance_transform(
|
||||
doc,
|
||||
ins,
|
||||
*offset,
|
||||
anno_scale,
|
||||
scale_policy,
|
||||
)
|
||||
};
|
||||
let insert_world = offsets.iter().fold([0.0_f32; 4], |aabb, offset| {
|
||||
aabb_union(
|
||||
aabb,
|
||||
transform_aabb_xy(defn.metrics.aabb_local, &cell_xform(offset)),
|
||||
)
|
||||
});
|
||||
let insert_local = [
|
||||
insert_world[0] as f32,
|
||||
insert_world[1] as f32,
|
||||
@@ -1062,7 +1069,7 @@ pub fn expand_insert(
|
||||
// Whole-Insert pixel-size LOD: if the entire Insert footprint projects
|
||||
// to sub-pixel size, skip it entirely.
|
||||
if let Some(wpp) = world_per_pixel {
|
||||
if !defn.has_point_marker && aabb_pixel_size(insert_local, wpp) < MIN_PIXEL_SIZE {
|
||||
if !defn.metrics.has_point_marker && aabb_pixel_size(insert_local, wpp) < MIN_PIXEL_SIZE {
|
||||
return Some(vec![]);
|
||||
}
|
||||
}
|
||||
@@ -1085,16 +1092,7 @@ pub fn expand_insert(
|
||||
is_xref,
|
||||
bg_color,
|
||||
};
|
||||
let offsets = crate::scene::render_graph::array_offsets(ins);
|
||||
if offsets.len() > 1 {
|
||||
let cell_xform = |offset: &[f64; 3]| {
|
||||
if offset == &[0.0; 3] {
|
||||
xform.clone()
|
||||
} else {
|
||||
Transform::from_translation(Vector3::new(offset[0], offset[1], offset[2]))
|
||||
.then(&xform)
|
||||
}
|
||||
};
|
||||
let first_xform = cell_xform(&offsets[0]);
|
||||
let first_translation = transform_translation(&first_xform);
|
||||
let mut first_batches = Batches::default();
|
||||
@@ -1135,14 +1133,7 @@ pub fn expand_insert(
|
||||
}
|
||||
|
||||
for offset in &offsets {
|
||||
let base_xform = if offset == &[0.0; 3] {
|
||||
xform.clone()
|
||||
} else {
|
||||
let translation = Transform::from_translation(Vector3::new(
|
||||
offset[0], offset[1], offset[2],
|
||||
));
|
||||
translation.then(&xform)
|
||||
};
|
||||
let base_xform = cell_xform(offset);
|
||||
expand_defn(
|
||||
defn,
|
||||
&base_xform,
|
||||
@@ -1816,8 +1807,18 @@ fn expand_defn(
|
||||
is_xref: ctx.is_xref,
|
||||
bg_color: ctx.bg_color,
|
||||
};
|
||||
for attachment in &nref.attachments {
|
||||
emit_wire(attachment, accum_xform, &inner_ctx, out, d_range);
|
||||
for offset in &nref.instance_offsets {
|
||||
let attachment_transform = nested_attachment_transform(nref, *offset)
|
||||
.then(accum_xform);
|
||||
for attachment in &nref.attachments {
|
||||
emit_wire(
|
||||
attachment,
|
||||
&attachment_transform,
|
||||
&inner_ctx,
|
||||
out,
|
||||
d_range,
|
||||
);
|
||||
}
|
||||
}
|
||||
if visited.iter().any(|n| n == &nref.block_name) {
|
||||
continue;
|
||||
@@ -1825,8 +1826,17 @@ fn expand_defn(
|
||||
let Some(nested_defn) = ctx.cache.defn(&nref.block_name) else {
|
||||
continue;
|
||||
};
|
||||
let composed = nref.xform.then(accum_xform);
|
||||
let world = transform_aabb_xy(nested_defn.aabb_local, &composed);
|
||||
let world = nref.instance_offsets.iter().fold(
|
||||
[0.0_f32; 4],
|
||||
|aabb, offset| {
|
||||
let composed = nested_instance_transform(nref, *offset)
|
||||
.then(accum_xform);
|
||||
aabb_union(
|
||||
aabb,
|
||||
transform_aabb_xy(nested_defn.metrics.aabb_local, &composed),
|
||||
)
|
||||
},
|
||||
);
|
||||
let local = [
|
||||
world[0] as f32,
|
||||
world[1] as f32,
|
||||
@@ -1851,15 +1861,7 @@ fn expand_defn(
|
||||
d_range.1 / (nested_defn.child_count.max(1) as f32 + 1.0),
|
||||
);
|
||||
let composed_for = |offset: &[f64; 3]| {
|
||||
if offset == &[0.0; 3] {
|
||||
nref.xform.then(accum_xform)
|
||||
} else {
|
||||
Transform::from_translation(Vector3::new(
|
||||
offset[0], offset[1], offset[2],
|
||||
))
|
||||
.then(&nref.xform)
|
||||
.then(accum_xform)
|
||||
}
|
||||
nested_instance_transform(nref, *offset).then(accum_xform)
|
||||
};
|
||||
if let Some(cp) = &nref.clip_poly {
|
||||
let base_composed = nref.xform.then(accum_xform);
|
||||
|
||||
@@ -107,10 +107,11 @@ fn block_entity_transform(
|
||||
visited,
|
||||
annotation_scale,
|
||||
)?;
|
||||
Some(inner.then(&crate::scene::render_graph::insert_transform_at_scale(
|
||||
Some(inner.then(&crate::scene::render_graph::insert_transform_with_policy(
|
||||
document,
|
||||
&block_use.insert,
|
||||
annotation_scale,
|
||||
block_use.scale_policy,
|
||||
)))
|
||||
})
|
||||
});
|
||||
|
||||
@@ -144,8 +144,8 @@ pub fn symbol_radius(doc: &CadDocument, block: Handle, scale: f64) -> f64 {
|
||||
) else {
|
||||
return 0.0;
|
||||
};
|
||||
graph.walk_insert(
|
||||
&block_use.insert,
|
||||
graph.walk_block_use(
|
||||
&block_use,
|
||||
block,
|
||||
|_, _| true,
|
||||
|entity, context| {
|
||||
@@ -322,16 +322,15 @@ pub fn place_block_wires(
|
||||
) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let insert = &mut block_use.insert;
|
||||
insert.set_x_scale(s);
|
||||
insert.set_y_scale(s);
|
||||
insert.set_z_scale(s);
|
||||
block_use.insert.set_x_scale(s);
|
||||
block_use.insert.set_y_scale(s);
|
||||
block_use.insert.set_z_scale(s);
|
||||
let depths = rustc_hash::FxHashMap::default();
|
||||
let graph = crate::scene::render_graph::RenderSceneGraph::new(doc, None, None, true, &depths)
|
||||
.with_annotation_scale(anno_scale);
|
||||
let mut out = Vec::new();
|
||||
graph.walk_insert(
|
||||
insert,
|
||||
graph.walk_block_use(
|
||||
&block_use,
|
||||
block,
|
||||
|_, _| true,
|
||||
|entity, context| {
|
||||
|
||||
+31
-16
@@ -39,6 +39,7 @@ pub(crate) struct ExpandedBlockObject {
|
||||
pub(crate) struct BlockObjectOptions {
|
||||
pub(crate) suppress_root_points: bool,
|
||||
pub(crate) apply_xclip: bool,
|
||||
pub(crate) scale_policy: crate::scene::BlockScalePolicy,
|
||||
}
|
||||
|
||||
fn block_object_style(
|
||||
@@ -141,16 +142,18 @@ pub(crate) fn expand_block_object(
|
||||
is_xref,
|
||||
bg_color,
|
||||
anno_scale,
|
||||
options.scale_policy,
|
||||
options.suppress_root_points,
|
||||
)
|
||||
.unwrap_or_default();
|
||||
|
||||
if options.apply_xclip {
|
||||
if let Some(filter) = pick::xclip::insert_spatial_filter(document, insert) {
|
||||
let transform = crate::scene::render_graph::insert_transform_at_scale(
|
||||
let transform = crate::scene::render_graph::insert_transform_with_policy(
|
||||
document,
|
||||
insert,
|
||||
anno_scale,
|
||||
options.scale_policy,
|
||||
);
|
||||
let polygon = pick::xclip::world_clip_polygon_for_transform(filter, &transform);
|
||||
pick::xclip::clip_wires(&mut wires, &polygon);
|
||||
@@ -568,7 +571,7 @@ fn tessellate_entity_inner(
|
||||
// True only when tessellating content shown inside a paper-space viewport.
|
||||
// Retained through recursive INSERT expansion; PSLTSCALE itself is applied
|
||||
// by the viewport's GPU uniform so it never changes resident wire content.
|
||||
paper_space: bool,
|
||||
_paper_space: bool,
|
||||
) -> Vec<WireModel> {
|
||||
let contextual = crate::scene::annotative::entity_for_annotation_context(
|
||||
document,
|
||||
@@ -980,6 +983,7 @@ fn tessellate_entity_inner(
|
||||
pslt_factor,
|
||||
BlockObjectOptions {
|
||||
suppress_root_points: block_use.suppress_root_points,
|
||||
scale_policy: block_use.scale_policy,
|
||||
..BlockObjectOptions::default()
|
||||
},
|
||||
)
|
||||
@@ -1087,7 +1091,10 @@ fn tessellate_entity_inner(
|
||||
view_aabb,
|
||||
world_per_pixel,
|
||||
pslt_factor,
|
||||
BlockObjectOptions::default(),
|
||||
BlockObjectOptions {
|
||||
scale_policy: block_use.scale_policy,
|
||||
..BlockObjectOptions::default()
|
||||
},
|
||||
)
|
||||
.wires;
|
||||
if !wires.is_empty() {
|
||||
@@ -1119,19 +1126,27 @@ fn tessellate_entity_inner(
|
||||
.filter(|block_use| {
|
||||
block_use.role == crate::scene::render_graph::BlockRole::TableCell
|
||||
}) {
|
||||
wires.extend(tessellate_entity(
|
||||
document,
|
||||
selected,
|
||||
active_viewport,
|
||||
bg_color,
|
||||
1.0,
|
||||
None,
|
||||
&EntityType::Insert(block_use.insert),
|
||||
block_cache,
|
||||
view_aabb,
|
||||
world_per_pixel,
|
||||
paper_space,
|
||||
));
|
||||
wires.extend(
|
||||
expand_block_object(
|
||||
document,
|
||||
&block_use.insert,
|
||||
h,
|
||||
sel,
|
||||
active_viewport,
|
||||
bg_color,
|
||||
table_anno,
|
||||
None,
|
||||
block_cache,
|
||||
view_aabb,
|
||||
world_per_pixel,
|
||||
pslt_factor,
|
||||
BlockObjectOptions {
|
||||
scale_policy: block_use.scale_policy,
|
||||
..BlockObjectOptions::default()
|
||||
},
|
||||
)
|
||||
.wires,
|
||||
);
|
||||
}
|
||||
if !wires.is_empty() {
|
||||
let aabb = entity_aabb(e);
|
||||
|
||||
@@ -2100,8 +2100,8 @@ fn custom_arrow_from_block(
|
||||
let mut lines = Vec::new();
|
||||
let mut fill = Vec::new();
|
||||
let mut deferred_hatch = false;
|
||||
graph.walk_insert(
|
||||
&block_use.insert,
|
||||
graph.walk_block_use(
|
||||
&block_use,
|
||||
record.handle,
|
||||
|_, _| true,
|
||||
|entity, context| {
|
||||
|
||||
+1
-1
@@ -1183,7 +1183,7 @@ impl Scene {
|
||||
fn layer_plottable_in_context(
|
||||
&self,
|
||||
entity: &EntityType,
|
||||
context: &crate::scene::render_graph::RenderContext,
|
||||
context: &crate::scene::render_graph::InstanceContext,
|
||||
) -> bool {
|
||||
let common = entity.common();
|
||||
let layer = if crate::scene::view::render::is_effective_layer_zero(&common.layer) {
|
||||
|
||||
+146
-16
@@ -17,6 +17,13 @@ pub(crate) mod render_graph;
|
||||
pub mod text;
|
||||
pub mod view;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)]
|
||||
pub enum BlockScalePolicy {
|
||||
#[default]
|
||||
FromInsert,
|
||||
Applied,
|
||||
}
|
||||
|
||||
// Topic submodules split out of this root (each contributes `impl Scene`
|
||||
// blocks and/or free functions). Pure text-move from the original mod.rs.
|
||||
mod boundary;
|
||||
@@ -66,6 +73,13 @@ struct DependencyTargets {
|
||||
touches_block_definition: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum DependencyKind {
|
||||
Layer,
|
||||
TextStyle,
|
||||
DimStyle,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct SceneDependencyIndex {
|
||||
layers: HashMap<String, DependencyTargets>,
|
||||
@@ -75,6 +89,7 @@ struct SceneDependencyIndex {
|
||||
points: DependencyTargets,
|
||||
text_geometry: DependencyTargets,
|
||||
annotation_geometry: DependencyTargets,
|
||||
signatures: HashMap<Handle, u64>,
|
||||
}
|
||||
|
||||
fn hatch_interaction_aabb(hatch: &model::hatch_model::HatchModel) -> Option<[f64; 4]> {
|
||||
@@ -164,6 +179,7 @@ use glam;
|
||||
use iced::time::Duration;
|
||||
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
|
||||
use std::cell::RefCell;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
@@ -2429,11 +2445,7 @@ impl Scene {
|
||||
}
|
||||
}
|
||||
if !changes.is_empty() {
|
||||
// A Modified delta can change layer/style/block references as well
|
||||
// as coordinates. Rebuild the reverse dependency map lazily on its
|
||||
// next use so later targeted global-property updates never follow
|
||||
// stale ownership.
|
||||
self.invalidate_dependency_index();
|
||||
self.refresh_dependency_index_for_changes(&changes);
|
||||
}
|
||||
let cached_light_changed = self.lighting_cache.borrow().values().any(|lights| {
|
||||
changes
|
||||
@@ -2533,6 +2545,7 @@ impl Scene {
|
||||
// Structural change — drop both per-entity tessellation memos.
|
||||
self.tess_memo.borrow_mut().clear();
|
||||
self.resident_tess_memo.borrow_mut().clear();
|
||||
self.invalidate_dependency_index();
|
||||
// Can't name the changed handles → force every journal consumer to rebuild.
|
||||
self.push_geometry_delta(epoch, Vec::new(), true);
|
||||
}
|
||||
@@ -2616,6 +2629,7 @@ impl Scene {
|
||||
let epoch = GEOMETRY_EPOCH.fetch_add(1, Ordering::Relaxed);
|
||||
self.geometry_epoch = epoch;
|
||||
self.lighting_cache.borrow_mut().clear();
|
||||
self.invalidate_dependency_index();
|
||||
self.push_geometry_delta(epoch, Vec::new(), true);
|
||||
}
|
||||
|
||||
@@ -7132,8 +7146,8 @@ impl Scene {
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
graph.walk_insert(
|
||||
&block_use.insert,
|
||||
graph.walk_block_use(
|
||||
&block_use,
|
||||
common.handle,
|
||||
|_, _| true,
|
||||
|entity, context| {
|
||||
@@ -7387,7 +7401,10 @@ impl Scene {
|
||||
let changed_live: Vec<Handle> = changes
|
||||
.iter()
|
||||
.filter_map(|(handle, kind)| {
|
||||
(!matches!(kind, ChangeKind::Removed)).then_some(*handle)
|
||||
(!matches!(kind, ChangeKind::Removed)
|
||||
&& !self.entity_temporarily_hidden(*handle)
|
||||
&& self.document.get_entity(*handle).is_some())
|
||||
.then_some(*handle)
|
||||
})
|
||||
.collect();
|
||||
let wires = Arc::new(self.wire_models_for(&changed_live));
|
||||
@@ -8847,6 +8864,113 @@ impl Scene {
|
||||
})
|
||||
}
|
||||
|
||||
fn entity_dependency_signature(&self, entity: &EntityType) -> u64 {
|
||||
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
||||
let common = entity.common();
|
||||
common.handle.hash(&mut hasher);
|
||||
common.owner_handle.hash(&mut hasher);
|
||||
normalize_name(&common.layer).hash(&mut hasher);
|
||||
|
||||
let category = match entity {
|
||||
EntityType::Point(_) => 1_u8,
|
||||
EntityType::Text(_)
|
||||
| EntityType::MText(_)
|
||||
| EntityType::AttributeDefinition(_)
|
||||
| EntityType::AttributeEntity(_)
|
||||
| EntityType::Dimension(_)
|
||||
| EntityType::Leader(_)
|
||||
| EntityType::Tolerance(_)
|
||||
| EntityType::MultiLeader(_)
|
||||
| EntityType::Table(_)
|
||||
| EntityType::Shape(_) => 2,
|
||||
EntityType::Hatch(_) => 3,
|
||||
EntityType::Insert(insert) if !insert.attributes.is_empty() => 4,
|
||||
_ => 0,
|
||||
};
|
||||
category.hash(&mut hasher);
|
||||
crate::scene::annotative::is_annotative(&self.document, entity).hash(&mut hasher);
|
||||
|
||||
for block_use in render_graph::entity_block_uses(&self.document, entity, 1.0) {
|
||||
block_use.block.hash(&mut hasher);
|
||||
normalize_name(&block_use.insert.block_name).hash(&mut hasher);
|
||||
block_use.role.hash(&mut hasher);
|
||||
block_use.scale_policy.hash(&mut hasher);
|
||||
block_use.active.hash(&mut hasher);
|
||||
}
|
||||
|
||||
match entity {
|
||||
EntityType::Text(text) => normalize_name(&text.style).hash(&mut hasher),
|
||||
EntityType::MText(text) => normalize_name(&text.style).hash(&mut hasher),
|
||||
EntityType::AttributeDefinition(attribute) => {
|
||||
normalize_name(&attribute.text_style).hash(&mut hasher)
|
||||
}
|
||||
EntityType::AttributeEntity(attribute) => {
|
||||
normalize_name(&attribute.text_style).hash(&mut hasher)
|
||||
}
|
||||
EntityType::Insert(insert) => {
|
||||
for attribute in &insert.attributes {
|
||||
normalize_name(&attribute.common.layer).hash(&mut hasher);
|
||||
normalize_name(&attribute.text_style).hash(&mut hasher);
|
||||
}
|
||||
}
|
||||
EntityType::Dimension(dimension) => {
|
||||
normalize_name(&dimension.base().style_name).hash(&mut hasher)
|
||||
}
|
||||
EntityType::Leader(leader) => {
|
||||
normalize_name(&leader.dimension_style).hash(&mut hasher)
|
||||
}
|
||||
EntityType::Tolerance(tolerance) => {
|
||||
normalize_name(&tolerance.dimension_style_name).hash(&mut hasher)
|
||||
}
|
||||
EntityType::Table(table) => {
|
||||
table.table_style_handle.hash(&mut hasher);
|
||||
for row in &table.rows {
|
||||
row.style
|
||||
.as_ref()
|
||||
.and_then(|style| style.text_style_handle)
|
||||
.hash(&mut hasher);
|
||||
for cell in &row.cells {
|
||||
cell.style
|
||||
.as_ref()
|
||||
.and_then(|style| style.text_style_handle)
|
||||
.hash(&mut hasher);
|
||||
for content in &cell.contents {
|
||||
content.text_style_handle.hash(&mut hasher);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EntityType::MultiLeader(leader) => {
|
||||
leader.style_handle.hash(&mut hasher);
|
||||
leader.text_style_handle.hash(&mut hasher);
|
||||
leader.context.text_style_handle.hash(&mut hasher);
|
||||
}
|
||||
EntityType::MLine(line) => line.style_handle.hash(&mut hasher),
|
||||
_ => {}
|
||||
}
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
fn refresh_dependency_index_for_changes(&self, changes: &[(Handle, ChangeKind)]) {
|
||||
let mut cache = self.dependency_index_cache.borrow_mut();
|
||||
let Some(index) = cache.as_ref() else {
|
||||
return;
|
||||
};
|
||||
let unchanged = changes.iter().all(|(handle, kind)| {
|
||||
if !matches!(kind, ChangeKind::Modified) {
|
||||
return false;
|
||||
}
|
||||
let Some(entity) = self.document.get_entity(*handle) else {
|
||||
return false;
|
||||
};
|
||||
index.signatures.get(handle).copied()
|
||||
== Some(self.entity_dependency_signature(entity))
|
||||
});
|
||||
if !unchanged {
|
||||
cache.take();
|
||||
}
|
||||
}
|
||||
|
||||
fn rebuild_dependency_index(&self) -> SceneDependencyIndex {
|
||||
let layout_blocks: HashSet<Handle> = self
|
||||
.document
|
||||
@@ -8928,6 +9052,9 @@ impl Scene {
|
||||
let mut index = SceneDependencyIndex::default();
|
||||
for entity in self.document.entities() {
|
||||
let common = entity.common();
|
||||
index
|
||||
.signatures
|
||||
.insert(common.handle, self.entity_dependency_signature(entity));
|
||||
let owner = if common.owner_handle.is_null() {
|
||||
membership
|
||||
.get(&common.handle)
|
||||
@@ -9102,17 +9229,20 @@ impl Scene {
|
||||
index
|
||||
}
|
||||
|
||||
fn dependency_targets(&self, kind: &str, names: &[String]) -> DependencyTargets {
|
||||
fn dependency_targets(
|
||||
&self,
|
||||
kind: DependencyKind,
|
||||
names: &[String],
|
||||
) -> DependencyTargets {
|
||||
if self.dependency_index_cache.borrow().is_none() {
|
||||
*self.dependency_index_cache.borrow_mut() = Some(self.rebuild_dependency_index());
|
||||
}
|
||||
let cache = self.dependency_index_cache.borrow();
|
||||
let index = cache.as_ref().unwrap();
|
||||
let map = match kind {
|
||||
"layer" => &index.layers,
|
||||
"text" => &index.text_styles,
|
||||
"dim" => &index.dim_styles,
|
||||
_ => unreachable!(),
|
||||
DependencyKind::Layer => &index.layers,
|
||||
DependencyKind::TextStyle => &index.text_styles,
|
||||
DependencyKind::DimStyle => &index.dim_styles,
|
||||
};
|
||||
let mut combined = DependencyTargets::default();
|
||||
for name in names {
|
||||
@@ -9148,7 +9278,7 @@ impl Scene {
|
||||
}
|
||||
|
||||
pub fn invalidate_layer_dependencies(&mut self, names: &[String]) {
|
||||
let targets = self.dependency_targets("layer", names);
|
||||
let targets = self.dependency_targets(DependencyKind::Layer, names);
|
||||
self.invalidate_dependency_targets(targets);
|
||||
}
|
||||
|
||||
@@ -9157,7 +9287,7 @@ impl Scene {
|
||||
}
|
||||
|
||||
pub fn invalidate_text_style_dependencies_many(&mut self, names: &[String]) {
|
||||
let targets = self.dependency_targets("text", names);
|
||||
let targets = self.dependency_targets(DependencyKind::TextStyle, names);
|
||||
self.invalidate_dependency_targets(targets);
|
||||
}
|
||||
|
||||
@@ -9166,7 +9296,7 @@ impl Scene {
|
||||
}
|
||||
|
||||
pub fn invalidate_dim_style_dependencies_many(&mut self, names: &[String]) {
|
||||
let targets = self.dependency_targets("dim", names);
|
||||
let targets = self.dependency_targets(DependencyKind::DimStyle, names);
|
||||
self.invalidate_dependency_targets(targets);
|
||||
}
|
||||
|
||||
|
||||
+165
-36
@@ -15,6 +15,7 @@ use crate::scene::view::render::{
|
||||
has_resolved_book_color, is_effective_layer_zero, layer_render_style_viewport,
|
||||
render_style_for_block_sub_viewport, render_style_for_viewport, InheritStyle,
|
||||
};
|
||||
use crate::scene::BlockScalePolicy;
|
||||
|
||||
pub type ResolvedStyle = ([f32; 4], f32, [f32; 8], f32, u8);
|
||||
|
||||
@@ -216,7 +217,7 @@ fn layer_aci(document: &CadDocument, layer: &str) -> u8 {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RenderContext {
|
||||
pub struct InstanceContext {
|
||||
pub transform: Transform,
|
||||
pub root_handle: Handle,
|
||||
pub parent_insert: Handle,
|
||||
@@ -227,9 +228,10 @@ pub struct RenderContext {
|
||||
pub depth_scale: f32,
|
||||
pub nesting_depth: usize,
|
||||
pub viewport: Option<Handle>,
|
||||
pub scale_policy: BlockScalePolicy,
|
||||
}
|
||||
|
||||
impl RenderContext {
|
||||
impl InstanceContext {
|
||||
fn direct(depth_base: f32, viewport: Option<Handle>) -> Self {
|
||||
Self {
|
||||
transform: Transform::identity(),
|
||||
@@ -242,6 +244,7 @@ impl RenderContext {
|
||||
depth_scale: 1.0,
|
||||
nesting_depth: 0,
|
||||
viewport,
|
||||
scale_policy: BlockScalePolicy::FromInsert,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,8 +317,8 @@ impl<'a> RenderSceneGraph<'a> {
|
||||
/// false for an Insert removes its whole subtree.
|
||||
pub fn walk_root<V, F>(&self, root: SceneRoot, mut visible: V, mut leaf: F)
|
||||
where
|
||||
V: FnMut(&EntityType, &RenderContext) -> bool,
|
||||
F: FnMut(&EntityType, &RenderContext),
|
||||
V: FnMut(&EntityType, &InstanceContext) -> bool,
|
||||
F: FnMut(&EntityType, &InstanceContext),
|
||||
{
|
||||
let block = root.content_block();
|
||||
let viewport = match root {
|
||||
@@ -344,12 +347,18 @@ impl<'a> RenderSceneGraph<'a> {
|
||||
.depths
|
||||
.get(&handle.value())
|
||||
.map_or(0.0, |depth| depth[0]);
|
||||
let context = RenderContext::direct(direct_depth, viewport);
|
||||
let context = InstanceContext::direct(direct_depth, viewport);
|
||||
if !self.document_visible(entity) || !visible(entity, &context) {
|
||||
continue;
|
||||
}
|
||||
if let EntityType::Insert(insert) = entity {
|
||||
self.walk_insert_instances(insert, &context, &mut visible, &mut leaf);
|
||||
self.walk_insert_instances(
|
||||
insert,
|
||||
BlockScalePolicy::FromInsert,
|
||||
&context,
|
||||
&mut visible,
|
||||
&mut leaf,
|
||||
);
|
||||
} else {
|
||||
leaf(entity, &context);
|
||||
self.walk_owned_content(entity, &context, &mut visible, &mut leaf, &mut Vec::new());
|
||||
@@ -359,15 +368,36 @@ impl<'a> RenderSceneGraph<'a> {
|
||||
|
||||
/// Walk one synthetic or document-owned Insert. Used by entity renderers
|
||||
/// whose content is itself a block reference.
|
||||
pub fn walk_insert<V, F>(
|
||||
pub fn walk_block_use<V, F>(
|
||||
&self,
|
||||
insert: &Insert,
|
||||
block_use: &BlockUse,
|
||||
root_handle: Handle,
|
||||
mut visible: V,
|
||||
mut leaf: F,
|
||||
)
|
||||
where
|
||||
V: FnMut(&EntityType, &InstanceContext) -> bool,
|
||||
F: FnMut(&EntityType, &InstanceContext),
|
||||
{
|
||||
self.walk_insert_with_policy(
|
||||
&block_use.insert,
|
||||
root_handle,
|
||||
block_use.scale_policy,
|
||||
&mut visible,
|
||||
&mut leaf,
|
||||
);
|
||||
}
|
||||
|
||||
fn walk_insert_with_policy<V, F>(
|
||||
&self,
|
||||
insert: &Insert,
|
||||
root_handle: Handle,
|
||||
scale_policy: BlockScalePolicy,
|
||||
visible: &mut V,
|
||||
leaf: &mut F,
|
||||
) where
|
||||
V: FnMut(&EntityType, &RenderContext) -> bool,
|
||||
F: FnMut(&EntityType, &RenderContext),
|
||||
V: FnMut(&EntityType, &InstanceContext) -> bool,
|
||||
F: FnMut(&EntityType, &InstanceContext),
|
||||
{
|
||||
let mut root_insert = insert.clone();
|
||||
root_insert.common.handle = root_handle;
|
||||
@@ -376,9 +406,15 @@ impl<'a> RenderSceneGraph<'a> {
|
||||
.depths
|
||||
.get(&root_handle.value())
|
||||
.map_or(0.0, |depth| depth[0]);
|
||||
let context = RenderContext::direct(depth_base, self.viewport);
|
||||
let context = InstanceContext::direct(depth_base, self.viewport);
|
||||
if self.document_visible(&entity) && visible(&entity, &context) {
|
||||
self.walk_insert_instances(&root_insert, &context, &mut visible, &mut leaf);
|
||||
self.walk_insert_instances(
|
||||
&root_insert,
|
||||
scale_policy,
|
||||
&context,
|
||||
visible,
|
||||
leaf,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,12 +422,13 @@ impl<'a> RenderSceneGraph<'a> {
|
||||
fn walk_insert_instances<V, F>(
|
||||
&self,
|
||||
insert: &Insert,
|
||||
parent: &RenderContext,
|
||||
scale_policy: BlockScalePolicy,
|
||||
parent: &InstanceContext,
|
||||
visible: &mut V,
|
||||
leaf: &mut F,
|
||||
) where
|
||||
V: FnMut(&EntityType, &RenderContext) -> bool,
|
||||
F: FnMut(&EntityType, &RenderContext),
|
||||
V: FnMut(&EntityType, &InstanceContext) -> bool,
|
||||
F: FnMut(&EntityType, &InstanceContext),
|
||||
{
|
||||
let insert_entity = EntityType::Insert(insert.clone());
|
||||
let block_style = parent
|
||||
@@ -424,6 +461,7 @@ impl<'a> RenderSceneGraph<'a> {
|
||||
insert,
|
||||
offset,
|
||||
self.annotation_scale,
|
||||
scale_policy,
|
||||
);
|
||||
let transform = local.then(&parent.transform);
|
||||
let mut context = parent.clone();
|
||||
@@ -435,6 +473,7 @@ impl<'a> RenderSceneGraph<'a> {
|
||||
context.depth_base = depth_base;
|
||||
context.depth_scale = depth_scale;
|
||||
context.nesting_depth += 1;
|
||||
context.scale_policy = scale_policy;
|
||||
if let Some(filter) = crate::scene::pick::xclip::insert_spatial_filter(
|
||||
self.document,
|
||||
insert,
|
||||
@@ -465,13 +504,13 @@ impl<'a> RenderSceneGraph<'a> {
|
||||
fn walk_block<V, F>(
|
||||
&self,
|
||||
block_name: &str,
|
||||
context: &RenderContext,
|
||||
context: &InstanceContext,
|
||||
visible: &mut V,
|
||||
leaf: &mut F,
|
||||
stack: &mut Vec<String>,
|
||||
) where
|
||||
V: FnMut(&EntityType, &RenderContext) -> bool,
|
||||
F: FnMut(&EntityType, &RenderContext),
|
||||
V: FnMut(&EntityType, &InstanceContext) -> bool,
|
||||
F: FnMut(&EntityType, &InstanceContext),
|
||||
{
|
||||
if context.nesting_depth > 32 {
|
||||
return;
|
||||
@@ -504,7 +543,13 @@ impl<'a> RenderSceneGraph<'a> {
|
||||
continue;
|
||||
}
|
||||
stack.push(nested.block_name.clone());
|
||||
self.walk_insert_instances(nested, context, visible, leaf);
|
||||
self.walk_insert_instances(
|
||||
nested,
|
||||
BlockScalePolicy::FromInsert,
|
||||
context,
|
||||
visible,
|
||||
leaf,
|
||||
);
|
||||
stack.pop();
|
||||
}
|
||||
_ => {
|
||||
@@ -518,13 +563,13 @@ impl<'a> RenderSceneGraph<'a> {
|
||||
fn walk_owned_content<V, F>(
|
||||
&self,
|
||||
entity: &EntityType,
|
||||
context: &RenderContext,
|
||||
context: &InstanceContext,
|
||||
visible: &mut V,
|
||||
leaf: &mut F,
|
||||
stack: &mut Vec<String>,
|
||||
) where
|
||||
V: FnMut(&EntityType, &RenderContext) -> bool,
|
||||
F: FnMut(&EntityType, &RenderContext),
|
||||
V: FnMut(&EntityType, &InstanceContext) -> bool,
|
||||
F: FnMut(&EntityType, &InstanceContext),
|
||||
{
|
||||
for block_use in entity_render_block_uses(self.document, entity, self.annotation_scale)
|
||||
.into_iter()
|
||||
@@ -537,7 +582,13 @@ impl<'a> RenderSceneGraph<'a> {
|
||||
continue;
|
||||
}
|
||||
stack.push(block_use.insert.block_name.clone());
|
||||
self.walk_insert_instances(&block_use.insert, context, visible, leaf);
|
||||
self.walk_insert_instances(
|
||||
&block_use.insert,
|
||||
block_use.scale_policy,
|
||||
context,
|
||||
visible,
|
||||
leaf,
|
||||
);
|
||||
stack.pop();
|
||||
}
|
||||
}
|
||||
@@ -595,9 +646,24 @@ pub fn insert_transform_at_scale(
|
||||
document: &CadDocument,
|
||||
insert: &Insert,
|
||||
annotation_scale: f32,
|
||||
) -> Transform {
|
||||
insert_transform_with_policy(
|
||||
document,
|
||||
insert,
|
||||
annotation_scale,
|
||||
BlockScalePolicy::FromInsert,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn insert_transform_with_policy(
|
||||
document: &CadDocument,
|
||||
insert: &Insert,
|
||||
annotation_scale: f32,
|
||||
scale_policy: BlockScalePolicy,
|
||||
) -> Transform {
|
||||
let mut transform = insert_transform(document, insert);
|
||||
if (annotation_scale - 1.0).abs() > 1.0e-6
|
||||
if scale_policy == BlockScalePolicy::FromInsert
|
||||
&& (annotation_scale - 1.0).abs() > 1.0e-6
|
||||
&& insert
|
||||
.common
|
||||
.extended_data
|
||||
@@ -615,7 +681,7 @@ pub fn insert_transform_at_scale(
|
||||
transform
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
|
||||
pub enum BlockRole {
|
||||
Insert,
|
||||
DimensionPicture,
|
||||
@@ -631,6 +697,7 @@ pub struct BlockUse {
|
||||
pub block: Handle,
|
||||
pub insert: Insert,
|
||||
pub role: BlockRole,
|
||||
pub scale_policy: BlockScalePolicy,
|
||||
/// Whether this edge belongs to the active render representation. Inactive
|
||||
/// edges remain dependencies so cache invalidation and purge stay complete.
|
||||
pub active: bool,
|
||||
@@ -654,6 +721,7 @@ fn block_use(
|
||||
document: &CadDocument,
|
||||
mut insert: Insert,
|
||||
role: BlockRole,
|
||||
scale_policy: BlockScalePolicy,
|
||||
active: bool,
|
||||
replaces_host_wire: bool,
|
||||
suppress_root_points: bool,
|
||||
@@ -667,6 +735,7 @@ fn block_use(
|
||||
block,
|
||||
insert,
|
||||
role,
|
||||
scale_policy,
|
||||
active,
|
||||
replaces_host_wire,
|
||||
suppress_root_points,
|
||||
@@ -687,6 +756,7 @@ pub fn block_use_from_handle(
|
||||
document,
|
||||
Insert::new(record.name.clone(), insertion),
|
||||
role,
|
||||
BlockScalePolicy::Applied,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
@@ -727,6 +797,7 @@ fn entity_owned_block_uses(
|
||||
document,
|
||||
insert.clone(),
|
||||
BlockRole::Insert,
|
||||
BlockScalePolicy::FromInsert,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
@@ -743,6 +814,7 @@ fn entity_owned_block_uses(
|
||||
document,
|
||||
insert,
|
||||
BlockRole::DimensionPicture,
|
||||
BlockScalePolicy::FromInsert,
|
||||
picture,
|
||||
picture,
|
||||
true,
|
||||
@@ -771,6 +843,7 @@ fn entity_owned_block_uses(
|
||||
document,
|
||||
insert,
|
||||
BlockRole::TablePicture,
|
||||
BlockScalePolicy::FromInsert,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
@@ -780,15 +853,12 @@ fn entity_owned_block_uses(
|
||||
uses.extend(
|
||||
crate::entities::table::block_cell_inserts(table, document, annotation_scale)
|
||||
.into_iter()
|
||||
.map(|mut insert| {
|
||||
insert
|
||||
.common
|
||||
.extended_data
|
||||
.remove_record("AcAnnotativeData");
|
||||
.map(|insert| {
|
||||
block_use(
|
||||
document,
|
||||
insert,
|
||||
BlockRole::TableCell,
|
||||
BlockScalePolicy::Applied,
|
||||
cells_active,
|
||||
false,
|
||||
false,
|
||||
@@ -799,15 +869,12 @@ fn entity_owned_block_uses(
|
||||
}
|
||||
EntityType::MultiLeader(multileader) => {
|
||||
crate::entities::multileader::block_content_insert(document, multileader)
|
||||
.map(|mut insert| {
|
||||
insert
|
||||
.common
|
||||
.extended_data
|
||||
.remove_record("AcAnnotativeData");
|
||||
.map(|insert| {
|
||||
vec![block_use(
|
||||
document,
|
||||
insert,
|
||||
BlockRole::MultiLeaderContent,
|
||||
BlockScalePolicy::Applied,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
@@ -997,8 +1064,9 @@ pub fn insert_instance_transform(
|
||||
insert: &Insert,
|
||||
offset: [f64; 3],
|
||||
annotation_scale: f32,
|
||||
scale_policy: BlockScalePolicy,
|
||||
) -> Transform {
|
||||
let transform = insert_transform_at_scale(document, insert, annotation_scale);
|
||||
let transform = insert_transform_with_policy(document, insert, annotation_scale, scale_policy);
|
||||
if offset == [0.0; 3] {
|
||||
transform
|
||||
} else {
|
||||
@@ -1007,6 +1075,32 @@ pub fn insert_instance_transform(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_instance_translation_delta(
|
||||
document: &CadDocument,
|
||||
insert: &Insert,
|
||||
offset: [f64; 3],
|
||||
annotation_scale: f32,
|
||||
scale_policy: BlockScalePolicy,
|
||||
) -> Vector3 {
|
||||
let base = insert_instance_transform(
|
||||
document,
|
||||
insert,
|
||||
[0.0; 3],
|
||||
annotation_scale,
|
||||
scale_policy,
|
||||
)
|
||||
.apply(Vector3::ZERO);
|
||||
let instance = insert_instance_transform(
|
||||
document,
|
||||
insert,
|
||||
offset,
|
||||
annotation_scale,
|
||||
scale_policy,
|
||||
)
|
||||
.apply(Vector3::ZERO);
|
||||
instance - base
|
||||
}
|
||||
|
||||
pub fn block_contains_hatch(
|
||||
document: &CadDocument,
|
||||
block_name: &str,
|
||||
@@ -1035,3 +1129,38 @@ pub fn block_contains_hatch(
|
||||
memo.insert(key, contains);
|
||||
contains
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use acadrust::xdata::ExtendedDataRecord;
|
||||
|
||||
#[test]
|
||||
fn applied_block_scale_skips_annotative_rescaling() {
|
||||
let document = CadDocument::new();
|
||||
let mut insert = Insert::new("BLOCK", Vector3::new(10.0, 20.0, 0.0));
|
||||
insert
|
||||
.common
|
||||
.extended_data
|
||||
.add_record(ExtendedDataRecord::new("AcAnnotativeData"));
|
||||
let point = Vector3::new(1.0, 0.0, 0.0);
|
||||
|
||||
let from_insert = insert_transform_with_policy(
|
||||
&document,
|
||||
&insert,
|
||||
2.0,
|
||||
BlockScalePolicy::FromInsert,
|
||||
)
|
||||
.apply(point);
|
||||
let applied = insert_transform_with_policy(
|
||||
&document,
|
||||
&insert,
|
||||
2.0,
|
||||
BlockScalePolicy::Applied,
|
||||
)
|
||||
.apply(point);
|
||||
|
||||
assert!((from_insert.x - 12.0).abs() < 1.0e-9);
|
||||
assert!((applied.x - 11.0).abs() < 1.0e-9);
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,7 @@ fn expand_block_mtext(
|
||||
&Default::default(),
|
||||
);
|
||||
expand_insert(
|
||||
&doc,
|
||||
&cache,
|
||||
&ins,
|
||||
Handle::new(999),
|
||||
@@ -86,6 +87,8 @@ fn expand_block_mtext(
|
||||
// Annotation scale: 1.0 = no annotative scaling, matching the
|
||||
// `BlockCache::build(&doc, 1.0, ...)` call above.
|
||||
1.0,
|
||||
OpenCADStudio::scene::BlockScalePolicy::FromInsert,
|
||||
false,
|
||||
)
|
||||
.expect("block defn is cached")
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user