forked from 1b-agent/opencadstudio-upstream
fix(blocks): unify block-backed content
Route pictures, table cells, leader content, symbols, and arrows through one dependency and transform graph.\n\nRefs #864
This commit is contained in:
1 parent
3a5591e036
commit
19629d1246
12 files changed
+902
-557
No files matched your search
@@ -261,28 +261,10 @@ impl OpenCADStudio {
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
|
||||
// Which block definitions are LIVE — reachable from a real
|
||||
// layout container by following every block reference. A
|
||||
// block nothing reachable inserts is dead and purges, even a
|
||||
// whole mutually-referencing subgraph (e.g. the dependent
|
||||
// blocks a detached xref leaves behind: they insert one
|
||||
// another but hang off no layout, so a flat "is it named by
|
||||
// any INSERT" scan keeps them alive forever). Roots:
|
||||
// • *Model_Space / *Paper_Space(N),
|
||||
// • any block whose layout handle resolves to a real Layout
|
||||
// object — a DANGLING layout handle, as orphaned xref
|
||||
// blocks carry, does NOT count, so is_layout() alone can't
|
||||
// shield the dead subgraph it heads,
|
||||
// • dimension-style arrowhead blocks (referenced by handle).
|
||||
// A block's out-edges: its members' INSERT names, dimension
|
||||
// *D blocks, table *T / cell blocks, and MultiLeader content.
|
||||
// Live blocks are reachable from layouts and style roots
|
||||
// through the shared graph. Dangling layouts do not count.
|
||||
let live_blocks: rustc_hash::FxHashSet<String> = {
|
||||
let doc = &self.tabs[i].scene.document;
|
||||
let by_handle: rustc_hash::FxHashMap<acadrust::Handle, String> = doc
|
||||
.block_records
|
||||
.iter()
|
||||
.map(|br| (br.handle, br.name.clone()))
|
||||
.collect();
|
||||
let is_real_layout = |br: &acadrust::BlockRecord| -> bool {
|
||||
let up = br.name.to_ascii_uppercase();
|
||||
up.starts_with("*MODEL_SPACE")
|
||||
@@ -298,44 +280,19 @@ impl OpenCADStudio {
|
||||
return out;
|
||||
};
|
||||
for &h in &br.entity_handles {
|
||||
match doc.get_entity(h) {
|
||||
Some(acadrust::EntityType::Insert(ins))
|
||||
if !ins.block_name.is_empty() =>
|
||||
{
|
||||
out.push(ins.block_name.clone());
|
||||
}
|
||||
Some(acadrust::EntityType::Dimension(d))
|
||||
if !d.base().block_name.is_empty() =>
|
||||
{
|
||||
out.push(d.base().block_name.clone());
|
||||
}
|
||||
Some(acadrust::EntityType::Table(t)) => {
|
||||
if let Some(bh) = t.block_record_handle {
|
||||
if let Some(n) = by_handle.get(&bh) {
|
||||
out.push(n.clone());
|
||||
}
|
||||
}
|
||||
for row in &t.rows {
|
||||
for cell in &row.cells {
|
||||
for c in &cell.contents {
|
||||
if let Some(bh) = c.block_handle {
|
||||
if let Some(n) = by_handle.get(&bh) {
|
||||
out.push(n.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(acadrust::EntityType::MultiLeader(ml)) => {
|
||||
if let Some(bh) = ml.block_content_handle {
|
||||
if let Some(n) = by_handle.get(&bh) {
|
||||
out.push(n.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
let Some(entity) = doc.get_entity(h) else {
|
||||
continue;
|
||||
};
|
||||
out.extend(
|
||||
crate::scene::render_graph::entity_block_uses(
|
||||
doc,
|
||||
entity,
|
||||
1.0,
|
||||
)
|
||||
.into_iter()
|
||||
.filter(|block_use| !block_use.block.is_null())
|
||||
.map(|block_use| block_use.insert.block_name),
|
||||
);
|
||||
}
|
||||
out
|
||||
};
|
||||
@@ -346,15 +303,11 @@ impl OpenCADStudio {
|
||||
.filter(|&br| is_real_layout(br))
|
||||
.map(|br| br.name.clone())
|
||||
.collect();
|
||||
for ds in doc.dim_styles.iter() {
|
||||
for h in [ds.dimblk, ds.dimblk1, ds.dimblk2, ds.dimldrblk] {
|
||||
if !h.is_null() {
|
||||
if let Some(n) = by_handle.get(&h) {
|
||||
stack.push(n.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stack.extend(
|
||||
crate::scene::render_graph::document_block_uses(doc)
|
||||
.into_iter()
|
||||
.map(|block_use| block_use.insert.block_name),
|
||||
);
|
||||
while let Some(n) = stack.pop() {
|
||||
if !live.insert(n.clone()) {
|
||||
continue;
|
||||
@@ -422,9 +375,8 @@ impl OpenCADStudio {
|
||||
.filter(|br| {
|
||||
let up = br.name.to_ascii_uppercase();
|
||||
// Never PURGE an xref *definition* — that is a
|
||||
// detach, not a purge. (Orphaned dependent blocks
|
||||
// a past detach left behind are is_xref=false and
|
||||
// still fall to the reachability test.)
|
||||
// detach, not a purge. Orphaned dependencies from
|
||||
// a past detach still use the reachability test.
|
||||
!br.flags.is_xref
|
||||
&& !br.flags.is_xref_overlay
|
||||
&& !up.starts_with("*MODEL_SPACE")
|
||||
|
||||
+55
-43
@@ -185,6 +185,57 @@ impl crate::entities::traits::FallbackTess for Insert {
|
||||
)
|
||||
}
|
||||
}
|
||||
pub(crate) fn insert_attribute_entities(
|
||||
document: &acadrust::CadDocument,
|
||||
insert: &acadrust::entities::Insert,
|
||||
annotation_scale: f32,
|
||||
) -> 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()
|
||||
{
|
||||
annotation_scale as f64
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
let insertion = insert.insert_point;
|
||||
let scale_about = |point: Vector3| {
|
||||
Vector3::new(
|
||||
insertion.x + (point.x - insertion.x) * block_scale,
|
||||
insertion.y + (point.y - insertion.y) * block_scale,
|
||||
insertion.z + (point.z - insertion.z) * block_scale,
|
||||
)
|
||||
};
|
||||
insert
|
||||
.attributes
|
||||
.iter()
|
||||
.filter(|attribute| {
|
||||
let layer_visible = document
|
||||
.layers
|
||||
.get(&attribute.common.layer)
|
||||
.is_none_or(|layer| !layer.flags.off && !layer.flags.frozen);
|
||||
layer_visible
|
||||
&& (visibility == 2
|
||||
|| (!attribute.common.invisible && !attribute.flags.invisible))
|
||||
})
|
||||
.map(|attribute| {
|
||||
let mut attribute = attribute.clone();
|
||||
if (block_scale - 1.0).abs() > 1.0e-6 {
|
||||
attribute.height *= block_scale;
|
||||
attribute.insertion_point = scale_about(attribute.insertion_point);
|
||||
attribute.alignment_point = scale_about(attribute.alignment_point);
|
||||
}
|
||||
EntityType::AttributeEntity(attribute)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn append_insert_attribute_wires(
|
||||
wires: &mut Vec<WireModel>,
|
||||
document: &acadrust::CadDocument,
|
||||
@@ -202,47 +253,8 @@ pub(crate) fn append_insert_attribute_wires(
|
||||
pslt_factor: f32,
|
||||
anno_scale: f32,
|
||||
) {
|
||||
if ins.attributes.is_empty() {
|
||||
return;
|
||||
}
|
||||
// ATTMODE (header.attribute_visibility):
|
||||
// 0 = Off — every attribute hidden
|
||||
// 1 = Normal — honour per-attribute `invisible` flag (default)
|
||||
// 2 = On — every attribute forced visible, ignoring its flag
|
||||
let attmode = document.header.attribute_visibility;
|
||||
if attmode == 0 {
|
||||
return;
|
||||
}
|
||||
// Attributes inherit the annotative block scale. Pre-scale them here so
|
||||
// text tessellation stays at a neutral scale.
|
||||
let annotative = ins
|
||||
.common
|
||||
.extended_data
|
||||
.get_record("AcAnnotativeData")
|
||||
.is_some();
|
||||
let block_scale = if annotative { anno_scale as f64 } else { 1.0 };
|
||||
let ip = ins.insert_point;
|
||||
let scale_about = |q: acadrust::types::Vector3| {
|
||||
acadrust::types::Vector3::new(
|
||||
ip.x + (q.x - ip.x) * block_scale,
|
||||
ip.y + (q.y - ip.y) * block_scale,
|
||||
ip.z + (q.z - ip.z) * block_scale,
|
||||
)
|
||||
};
|
||||
for attr in &ins.attributes {
|
||||
let per_attr_hidden = attr.common.invisible || attr.flags.invisible;
|
||||
if attmode == 1 && per_attr_hidden {
|
||||
continue;
|
||||
}
|
||||
let attr_entity = EntityType::AttributeEntity({
|
||||
let mut a = attr.clone();
|
||||
if (block_scale - 1.0).abs() > 1e-6 {
|
||||
a.height *= block_scale;
|
||||
a.insertion_point = scale_about(a.insertion_point);
|
||||
a.alignment_point = scale_about(a.alignment_point);
|
||||
}
|
||||
a
|
||||
});
|
||||
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(
|
||||
document,
|
||||
&attr_entity,
|
||||
@@ -258,12 +270,12 @@ pub(crate) fn append_insert_attribute_wires(
|
||||
} else {
|
||||
sub_color
|
||||
};
|
||||
let attr_plottable = if render::is_effective_layer_zero(&attr.common.layer) {
|
||||
let attr_plottable = if render::is_effective_layer_zero(&attr.layer) {
|
||||
ins_layer_plottable
|
||||
} else {
|
||||
document
|
||||
.layers
|
||||
.get(&attr.common.layer)
|
||||
.get(&attr.layer)
|
||||
.map(|layer| layer.is_plottable)
|
||||
.unwrap_or(true)
|
||||
};
|
||||
|
||||
@@ -2104,11 +2104,19 @@ impl MultiLeaderTess for MultiLeader {
|
||||
fill_tris_low: Vec::new(),
|
||||
});
|
||||
|
||||
if let Some(insert) = block_content_insert(document, ml) {
|
||||
let host = acadrust::EntityType::MultiLeader(ml.clone());
|
||||
for block_use in crate::scene::render_graph::entity_render_block_uses(document, &host, 1.0)
|
||||
.into_iter()
|
||||
.filter(|block_use| {
|
||||
block_use.active
|
||||
&& block_use.role
|
||||
== crate::scene::render_graph::BlockRole::MultiLeaderContent
|
||||
})
|
||||
{
|
||||
wires.extend(
|
||||
crate::scene::convert::tess::expand_block_object(
|
||||
document,
|
||||
&insert,
|
||||
&block_use.insert,
|
||||
handle,
|
||||
selected,
|
||||
active_viewport,
|
||||
|
||||
+12
-6
@@ -963,16 +963,22 @@ pub(crate) fn block_cell_inserts(
|
||||
insert.set_x_scale(scale);
|
||||
insert.set_y_scale(scale);
|
||||
insert.set_z_scale(scale);
|
||||
let transform = insert.get_transform();
|
||||
let local_anchor = if has_block_bounds {
|
||||
(block_min + block_max) * 0.5
|
||||
} else {
|
||||
record.base_point
|
||||
crate::scene::render_graph::block_base_point(document, &record.name)
|
||||
};
|
||||
let transformed_base = transform.apply(local_anchor);
|
||||
let transformed_zero = transform.apply(Vector3::ZERO);
|
||||
let transformed_anchor = crate::scene::render_graph::insert_transform(
|
||||
document,
|
||||
&insert,
|
||||
)
|
||||
.apply(local_anchor);
|
||||
insert.insert_point = insert.insert_point
|
||||
- (transformed_base - transformed_zero);
|
||||
+ Vector3::new(
|
||||
position.x as f64 - transformed_anchor.x,
|
||||
position.y as f64 - transformed_anchor.y,
|
||||
position.z as f64 - transformed_anchor.z,
|
||||
);
|
||||
let attribute_definitions: Vec<_> = record
|
||||
.entity_handles
|
||||
.iter()
|
||||
@@ -1001,7 +1007,7 @@ pub(crate) fn block_cell_inserts(
|
||||
AttributeEntity::from_definition(definition, Some(attribute.value.clone()));
|
||||
acadrust::Entity::apply_transform(
|
||||
&mut entity,
|
||||
&insert.get_transform(),
|
||||
&crate::scene::render_graph::insert_transform(document, &insert),
|
||||
);
|
||||
entity.common.handle = table.common.handle;
|
||||
insert.attributes.push(entity);
|
||||
|
||||
Vendored
+119
-142
@@ -115,6 +115,9 @@ pub struct NestedRef {
|
||||
pub block_name: String,
|
||||
pub xform: Transform,
|
||||
pub style: crate::scene::render_graph::InsertStyleSpec,
|
||||
/// ATTRIB geometry is stored in the parent definition's frame, but resolves
|
||||
/// ByBlock and layer-0 properties through this nested insert's style.
|
||||
pub attachments: Vec<LocalWire>,
|
||||
pub instance_offsets: Vec<[f64; 3]>,
|
||||
pub plot_visible: bool,
|
||||
pub plot_l0: bool,
|
||||
@@ -139,12 +142,8 @@ pub enum LocalSub {
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct BlockDefn {
|
||||
pub subs: Vec<LocalSub>,
|
||||
pub base_point: Vector3,
|
||||
pub has_point_marker: bool,
|
||||
/// Union of every sub's local AABB (including nested-INSERT contributions
|
||||
/// resolved at expand time via their own defn's `aabb_local`). XY only —
|
||||
/// the wire renderer is 2D-dominant. Expressed in this defn's *offset*
|
||||
/// Absolute world-space XY (the double-single render path keeps it precise).
|
||||
/// Union of direct and nested geometry in definition coordinates.
|
||||
pub aabb_local: [f32; 4],
|
||||
/// Raw entity count of the source block record (`entity_handles.len()`).
|
||||
/// Divisor for nested depth composition: a nested insert's children get a
|
||||
@@ -207,7 +206,12 @@ impl BlockCache {
|
||||
}
|
||||
|
||||
pub fn defn(&self, block_name: &str) -> Option<&Arc<BlockDefn>> {
|
||||
self.defns.get(block_name)
|
||||
self.defns.get(block_name).or_else(|| {
|
||||
self.defns
|
||||
.iter()
|
||||
.find(|(name, _)| name.eq_ignore_ascii_case(block_name))
|
||||
.map(|(_, definition)| definition)
|
||||
})
|
||||
}
|
||||
|
||||
/// Build definitions referenced by block-backed entities. Space records stay
|
||||
@@ -225,7 +229,7 @@ impl BlockCache {
|
||||
) -> Self {
|
||||
use crate::par::prelude::*;
|
||||
let mut cache = Self::new();
|
||||
let referenced = collect_referenced_blocks(doc);
|
||||
let referenced = collect_referenced_blocks(doc, anno_scale);
|
||||
let mut reference_counts: HashMap<String, usize> = HashMap::default();
|
||||
for entity in doc.entities() {
|
||||
if let EntityType::Insert(insert) = entity {
|
||||
@@ -280,7 +284,7 @@ impl BlockCache {
|
||||
) -> Self {
|
||||
use crate::par::prelude::*;
|
||||
let mut cache = Self::new();
|
||||
let referenced = referenced_block_tree(doc, block_name);
|
||||
let referenced = referenced_block_tree(doc, block_name, anno_scale);
|
||||
cache.defns = referenced
|
||||
.par_iter()
|
||||
.map(|name| {
|
||||
@@ -355,7 +359,11 @@ impl BlockCache {
|
||||
let found = defn.subs.iter().any(|sub| match sub {
|
||||
LocalSub::Wire(wire) => wire.point_marker.is_some(),
|
||||
LocalSub::Nested(insert) => {
|
||||
self.defn_has_point_marker_recursive(&insert.block_name, visited)
|
||||
insert
|
||||
.attachments
|
||||
.iter()
|
||||
.any(|wire| wire.point_marker.is_some())
|
||||
|| self.defn_has_point_marker_recursive(&insert.block_name, visited)
|
||||
}
|
||||
});
|
||||
visited.pop();
|
||||
@@ -385,7 +393,10 @@ impl BlockCache {
|
||||
LocalSub::Wire(lw) => lw.aabb_local,
|
||||
LocalSub::Nested(nref) => {
|
||||
let nested = self.defn_aabb_recursive(&nref.block_name, visited);
|
||||
transform_aabb_xy(nested, &nref.xform)
|
||||
nref.attachments.iter().fold(
|
||||
transform_aabb_xy(nested, &nref.xform),
|
||||
|aabb, wire| aabb_union(aabb, wire.aabb_local),
|
||||
)
|
||||
}
|
||||
};
|
||||
acc = aabb_union(acc, aabb);
|
||||
@@ -395,36 +406,39 @@ impl BlockCache {
|
||||
}
|
||||
}
|
||||
|
||||
fn block_object_name(doc: &CadDocument, entity: &EntityType) -> Option<String> {
|
||||
if let EntityType::Insert(insert) = entity {
|
||||
Some(insert.block_name.clone())
|
||||
} else {
|
||||
crate::scene::render_graph::owned_block_insert(doc, entity)
|
||||
.map(|owned| owned.insert.block_name)
|
||||
}
|
||||
fn block_object_names(
|
||||
doc: &CadDocument,
|
||||
entity: &EntityType,
|
||||
anno_scale: f32,
|
||||
) -> Vec<String> {
|
||||
crate::scene::render_graph::entity_render_block_uses(doc, entity, anno_scale)
|
||||
.into_iter()
|
||||
.filter(|block_use| !block_use.block.is_null())
|
||||
.map(|block_use| block_use.insert.block_name)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Collect every referenced block-backed object.
|
||||
fn collect_referenced_blocks(doc: &CadDocument) -> Vec<String> {
|
||||
/// Collect block definitions needed by rendered representations.
|
||||
fn collect_referenced_blocks(doc: &CadDocument, anno_scale: f32) -> Vec<String> {
|
||||
let mut seen: HashSet<String> = HashSet::default();
|
||||
let mut queue: Vec<String> = Vec::new();
|
||||
|
||||
for entity in doc.entities() {
|
||||
if let Some(name) = block_object_name(doc, entity) {
|
||||
for name in block_object_names(doc, entity, anno_scale) {
|
||||
if seen.insert(name.clone()) {
|
||||
queue.push(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
while let Some(name) = queue.pop() {
|
||||
let Some(br) = doc.block_records.get(&name) else {
|
||||
let Some(br) = crate::scene::render_graph::block_record_by_name(doc, &name) else {
|
||||
continue;
|
||||
};
|
||||
for &eh in &br.entity_handles {
|
||||
let Some(entity) = doc.get_entity(eh) else {
|
||||
continue;
|
||||
};
|
||||
if let Some(name) = block_object_name(doc, entity) {
|
||||
for name in block_object_names(doc, entity, anno_scale) {
|
||||
if seen.insert(name.clone()) {
|
||||
queue.push(name);
|
||||
}
|
||||
@@ -434,19 +448,19 @@ fn collect_referenced_blocks(doc: &CadDocument) -> Vec<String> {
|
||||
seen.into_iter().collect()
|
||||
}
|
||||
|
||||
fn referenced_block_tree(doc: &CadDocument, root: &str) -> Vec<String> {
|
||||
fn referenced_block_tree(doc: &CadDocument, root: &str, anno_scale: f32) -> Vec<String> {
|
||||
let mut seen: HashSet<String> = HashSet::default();
|
||||
let mut queue = vec![root.to_string()];
|
||||
seen.insert(root.to_string());
|
||||
while let Some(name) = queue.pop() {
|
||||
let Some(record) = doc.block_records.get(&name) else {
|
||||
let Some(record) = crate::scene::render_graph::block_record_by_name(doc, &name) else {
|
||||
continue;
|
||||
};
|
||||
for &handle in &record.entity_handles {
|
||||
let Some(entity) = doc.get_entity(handle) else {
|
||||
continue;
|
||||
};
|
||||
if let Some(name) = block_object_name(doc, entity) {
|
||||
for name in block_object_names(doc, entity, anno_scale) {
|
||||
if seen.insert(name.clone()) {
|
||||
queue.push(name);
|
||||
}
|
||||
@@ -474,7 +488,7 @@ fn build_defn(
|
||||
viewport: Option<Handle>,
|
||||
depth_map: &HashMap<u64, [f32; 2]>,
|
||||
) -> BlockDefn {
|
||||
let br = match doc.block_records.get(block_name) {
|
||||
let br = match crate::scene::render_graph::block_record_by_name(doc, block_name) {
|
||||
Some(br) => br,
|
||||
None => return BlockDefn::default(),
|
||||
};
|
||||
@@ -520,19 +534,31 @@ fn build_defn(
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if let Some(owned) = crate::scene::render_graph::owned_block_insert(doc, entity) {
|
||||
if owned.render_picture {
|
||||
let mut nested = build_nested_ref(
|
||||
&owned.insert,
|
||||
doc,
|
||||
bg_color,
|
||||
viewport,
|
||||
depth_map,
|
||||
);
|
||||
nested.suppress_root_points = owned.suppress_root_points;
|
||||
subs.push(LocalSub::Nested(nested));
|
||||
continue;
|
||||
}
|
||||
let block_uses: Vec<_> = crate::scene::render_graph::entity_render_block_uses(
|
||||
doc,
|
||||
entity,
|
||||
anno_scale,
|
||||
)
|
||||
.into_iter()
|
||||
.filter(|block_use| block_use.active)
|
||||
.collect();
|
||||
let replaces_host = block_uses
|
||||
.iter()
|
||||
.any(|block_use| block_use.replaces_host_wire);
|
||||
for block_use in &block_uses {
|
||||
let mut nested = build_nested_ref(
|
||||
&block_use.insert,
|
||||
doc,
|
||||
anno_scale,
|
||||
bg_color,
|
||||
viewport,
|
||||
depth_map,
|
||||
);
|
||||
nested.suppress_root_points = block_use.suppress_root_points;
|
||||
subs.push(LocalSub::Nested(nested));
|
||||
}
|
||||
if replaces_host {
|
||||
continue;
|
||||
}
|
||||
match entity {
|
||||
EntityType::Block(_) | EntityType::BlockEnd(_) => continue,
|
||||
@@ -549,50 +575,6 @@ fn build_defn(
|
||||
{
|
||||
continue
|
||||
}
|
||||
EntityType::Insert(nested_ins) => {
|
||||
subs.push(LocalSub::Nested(build_nested_ref(
|
||||
nested_ins, doc, bg_color, viewport, depth_map,
|
||||
)));
|
||||
}
|
||||
EntityType::Dimension(_) => {
|
||||
for wire in tessellate_sub_local(
|
||||
doc,
|
||||
entity,
|
||||
anno_scale,
|
||||
annotation_scale_handle,
|
||||
bg_color,
|
||||
viewport,
|
||||
depth_map,
|
||||
) {
|
||||
subs.push(LocalSub::Wire(wire));
|
||||
}
|
||||
}
|
||||
EntityType::Table(table) => {
|
||||
for wire in tessellate_sub_local(
|
||||
doc,
|
||||
entity,
|
||||
anno_scale,
|
||||
annotation_scale_handle,
|
||||
bg_color,
|
||||
viewport,
|
||||
depth_map,
|
||||
) {
|
||||
subs.push(LocalSub::Wire(wire));
|
||||
}
|
||||
for insert in crate::entities::table::block_cell_inserts(
|
||||
table,
|
||||
doc,
|
||||
anno_scale,
|
||||
) {
|
||||
subs.push(LocalSub::Nested(build_nested_ref(
|
||||
&insert,
|
||||
doc,
|
||||
bg_color,
|
||||
viewport,
|
||||
depth_map,
|
||||
)));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// A wide polyline inside a block carries its `world_width` on
|
||||
// the LocalWire; `emit_wire` scales it by the insert transform
|
||||
@@ -614,7 +596,6 @@ fn build_defn(
|
||||
}
|
||||
BlockDefn {
|
||||
subs,
|
||||
base_point: crate::scene::render_graph::block_base_point(doc, block_name),
|
||||
has_point_marker: false,
|
||||
aabb_local: [0.0; 4],
|
||||
child_count: br.entity_handles.len(),
|
||||
@@ -624,16 +605,19 @@ fn build_defn(
|
||||
fn build_nested_ref(
|
||||
nested_ins: &acadrust::entities::Insert,
|
||||
doc: &CadDocument,
|
||||
anno_scale: f32,
|
||||
bg_color: [f32; 4],
|
||||
viewport: Option<Handle>,
|
||||
depth_map: &HashMap<u64, [f32; 2]>,
|
||||
) -> NestedRef {
|
||||
let _ = bg_color;
|
||||
|
||||
// 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(doc, nested_ins);
|
||||
let xform = crate::scene::render_graph::insert_transform_at_scale(
|
||||
doc,
|
||||
nested_ins,
|
||||
anno_scale,
|
||||
);
|
||||
let clip_poly = crate::scene::pick::xclip::insert_spatial_filter(doc, nested_ins)
|
||||
.map(|filter| {
|
||||
crate::scene::pick::xclip::world_clip_polygon_for_transform(filter, &xform)
|
||||
@@ -646,11 +630,30 @@ fn build_nested_ref(
|
||||
.get(&nested_ins.common.layer)
|
||||
.map(|layer| layer.is_plottable)
|
||||
.unwrap_or(true);
|
||||
let attachments = crate::entities::insert::insert_attribute_entities(
|
||||
doc,
|
||||
nested_ins,
|
||||
anno_scale,
|
||||
)
|
||||
.into_iter()
|
||||
.flat_map(|attribute| {
|
||||
tessellate_sub_local(
|
||||
doc,
|
||||
&attribute,
|
||||
1.0,
|
||||
None,
|
||||
bg_color,
|
||||
viewport,
|
||||
depth_map,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
NestedRef {
|
||||
block_name: nested_ins.block_name.clone(),
|
||||
xform,
|
||||
style: crate::scene::render_graph::InsertStyleSpec::new(doc, nested_ins, viewport),
|
||||
attachments,
|
||||
instance_offsets: crate::scene::render_graph::array_offsets(nested_ins),
|
||||
plot_visible,
|
||||
plot_l0,
|
||||
@@ -941,6 +944,7 @@ pub fn aabb_disjoint_xy(a: [f32; 4], b: [f32; 4]) -> bool {
|
||||
/// Returns `None` if no defn is cached for `ins.block_name`. Returns
|
||||
/// `Some(empty)` if the defn exists but is empty.
|
||||
pub fn expand_insert(
|
||||
doc: &CadDocument,
|
||||
cache: &BlockCache,
|
||||
ins: &acadrust::entities::Insert,
|
||||
ins_handle: Handle,
|
||||
@@ -975,26 +979,7 @@ pub fn expand_insert(
|
||||
suppress_root_points: bool,
|
||||
) -> Option<Vec<WireModel>> {
|
||||
let defn = cache.defn(&ins.block_name)?;
|
||||
let base = defn.base_point;
|
||||
let mut xform = Transform::from_translation(Vector3::new(-base.x, -base.y, -base.z))
|
||||
.then(&ins.get_transform());
|
||||
// Annotative blocks (the flag lives on the block definition; the instance is
|
||||
// marked with the AcAnnotativeData XDATA) scale as ONE uniform unit about
|
||||
// their insertion point — internal geometry/text/attributes are carried by
|
||||
// this transform, never scaled individually (which would double-scale).
|
||||
if (anno_scale - 1.0).abs() > 1e-6
|
||||
&& ins
|
||||
.common
|
||||
.extended_data
|
||||
.get_record("AcAnnotativeData")
|
||||
.is_some()
|
||||
{
|
||||
let p = ins.insert_point;
|
||||
let scale_about_p = Transform::from_translation(Vector3::new(-p.x, -p.y, -p.z))
|
||||
.then(&Transform::from_scale(anno_scale as f64))
|
||||
.then(&Transform::from_translation(Vector3::new(p.x, p.y, p.z)));
|
||||
xform = xform.then(&scale_about_p);
|
||||
}
|
||||
let xform = crate::scene::render_graph::insert_transform_at_scale(doc, ins, anno_scale);
|
||||
let name = ins_handle.value().to_string();
|
||||
let prototype_key = if !ins.is_array()
|
||||
&& cache.prototype_blocks.contains(&ins.block_name)
|
||||
@@ -1793,41 +1778,6 @@ fn expand_defn(
|
||||
emit_wire(lw, accum_xform, ctx, out, d_range);
|
||||
}
|
||||
LocalSub::Nested(nref) => {
|
||||
if visited.iter().any(|n| n == &nref.block_name) {
|
||||
// Cycle — skip.
|
||||
continue;
|
||||
}
|
||||
let Some(nested_defn) = ctx.cache.defn(&nref.block_name) else {
|
||||
continue;
|
||||
};
|
||||
// Nested-INSERT cull: union AABB of the nested defn,
|
||||
// transformed by composed xform, vs view rect + pixel size.
|
||||
// `nested_defn.aabb_local` lives in the nested defn's offset
|
||||
// frame — re-add `nested_defn.local_offset` in f64 before
|
||||
// composing with the parent transforms.
|
||||
let composed = nref.xform.then(accum_xform);
|
||||
let world = transform_aabb_xy(nested_defn.aabb_local, &composed);
|
||||
let local = [
|
||||
world[0] as f32,
|
||||
world[1] as f32,
|
||||
world[2] as f32,
|
||||
world[3] as f32,
|
||||
];
|
||||
if let Some(view) = ctx.view_aabb {
|
||||
if aabb_disjoint_xy(local, view) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if let Some(wpp) = ctx.world_per_pixel {
|
||||
if aabb_pixel_size(local, wpp) < MIN_PIXEL_SIZE {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Resolve the nested insert's own style against the outer ctx:
|
||||
// ByBlock inherits the outer insert; a nested insert that is
|
||||
// itself on layer "0" with ByLayer props inherits the outer
|
||||
// layer-0 target (so its ByBlock leaves resolve to that layer,
|
||||
// not layer 0). Mirrors the leaf resolution in emit_wire.
|
||||
let parent_style = crate::scene::render_graph::BlockStyle {
|
||||
insert: (
|
||||
ctx.ins_color,
|
||||
@@ -1863,6 +1813,33 @@ 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);
|
||||
}
|
||||
if visited.iter().any(|n| n == &nref.block_name) {
|
||||
continue;
|
||||
}
|
||||
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 local = [
|
||||
world[0] as f32,
|
||||
world[1] as f32,
|
||||
world[2] as f32,
|
||||
world[3] as f32,
|
||||
];
|
||||
if let Some(view) = ctx.view_aabb {
|
||||
if aabb_disjoint_xy(local, view) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if let Some(wpp) = ctx.world_per_pixel {
|
||||
if aabb_pixel_size(local, wpp) < MIN_PIXEL_SIZE {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
visited.push(nref.block_name.clone());
|
||||
// Children of this nested insert stack inside the slot its own
|
||||
// rank owns — same composition the scene graph applies.
|
||||
|
||||
+30
-33
@@ -73,6 +73,7 @@ fn block_entity_transform(
|
||||
block_name: &str,
|
||||
target: Handle,
|
||||
visited: &mut Vec<String>,
|
||||
annotation_scale: f32,
|
||||
) -> Option<acadrust::types::Transform> {
|
||||
if visited
|
||||
.iter()
|
||||
@@ -90,12 +91,28 @@ fn block_entity_transform(
|
||||
|
||||
visited.push(record.name.clone());
|
||||
let transform = record.entity_handles.iter().find_map(|handle| {
|
||||
let EntityType::Insert(insert) = document.get_entity(*handle)? else {
|
||||
return None;
|
||||
};
|
||||
let inner =
|
||||
block_entity_transform(document, &insert.block_name, target, visited)?;
|
||||
Some(inner.then(&insert.get_transform()))
|
||||
let entity = document.get_entity(*handle)?;
|
||||
crate::scene::render_graph::entity_render_block_uses(
|
||||
document,
|
||||
entity,
|
||||
annotation_scale,
|
||||
)
|
||||
.into_iter()
|
||||
.filter(|block_use| block_use.active)
|
||||
.find_map(|block_use| {
|
||||
let inner = block_entity_transform(
|
||||
document,
|
||||
&block_use.insert.block_name,
|
||||
target,
|
||||
visited,
|
||||
annotation_scale,
|
||||
)?;
|
||||
Some(inner.then(&crate::scene::render_graph::insert_transform_at_scale(
|
||||
document,
|
||||
&block_use.insert,
|
||||
annotation_scale,
|
||||
)))
|
||||
})
|
||||
});
|
||||
visited.pop();
|
||||
transform
|
||||
@@ -461,7 +478,7 @@ impl Scene {
|
||||
/// Centre on a text entity stored in a block definition, transformed
|
||||
/// through the concrete visible INSERT occurrence that FIND is visiting.
|
||||
pub fn center_camera_on_block_entity(&mut self, insert: Handle, entity: Handle) -> bool {
|
||||
let (source, mut transform) = {
|
||||
let (source, transform) = {
|
||||
let Some(EntityType::Insert(insert_entity)) = self.document.get_entity(insert) else {
|
||||
return false;
|
||||
};
|
||||
@@ -474,40 +491,20 @@ impl Scene {
|
||||
&insert_entity.block_name,
|
||||
entity,
|
||||
&mut visited,
|
||||
self.annotation_scale,
|
||||
) else {
|
||||
return false;
|
||||
};
|
||||
(
|
||||
source.clone(),
|
||||
inner.then(&insert_entity.get_transform()),
|
||||
inner.then(&crate::scene::render_graph::insert_transform_at_scale(
|
||||
&self.document,
|
||||
insert_entity,
|
||||
self.annotation_scale,
|
||||
)),
|
||||
)
|
||||
};
|
||||
|
||||
if (self.annotation_scale - 1.0).abs() > 1e-6 {
|
||||
let Some(EntityType::Insert(insert_entity)) = self.document.get_entity(insert) else {
|
||||
return false;
|
||||
};
|
||||
if insert_entity
|
||||
.common
|
||||
.extended_data
|
||||
.get_record("AcAnnotativeData")
|
||||
.is_some()
|
||||
{
|
||||
let point = insert_entity.insert_point;
|
||||
let scale_about =
|
||||
acadrust::types::Transform::from_translation(acadrust::types::Vector3::new(
|
||||
-point.x, -point.y, -point.z,
|
||||
))
|
||||
.then(&acadrust::types::Transform::from_scale(
|
||||
self.annotation_scale as f64,
|
||||
))
|
||||
.then(&acadrust::types::Transform::from_translation(
|
||||
acadrust::types::Vector3::new(point.x, point.y, point.z),
|
||||
));
|
||||
transform = transform.then(&scale_about);
|
||||
}
|
||||
}
|
||||
|
||||
let fallback_z = self.active_camera_target().z;
|
||||
let wires = self.tessellate_one(&source);
|
||||
let Some(local_center) = rendered_wire_center(&wires, fallback_z) else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! DGN line-style rendering (first pass).
|
||||
//!
|
||||
//! Linetypes converted from MicroStation DGN store their real pattern as DGN
|
||||
//! Imported DGN linetypes store their real pattern as DGN
|
||||
//! line-style objects (`AcDbLS*`), not standard `LTYPE` dashes — the standard
|
||||
//! table entry is empty, so acadrust exposes the structure in
|
||||
//! [`CadDocument::dgn_ls_definitions`] / `dgn_ls_components` instead. See
|
||||
@@ -12,7 +12,7 @@
|
||||
//! lengths are carried through to the pipe walls.
|
||||
|
||||
use acadrust::objects::DgnLsComponentType;
|
||||
use acadrust::types::{Handle, Transform, Vector3};
|
||||
use acadrust::types::{Handle, Vector3};
|
||||
use acadrust::{CadDocument, EntityType};
|
||||
use std::collections::HashSet;
|
||||
|
||||
@@ -133,29 +133,52 @@ pub fn wall_dashes(doc: &CadDocument, lt_name: &str) -> Vec<f64> {
|
||||
/// radius, which is also the offset of the two pipe walls (they sit tangent to
|
||||
/// the end circles) — so it doubles as the rail offset for the double line.
|
||||
pub fn symbol_radius(doc: &CadDocument, block: Handle, scale: f64) -> f64 {
|
||||
let Some(br) = doc.block_records.iter().find(|b| b.handle == block) else {
|
||||
let mut r = 0.0_f64;
|
||||
let depths = rustc_hash::FxHashMap::default();
|
||||
let graph = crate::scene::render_graph::RenderSceneGraph::new(doc, None, None, true, &depths);
|
||||
let Some(block_use) = crate::scene::render_graph::block_use_from_handle(
|
||||
doc,
|
||||
block,
|
||||
crate::scene::render_graph::BlockRole::LineStyleSymbol,
|
||||
Vector3::ZERO,
|
||||
) else {
|
||||
return 0.0;
|
||||
};
|
||||
let bx = br.base_point.x;
|
||||
let by = br.base_point.y;
|
||||
let d = |x: f64, y: f64| ((x - bx).powi(2) + (y - by).powi(2)).sqrt();
|
||||
let mut r = 0.0_f64;
|
||||
for eh in &br.entity_handles {
|
||||
let Some(e) = doc.get_entity(*eh) else {
|
||||
continue;
|
||||
};
|
||||
let ext = match e {
|
||||
EntityType::Ellipse(el) => {
|
||||
let a = (el.major_axis.x.powi(2) + el.major_axis.y.powi(2)).sqrt();
|
||||
d(el.center.x, el.center.y) + a
|
||||
}
|
||||
EntityType::Circle(c) => d(c.center.x, c.center.y) + c.radius,
|
||||
EntityType::Arc(a) => d(a.center.x, a.center.y) + a.radius,
|
||||
EntityType::Line(l) => d(l.start.x, l.start.y).max(d(l.end.x, l.end.y)),
|
||||
_ => 0.0,
|
||||
};
|
||||
r = r.max(ext);
|
||||
}
|
||||
graph.walk_insert(
|
||||
&block_use.insert,
|
||||
block,
|
||||
|_, _| true,
|
||||
|entity, context| {
|
||||
let mut placed = entity.clone();
|
||||
placed.apply_transform(&context.transform);
|
||||
let distance = |x: f64, y: f64| x.hypot(y);
|
||||
let extent = match &placed {
|
||||
EntityType::Ellipse(ellipse) => {
|
||||
distance(ellipse.center.x, ellipse.center.y)
|
||||
+ distance(ellipse.major_axis.x, ellipse.major_axis.y)
|
||||
}
|
||||
EntityType::Circle(circle) => {
|
||||
distance(circle.center.x, circle.center.y) + circle.radius
|
||||
}
|
||||
EntityType::Arc(arc) => distance(arc.center.x, arc.center.y) + arc.radius,
|
||||
EntityType::Line(line) => distance(line.start.x, line.start.y)
|
||||
.max(distance(line.end.x, line.end.y)),
|
||||
_ => crate::scene::convert::tess::entity_world_aabb_f64(&placed)
|
||||
.map(|[min_x, min_y, max_x, max_y]| {
|
||||
[
|
||||
distance(min_x, min_y),
|
||||
distance(max_x, min_y),
|
||||
distance(max_x, max_y),
|
||||
distance(min_x, max_y),
|
||||
]
|
||||
.into_iter()
|
||||
.fold(0.0_f64, f64::max)
|
||||
})
|
||||
.unwrap_or(0.0),
|
||||
};
|
||||
r = r.max(extent);
|
||||
},
|
||||
);
|
||||
let s = if scale.abs() > 1e-9 { scale } else { 1.0 };
|
||||
r / s
|
||||
}
|
||||
@@ -286,46 +309,50 @@ pub fn place_block_wires(
|
||||
world_per_pixel: Option<f32>,
|
||||
bg_color: [f32; 4],
|
||||
) -> Vec<WireModel> {
|
||||
let Some(br) = doc.block_records.iter().find(|b| b.handle == block) else {
|
||||
return Vec::new();
|
||||
};
|
||||
// The symbol block's native geometry is drawn at 1 / scale_divisor (the
|
||||
// divisor is read from the symbol component's leaf data). Scale about the
|
||||
// origin, then translate the (scaled) base point to the placement point.
|
||||
let s = if scale_divisor.abs() > 1e-9 {
|
||||
1.0 / scale_divisor
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
let scale = Transform::from_scale(s);
|
||||
let offset = Vector3::new(
|
||||
at[0] - br.base_point.x * s,
|
||||
at[1] - br.base_point.y * s,
|
||||
at[2] - br.base_point.z * s,
|
||||
);
|
||||
let Some(mut block_use) = crate::scene::render_graph::block_use_from_handle(
|
||||
doc,
|
||||
block,
|
||||
crate::scene::render_graph::BlockRole::LineStyleSymbol,
|
||||
Vector3::new(at[0], at[1], at[2]),
|
||||
) 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);
|
||||
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();
|
||||
for eh in &br.entity_handles {
|
||||
let Some(ent) = doc.get_entity(*eh) else {
|
||||
continue;
|
||||
};
|
||||
let mut clone = ent.clone();
|
||||
clone.as_entity_mut().apply_transform(&scale);
|
||||
clone.as_entity_mut().translate(offset);
|
||||
out.extend(super::tessellate::tessellate(
|
||||
doc,
|
||||
*eh,
|
||||
&clone,
|
||||
false,
|
||||
color,
|
||||
0.0,
|
||||
[0.0; 8],
|
||||
line_weight_px,
|
||||
anno_scale,
|
||||
None,
|
||||
world_per_pixel,
|
||||
bg_color,
|
||||
false,
|
||||
));
|
||||
}
|
||||
graph.walk_insert(
|
||||
insert,
|
||||
block,
|
||||
|_, _| true,
|
||||
|entity, context| {
|
||||
let mut placed = entity.clone();
|
||||
placed.apply_transform(&context.transform);
|
||||
out.extend(super::tessellate::tessellate(
|
||||
doc,
|
||||
entity.common().handle,
|
||||
&placed,
|
||||
false,
|
||||
color,
|
||||
0.0,
|
||||
[0.0; 8],
|
||||
line_weight_px,
|
||||
anno_scale,
|
||||
None,
|
||||
world_per_pixel,
|
||||
bg_color,
|
||||
false,
|
||||
));
|
||||
},
|
||||
);
|
||||
out
|
||||
}
|
||||
+36
-11
@@ -122,6 +122,7 @@ pub(crate) fn expand_block_object(
|
||||
.map(|record| record.flags.is_xref || record.flags.is_xref_overlay)
|
||||
.unwrap_or(false);
|
||||
let mut wires = cache::block_cache::expand_insert(
|
||||
document,
|
||||
cache,
|
||||
insert,
|
||||
owner,
|
||||
@@ -146,7 +147,11 @@ pub(crate) fn expand_block_object(
|
||||
|
||||
if options.apply_xclip {
|
||||
if let Some(filter) = pick::xclip::insert_spatial_filter(document, insert) {
|
||||
let transform = crate::scene::render_graph::insert_transform(document, insert);
|
||||
let transform = crate::scene::render_graph::insert_transform_at_scale(
|
||||
document,
|
||||
insert,
|
||||
anno_scale,
|
||||
);
|
||||
let polygon = pick::xclip::world_clip_polygon_for_transform(filter, &transform);
|
||||
pick::xclip::clip_wires(&mut wires, &polygon);
|
||||
for wire in &mut wires {
|
||||
@@ -952,12 +957,17 @@ fn tessellate_entity_inner(
|
||||
// Render non-annotative dimensions from their stored picture block.
|
||||
// Rebuild geometry only when no usable block exists.
|
||||
if matches!(e, EntityType::Dimension(_)) {
|
||||
if let Some(owned) = crate::scene::render_graph::owned_block_insert(document, e)
|
||||
.filter(|owned| owned.render_picture)
|
||||
if let Some(block_use) = crate::scene::render_graph::entity_render_block_uses(
|
||||
document,
|
||||
e,
|
||||
anno_scale,
|
||||
)
|
||||
.into_iter()
|
||||
.find(|block_use| block_use.active)
|
||||
{
|
||||
let mut wires = expand_block_object(
|
||||
document,
|
||||
&owned.insert,
|
||||
&block_use.insert,
|
||||
h,
|
||||
sel,
|
||||
active_viewport,
|
||||
@@ -969,7 +979,7 @@ fn tessellate_entity_inner(
|
||||
world_per_pixel,
|
||||
pslt_factor,
|
||||
BlockObjectOptions {
|
||||
suppress_root_points: owned.suppress_root_points,
|
||||
suppress_root_points: block_use.suppress_root_points,
|
||||
..BlockObjectOptions::default()
|
||||
},
|
||||
)
|
||||
@@ -1054,10 +1064,19 @@ fn tessellate_entity_inner(
|
||||
// When the block exists we render it directly. Same pattern as
|
||||
// Dimension's `block_name`.
|
||||
if let EntityType::Table(table) = e {
|
||||
if let Some(owned) = crate::scene::render_graph::owned_block_insert(document, e) {
|
||||
if let Some(block_use) = crate::scene::render_graph::entity_render_block_uses(
|
||||
document,
|
||||
e,
|
||||
anno_scale,
|
||||
)
|
||||
.into_iter()
|
||||
.find(|block_use| {
|
||||
block_use.active
|
||||
&& block_use.role == crate::scene::render_graph::BlockRole::TablePicture
|
||||
}) {
|
||||
let mut wires = expand_block_object(
|
||||
document,
|
||||
&owned.insert,
|
||||
&block_use.insert,
|
||||
h,
|
||||
sel,
|
||||
active_viewport,
|
||||
@@ -1091,9 +1110,15 @@ fn tessellate_entity_inner(
|
||||
line_weight_px,
|
||||
table_anno,
|
||||
);
|
||||
for insert in
|
||||
crate::entities::table::block_cell_inserts(table, document, table_anno)
|
||||
{
|
||||
for block_use in crate::scene::render_graph::entity_render_block_uses(
|
||||
document,
|
||||
e,
|
||||
table_anno,
|
||||
)
|
||||
.into_iter()
|
||||
.filter(|block_use| {
|
||||
block_use.role == crate::scene::render_graph::BlockRole::TableCell
|
||||
}) {
|
||||
wires.extend(tessellate_entity(
|
||||
document,
|
||||
selected,
|
||||
@@ -1101,7 +1126,7 @@ fn tessellate_entity_inner(
|
||||
bg_color,
|
||||
1.0,
|
||||
None,
|
||||
&EntityType::Insert(insert),
|
||||
&EntityType::Insert(block_use.insert),
|
||||
block_cache,
|
||||
view_aabb,
|
||||
world_per_pixel,
|
||||
|
||||
@@ -2091,15 +2091,17 @@ fn custom_arrow_from_block(
|
||||
true,
|
||||
&depths,
|
||||
);
|
||||
let insert = acadrust::entities::Insert::new(
|
||||
record.name.clone(),
|
||||
let block_use = crate::scene::render_graph::block_use_from_handle(
|
||||
doc,
|
||||
record.handle,
|
||||
crate::scene::render_graph::BlockRole::ArrowHead,
|
||||
acadrust::types::Vector3::ZERO,
|
||||
);
|
||||
)?;
|
||||
let mut lines = Vec::new();
|
||||
let mut fill = Vec::new();
|
||||
let mut deferred_hatch = false;
|
||||
graph.walk_insert(
|
||||
&insert,
|
||||
&block_use.insert,
|
||||
record.handle,
|
||||
|_, _| true,
|
||||
|entity, context| {
|
||||
|
||||
+17
-15
@@ -1289,6 +1289,7 @@ impl Scene {
|
||||
all_visible,
|
||||
depth_map.as_ref(),
|
||||
)
|
||||
.with_annotation_scale(self.annotation_scale)
|
||||
.with_viewport(viewport);
|
||||
let mut hatch_block_memo = std::collections::HashMap::new();
|
||||
let mut models = Vec::new();
|
||||
@@ -1315,24 +1316,24 @@ impl Scene {
|
||||
return matches!(entity, EntityType::Insert(_))
|
||||
&& targets.contains(&common.handle);
|
||||
}
|
||||
match entity {
|
||||
EntityType::Insert(insert) => {
|
||||
let block_uses: Vec<_> = crate::scene::render_graph::entity_render_block_uses(
|
||||
&self.document,
|
||||
entity,
|
||||
1.0,
|
||||
)
|
||||
.into_iter()
|
||||
.filter(|block_use| block_use.active)
|
||||
.collect();
|
||||
if block_uses.is_empty() {
|
||||
true
|
||||
} else {
|
||||
block_uses.into_iter().any(|block_use| {
|
||||
crate::scene::render_graph::block_contains_hatch(
|
||||
&self.document,
|
||||
&insert.block_name,
|
||||
&block_use.insert.block_name,
|
||||
&mut hatch_block_memo,
|
||||
)
|
||||
}
|
||||
EntityType::Dimension(dimension) => {
|
||||
let name = dimension.base().block_name.trim();
|
||||
!name.is_empty()
|
||||
&& crate::scene::render_graph::block_contains_hatch(
|
||||
&self.document,
|
||||
name,
|
||||
&mut hatch_block_memo,
|
||||
)
|
||||
}
|
||||
_ => true,
|
||||
})
|
||||
}
|
||||
},
|
||||
|entity, context| {
|
||||
@@ -1464,7 +1465,8 @@ impl Scene {
|
||||
annotation_scale_handle,
|
||||
all_visible,
|
||||
depth_map.as_ref(),
|
||||
);
|
||||
)
|
||||
.with_annotation_scale(self.annotation_scale);
|
||||
let mut models = Vec::new();
|
||||
let mut wipeout_sources = rustc_hash::FxHashMap::default();
|
||||
graph.walk_root(
|
||||
|
||||
+86
-70
@@ -6082,6 +6082,7 @@ impl Scene {
|
||||
all_visible,
|
||||
depth_map.as_ref(),
|
||||
)
|
||||
.with_annotation_scale(self.annotation_scale)
|
||||
.with_viewport(viewport);
|
||||
let mut models = Vec::new();
|
||||
let mut image_sources = rustc_hash::FxHashMap::default();
|
||||
@@ -6739,6 +6740,7 @@ impl Scene {
|
||||
all_visible,
|
||||
depth_map.as_ref(),
|
||||
)
|
||||
.with_annotation_scale(self.annotation_scale)
|
||||
.with_viewport(viewport);
|
||||
let mut out = Vec::new();
|
||||
graph.walk_root(
|
||||
@@ -7027,9 +7029,7 @@ impl Scene {
|
||||
self.belongs_to_visible_block(handle, common.owner_handle, self.interaction_block_handle())
|
||||
}
|
||||
|
||||
/// Per-Insert hatch models in the current layout, keyed by the Insert
|
||||
/// handle so a click on a block-internal hatch can select the parent
|
||||
/// Insert; block-owned leaves resolve to their owning instance.
|
||||
/// Instanced hatch models keyed by their block-backed host handle.
|
||||
|
||||
pub fn insert_hatches_for_click(&self) -> Arc<HashMap<Handle, Vec<HatchModel>>> {
|
||||
let interaction_block = self.interaction_block_handle();
|
||||
@@ -7042,10 +7042,15 @@ impl Scene {
|
||||
epoch,
|
||||
CACHE_CATEGORY_INSERT_HATCH,
|
||||
|handle| {
|
||||
matches!(
|
||||
self.document.get_entity(handle),
|
||||
Some(EntityType::Insert(_))
|
||||
)
|
||||
self.document.get_entity(handle).is_some_and(|entity| {
|
||||
render_graph::entity_render_block_uses(
|
||||
&self.document,
|
||||
entity,
|
||||
self.annotation_scale,
|
||||
)
|
||||
.into_iter()
|
||||
.any(|block_use| block_use.active)
|
||||
})
|
||||
},
|
||||
)
|
||||
{
|
||||
@@ -7082,22 +7087,22 @@ impl Scene {
|
||||
annotation_scale_handle,
|
||||
all_visible,
|
||||
depth_map.as_ref(),
|
||||
);
|
||||
)
|
||||
.with_annotation_scale(self.annotation_scale);
|
||||
for entity in self.document.entities() {
|
||||
let contextual = crate::scene::annotative::entity_for_annotation_context(
|
||||
&self.document,
|
||||
entity,
|
||||
annotation_scale_handle,
|
||||
);
|
||||
let EntityType::Insert(ins) = contextual.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
if ins.common.invisible
|
||||
|| self.entity_temporarily_hidden(ins.common.handle)
|
||||
|| layer_hidden(&ins.common.layer)
|
||||
let host = contextual.as_ref();
|
||||
let common = host.common();
|
||||
if common.invisible
|
||||
|| self.entity_temporarily_hidden(common.handle)
|
||||
|| layer_hidden(&common.layer)
|
||||
|| crate::scene::annotative::annotative_offscale_for(
|
||||
&self.document,
|
||||
&ins.common,
|
||||
common,
|
||||
annotation_scale_handle,
|
||||
all_visible,
|
||||
)
|
||||
@@ -7105,58 +7110,67 @@ impl Scene {
|
||||
continue;
|
||||
}
|
||||
if !self.belongs_to_visible_block(
|
||||
ins.common.handle,
|
||||
ins.common.owner_handle,
|
||||
common.handle,
|
||||
common.owner_handle,
|
||||
interaction_block,
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if !render_graph::block_contains_hatch(
|
||||
for block_use in render_graph::entity_render_block_uses(
|
||||
&self.document,
|
||||
&ins.block_name,
|
||||
&mut hatch_memo,
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
graph.walk_insert(
|
||||
ins,
|
||||
ins.common.handle,
|
||||
|_, _| true,
|
||||
|entity, context| {
|
||||
let EntityType::Hatch(source) = entity else {
|
||||
return;
|
||||
};
|
||||
let mut placed = EntityType::Hatch(source.clone());
|
||||
placed.apply_transform(&context.transform);
|
||||
let EntityType::Hatch(hatch) = placed else {
|
||||
return;
|
||||
};
|
||||
let color = crate::scene::view::render::adapt_to_bg(
|
||||
context.style_for(&self.document, entity).0,
|
||||
self.current_bg(),
|
||||
);
|
||||
let Some(mut model) = Self::hatch_model_from_dxf(&hatch, color)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
for clip in &context.clips {
|
||||
let clip: Vec<[f32; 2]> = clip
|
||||
.iter()
|
||||
.map(|point| [point[0] as f32, point[1] as f32])
|
||||
.collect();
|
||||
let clipped = pick::xclip::clip_hatch_boundary(
|
||||
&model.boundary,
|
||||
model.world_origin,
|
||||
&clip,
|
||||
);
|
||||
if clipped.is_empty() {
|
||||
host,
|
||||
self.annotation_scale,
|
||||
)
|
||||
.into_iter()
|
||||
.filter(|block_use| block_use.active)
|
||||
{
|
||||
if !render_graph::block_contains_hatch(
|
||||
&self.document,
|
||||
&block_use.insert.block_name,
|
||||
&mut hatch_memo,
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
graph.walk_insert(
|
||||
&block_use.insert,
|
||||
common.handle,
|
||||
|_, _| true,
|
||||
|entity, context| {
|
||||
let EntityType::Hatch(source) = entity else {
|
||||
return;
|
||||
};
|
||||
let mut placed = EntityType::Hatch(source.clone());
|
||||
placed.apply_transform(&context.transform);
|
||||
let EntityType::Hatch(hatch) = placed else {
|
||||
return;
|
||||
};
|
||||
let color = crate::scene::view::render::adapt_to_bg(
|
||||
context.style_for(&self.document, entity).0,
|
||||
self.current_bg(),
|
||||
);
|
||||
let Some(mut model) = Self::hatch_model_from_dxf(&hatch, color)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
for clip in &context.clips {
|
||||
let clip: Vec<[f32; 2]> = clip
|
||||
.iter()
|
||||
.map(|point| [point[0] as f32, point[1] as f32])
|
||||
.collect();
|
||||
let clipped = pick::xclip::clip_hatch_boundary(
|
||||
&model.boundary,
|
||||
model.world_origin,
|
||||
&clip,
|
||||
);
|
||||
if clipped.is_empty() {
|
||||
return;
|
||||
}
|
||||
model.boundary = Arc::new(clipped);
|
||||
}
|
||||
model.boundary = Arc::new(clipped);
|
||||
}
|
||||
out.entry(ins.common.handle).or_default().push(model);
|
||||
},
|
||||
);
|
||||
out.entry(common.handle).or_default().push(model);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
let arc = Arc::new(out);
|
||||
*self.insert_hatch_cache.borrow_mut() =
|
||||
@@ -8872,11 +8886,7 @@ impl Scene {
|
||||
let mut roots: HashMap<String, HashSet<Handle>> = HashMap::default();
|
||||
let mut parents: HashMap<String, HashSet<String>> = HashMap::default();
|
||||
for entity in self.document.entities() {
|
||||
let EntityType::Insert(insert) = entity else {
|
||||
continue;
|
||||
};
|
||||
let target = normalize_name(&insert.block_name);
|
||||
let common = &insert.common;
|
||||
let common = entity.common();
|
||||
let owner = if common.owner_handle.is_null() {
|
||||
membership
|
||||
.get(&common.handle)
|
||||
@@ -8885,13 +8895,19 @@ impl Scene {
|
||||
} else {
|
||||
common.owner_handle
|
||||
};
|
||||
if owner.is_null() || layout_blocks.contains(&owner) {
|
||||
roots.entry(target).or_default().insert(common.handle);
|
||||
} else if let Some(parent) = block_names.get(&owner) {
|
||||
parents.entry(target).or_default().insert(parent.clone());
|
||||
for block_use in render_graph::entity_block_uses(&self.document, entity, 1.0) {
|
||||
let target = normalize_name(&block_use.insert.block_name);
|
||||
if target.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if owner.is_null() || layout_blocks.contains(&owner) {
|
||||
roots.entry(target).or_default().insert(common.handle);
|
||||
} else if let Some(parent) = block_names.get(&owner) {
|
||||
parents.entry(target).or_default().insert(parent.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
// Propagate top-level INSERT users through nested block references.
|
||||
// Propagate root users through nested block references.
|
||||
// Fixed-point form is cycle-safe and block graphs are normally shallow.
|
||||
let mut changed = true;
|
||||
while changed {
|
||||
|
||||
+424
-103
@@ -259,6 +259,7 @@ pub struct RenderSceneGraph<'a> {
|
||||
document: &'a CadDocument,
|
||||
frozen_layers: Option<&'a FxHashSet<Handle>>,
|
||||
annotation_scale_handle: Option<Handle>,
|
||||
annotation_scale: f32,
|
||||
all_visible: bool,
|
||||
depths: &'a FxHashMap<u64, [f32; 2]>,
|
||||
viewport: Option<Handle>,
|
||||
@@ -276,6 +277,7 @@ impl<'a> RenderSceneGraph<'a> {
|
||||
document,
|
||||
frozen_layers,
|
||||
annotation_scale_handle,
|
||||
annotation_scale: 1.0,
|
||||
all_visible,
|
||||
depths,
|
||||
viewport: None,
|
||||
@@ -287,6 +289,11 @@ impl<'a> RenderSceneGraph<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_annotation_scale(mut self, annotation_scale: f32) -> Self {
|
||||
self.annotation_scale = annotation_scale.max(1.0e-6);
|
||||
self
|
||||
}
|
||||
|
||||
/// Walk direct root entities and every referenced block subtree. `visible`
|
||||
/// can add session-only rules such as isolate/preview hiding; returning
|
||||
/// false for an Insert removes its whole subtree.
|
||||
@@ -397,7 +404,12 @@ impl<'a> RenderSceneGraph<'a> {
|
||||
};
|
||||
|
||||
for offset in array_offsets(insert) {
|
||||
let local = insert_instance_transform(self.document, insert, offset);
|
||||
let local = insert_instance_transform(
|
||||
self.document,
|
||||
insert,
|
||||
offset,
|
||||
self.annotation_scale,
|
||||
);
|
||||
let transform = local.then(&parent.transform);
|
||||
let mut context = parent.clone();
|
||||
context.transform = transform;
|
||||
@@ -449,7 +461,7 @@ impl<'a> RenderSceneGraph<'a> {
|
||||
if context.nesting_depth > 32 {
|
||||
return;
|
||||
}
|
||||
let Some(record) = self.document.block_records.get(block_name) else {
|
||||
let Some(record) = block_record_by_name(self.document, block_name) else {
|
||||
return;
|
||||
};
|
||||
for &handle in &record.entity_handles {
|
||||
@@ -499,16 +511,20 @@ impl<'a> RenderSceneGraph<'a> {
|
||||
V: FnMut(&EntityType, &RenderContext) -> bool,
|
||||
F: FnMut(&EntityType, &RenderContext),
|
||||
{
|
||||
let Some(owned) = owned_block_insert(self.document, entity) else {
|
||||
return;
|
||||
};
|
||||
if stack
|
||||
.iter()
|
||||
.any(|name| name.eq_ignore_ascii_case(&owned.insert.block_name))
|
||||
for block_use in entity_render_block_uses(self.document, entity, self.annotation_scale)
|
||||
.into_iter()
|
||||
.filter(|block_use| block_use.active)
|
||||
{
|
||||
return;
|
||||
if stack
|
||||
.iter()
|
||||
.any(|name| name.eq_ignore_ascii_case(&block_use.insert.block_name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
stack.push(block_use.insert.block_name.clone());
|
||||
self.walk_insert_instances(&block_use.insert, context, visible, leaf);
|
||||
stack.pop();
|
||||
}
|
||||
self.walk_insert_instances(&owned.insert, context, visible, leaf);
|
||||
}
|
||||
|
||||
fn document_visible(&self, entity: &EntityType) -> bool {
|
||||
@@ -540,11 +556,16 @@ impl<'a> RenderSceneGraph<'a> {
|
||||
pub fn block_base_point(document: &CadDocument, block_name: &str) -> Vector3 {
|
||||
document
|
||||
.block_records
|
||||
.get(block_name)
|
||||
.and_then(|record| document.get_entity(record.block_entity_handle))
|
||||
.and_then(|entity| match entity {
|
||||
EntityType::Block(block) => Some(block.base_point),
|
||||
_ => None,
|
||||
.iter()
|
||||
.find(|record| record.name.eq_ignore_ascii_case(block_name))
|
||||
.map(|record| {
|
||||
document
|
||||
.get_entity(record.block_entity_handle)
|
||||
.and_then(|entity| match entity {
|
||||
EntityType::Block(block) => Some(block.base_point),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(record.base_point)
|
||||
})
|
||||
.unwrap_or(Vector3::ZERO)
|
||||
}
|
||||
@@ -555,64 +576,388 @@ pub fn insert_transform(document: &CadDocument, insert: &Insert) -> Transform {
|
||||
.then(&insert.get_transform())
|
||||
}
|
||||
|
||||
pub struct OwnedBlockInsert {
|
||||
pub insert: Insert,
|
||||
pub suppress_root_points: bool,
|
||||
pub render_picture: bool,
|
||||
pub fn insert_transform_at_scale(
|
||||
document: &CadDocument,
|
||||
insert: &Insert,
|
||||
annotation_scale: f32,
|
||||
) -> Transform {
|
||||
let mut transform = insert_transform(document, insert);
|
||||
if (annotation_scale - 1.0).abs() > 1.0e-6
|
||||
&& insert
|
||||
.common
|
||||
.extended_data
|
||||
.get_record("AcAnnotativeData")
|
||||
.is_some()
|
||||
{
|
||||
let point = insert.insert_point;
|
||||
let scale = Transform::from_translation(Vector3::new(-point.x, -point.y, -point.z))
|
||||
.then(&Transform::from_scale(annotation_scale as f64))
|
||||
.then(&Transform::from_translation(Vector3::new(
|
||||
point.x, point.y, point.z,
|
||||
)));
|
||||
transform = transform.then(&scale);
|
||||
}
|
||||
transform
|
||||
}
|
||||
|
||||
pub fn owned_block_insert(
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum BlockRole {
|
||||
Insert,
|
||||
DimensionPicture,
|
||||
TablePicture,
|
||||
TableCell,
|
||||
MultiLeaderContent,
|
||||
LineStyleSymbol,
|
||||
ArrowHead,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BlockUse {
|
||||
pub block: Handle,
|
||||
pub insert: Insert,
|
||||
pub role: BlockRole,
|
||||
/// Whether this edge belongs to the active render representation. Inactive
|
||||
/// edges remain dependencies so cache invalidation and purge stay complete.
|
||||
pub active: bool,
|
||||
pub replaces_host_wire: bool,
|
||||
pub suppress_root_points: bool,
|
||||
}
|
||||
|
||||
pub(crate) fn block_record_by_name<'a>(
|
||||
document: &'a CadDocument,
|
||||
name: &str,
|
||||
) -> Option<&'a acadrust::tables::BlockRecord> {
|
||||
document.block_records.get(name).or_else(|| {
|
||||
document
|
||||
.block_records
|
||||
.iter()
|
||||
.find(|record| record.name.eq_ignore_ascii_case(name))
|
||||
})
|
||||
}
|
||||
|
||||
fn block_use(
|
||||
document: &CadDocument,
|
||||
mut insert: Insert,
|
||||
role: BlockRole,
|
||||
active: bool,
|
||||
replaces_host_wire: bool,
|
||||
suppress_root_points: bool,
|
||||
) -> BlockUse {
|
||||
let block =
|
||||
block_record_by_name(document, &insert.block_name).map_or(Handle::NULL, |record| {
|
||||
insert.block_name.clone_from(&record.name);
|
||||
record.handle
|
||||
});
|
||||
BlockUse {
|
||||
block,
|
||||
insert,
|
||||
role,
|
||||
active,
|
||||
replaces_host_wire,
|
||||
suppress_root_points,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn block_use_from_handle(
|
||||
document: &CadDocument,
|
||||
block: Handle,
|
||||
role: BlockRole,
|
||||
insertion: Vector3,
|
||||
) -> Option<BlockUse> {
|
||||
let record = document
|
||||
.block_records
|
||||
.iter()
|
||||
.find(|record| record.handle == block)?;
|
||||
Some(block_use(
|
||||
document,
|
||||
Insert::new(record.name.clone(), insertion),
|
||||
role,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
))
|
||||
}
|
||||
|
||||
fn push_dependency_use(
|
||||
uses: &mut Vec<BlockUse>,
|
||||
document: &CadDocument,
|
||||
block: Handle,
|
||||
role: BlockRole,
|
||||
) {
|
||||
if block.is_null()
|
||||
|| uses
|
||||
.iter()
|
||||
.any(|block_use| block_use.block == block && block_use.role == role)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if let Some(mut block_use) =
|
||||
block_use_from_handle(document, block, role, Vector3::ZERO)
|
||||
{
|
||||
block_use.active = false;
|
||||
uses.push(block_use);
|
||||
}
|
||||
}
|
||||
|
||||
/// Every direct block edge owned by an entity. One host may have multiple
|
||||
/// edges, such as block-valued table cells. Representation flags let renderers
|
||||
/// choose the active picture while dependency consumers keep the full graph.
|
||||
fn entity_owned_block_uses(
|
||||
document: &CadDocument,
|
||||
entity: &EntityType,
|
||||
) -> Option<OwnedBlockInsert> {
|
||||
let (insert, suppress_root_points, render_picture) = match entity {
|
||||
annotation_scale: f32,
|
||||
) -> Vec<BlockUse> {
|
||||
match entity {
|
||||
EntityType::Insert(insert) => vec![block_use(
|
||||
document,
|
||||
insert.clone(),
|
||||
BlockRole::Insert,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
)],
|
||||
EntityType::Dimension(dimension) => {
|
||||
let name = dimension.base().block_name.trim();
|
||||
let record = document
|
||||
.block_records
|
||||
.iter()
|
||||
.find(|record| record.name.eq_ignore_ascii_case(name))?;
|
||||
if record.entity_handles.is_empty() {
|
||||
return None;
|
||||
if let Some(record) = block_record_by_name(document, name)
|
||||
.filter(|record| !record.entity_handles.is_empty())
|
||||
{
|
||||
let mut insert = Insert::new(record.name.clone(), Vector3::ZERO);
|
||||
insert.common = dimension.base().common.clone();
|
||||
let picture = !crate::scene::annotative::is_annotative(document, entity);
|
||||
vec![block_use(
|
||||
document,
|
||||
insert,
|
||||
BlockRole::DimensionPicture,
|
||||
picture,
|
||||
picture,
|
||||
true,
|
||||
)]
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
let mut insert = Insert::new(record.name.clone(), Vector3::ZERO);
|
||||
insert.common = dimension.base().common.clone();
|
||||
(
|
||||
insert,
|
||||
true,
|
||||
!crate::scene::annotative::is_annotative(document, entity),
|
||||
)
|
||||
}
|
||||
EntityType::Table(table) => {
|
||||
let record = table.block_record_handle.and_then(|handle| {
|
||||
let picture_record = table.block_record_handle.and_then(|handle| {
|
||||
document
|
||||
.block_records
|
||||
.iter()
|
||||
.find(|record| record.handle == handle)
|
||||
})?;
|
||||
if record.entity_handles.is_empty() {
|
||||
return None;
|
||||
.filter(|record| !record.entity_handles.is_empty())
|
||||
});
|
||||
let mut uses = Vec::new();
|
||||
if let Some(record) = picture_record {
|
||||
let mut insert = Insert::new(record.name.clone(), table.insertion_point);
|
||||
insert.rotation = table
|
||||
.horizontal_direction
|
||||
.y
|
||||
.atan2(table.horizontal_direction.x);
|
||||
insert.common = table.common.clone();
|
||||
uses.push(block_use(
|
||||
document,
|
||||
insert,
|
||||
BlockRole::TablePicture,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
));
|
||||
}
|
||||
let mut insert = Insert::new(record.name.clone(), table.insertion_point);
|
||||
insert.rotation = table
|
||||
.horizontal_direction
|
||||
.y
|
||||
.atan2(table.horizontal_direction.x);
|
||||
insert.common = table.common.clone();
|
||||
(insert, false, true)
|
||||
let cells_active = picture_record.is_none();
|
||||
uses.extend(
|
||||
crate::entities::table::block_cell_inserts(table, document, annotation_scale)
|
||||
.into_iter()
|
||||
.map(|mut insert| {
|
||||
insert
|
||||
.common
|
||||
.extended_data
|
||||
.remove_record("AcAnnotativeData");
|
||||
block_use(
|
||||
document,
|
||||
insert,
|
||||
BlockRole::TableCell,
|
||||
cells_active,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
}),
|
||||
);
|
||||
uses
|
||||
}
|
||||
EntityType::MultiLeader(multileader) => (
|
||||
crate::entities::multileader::block_content_insert(document, multileader)?,
|
||||
false,
|
||||
true,
|
||||
),
|
||||
_ => return None,
|
||||
};
|
||||
Some(OwnedBlockInsert {
|
||||
insert,
|
||||
suppress_root_points,
|
||||
render_picture,
|
||||
})
|
||||
EntityType::MultiLeader(multileader) => {
|
||||
crate::entities::multileader::block_content_insert(document, multileader)
|
||||
.map(|mut insert| {
|
||||
insert
|
||||
.common
|
||||
.extended_data
|
||||
.remove_record("AcAnnotativeData");
|
||||
vec![block_use(
|
||||
document,
|
||||
insert,
|
||||
BlockRole::MultiLeaderContent,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
)]
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn entity_render_block_uses(
|
||||
document: &CadDocument,
|
||||
entity: &EntityType,
|
||||
annotation_scale: f32,
|
||||
) -> Vec<BlockUse> {
|
||||
entity_owned_block_uses(document, entity, annotation_scale)
|
||||
}
|
||||
|
||||
pub fn entity_block_uses(
|
||||
document: &CadDocument,
|
||||
entity: &EntityType,
|
||||
annotation_scale: f32,
|
||||
) -> Vec<BlockUse> {
|
||||
let mut uses = entity_owned_block_uses(document, entity, annotation_scale);
|
||||
|
||||
let line_type = crate::scene::view::render::linetype_name_for(document, entity);
|
||||
for symbol in crate::scene::convert::dgn_linestyle::symbol_blocks(document, line_type) {
|
||||
push_dependency_use(
|
||||
&mut uses,
|
||||
document,
|
||||
symbol.block,
|
||||
BlockRole::LineStyleSymbol,
|
||||
);
|
||||
}
|
||||
|
||||
match entity {
|
||||
EntityType::Dimension(dimension) => {
|
||||
let style_name = dimension.base().style_name.trim();
|
||||
if let Some(style) = document.dim_styles.iter().find(|style| {
|
||||
style.name.eq_ignore_ascii_case(style_name)
|
||||
|| (style_name.is_empty() && style.name.eq_ignore_ascii_case("Standard"))
|
||||
}) {
|
||||
let style = crate::entities::dimension::resolved_dimension_style(
|
||||
style,
|
||||
dimension,
|
||||
document,
|
||||
);
|
||||
for handle in [style.dimblk, style.dimblk1, style.dimblk2, style.dimldrblk] {
|
||||
push_dependency_use(
|
||||
&mut uses,
|
||||
document,
|
||||
handle,
|
||||
BlockRole::ArrowHead,
|
||||
);
|
||||
}
|
||||
for name in [style.dimblk_name, style.dimblk1_name, style.dimblk2_name] {
|
||||
if let Some(record) = block_record_by_name(document, &name) {
|
||||
push_dependency_use(
|
||||
&mut uses,
|
||||
document,
|
||||
record.handle,
|
||||
BlockRole::ArrowHead,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EntityType::Leader(leader) => {
|
||||
let style = document.dim_styles.iter().find(|style| {
|
||||
style.name.eq_ignore_ascii_case(&leader.dimension_style)
|
||||
|| (leader.dimension_style.trim().is_empty()
|
||||
&& style.name.eq_ignore_ascii_case("Standard"))
|
||||
});
|
||||
let handle = crate::entities::dim_override::handle(
|
||||
&leader.common.extended_data,
|
||||
crate::entities::dim_override::DIMLDRBLK,
|
||||
)
|
||||
.or_else(|| style.map(|style| style.dimldrblk));
|
||||
if let Some(handle) = handle {
|
||||
push_dependency_use(
|
||||
&mut uses,
|
||||
document,
|
||||
handle,
|
||||
BlockRole::ArrowHead,
|
||||
);
|
||||
}
|
||||
}
|
||||
EntityType::MultiLeader(multileader) => {
|
||||
if let Some(handle) = multileader.arrowhead_handle {
|
||||
push_dependency_use(
|
||||
&mut uses,
|
||||
document,
|
||||
handle,
|
||||
BlockRole::ArrowHead,
|
||||
);
|
||||
}
|
||||
for line in multileader
|
||||
.context
|
||||
.leader_roots
|
||||
.iter()
|
||||
.flat_map(|root| &root.lines)
|
||||
{
|
||||
if let Some(handle) = line.arrowhead_handle {
|
||||
push_dependency_use(
|
||||
&mut uses,
|
||||
document,
|
||||
handle,
|
||||
BlockRole::ArrowHead,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
uses
|
||||
}
|
||||
|
||||
/// Blocks referenced by document-level styles rather than a concrete entity.
|
||||
pub fn document_block_uses(document: &CadDocument) -> Vec<BlockUse> {
|
||||
let mut uses = Vec::new();
|
||||
for style in document.dim_styles.iter() {
|
||||
for handle in [style.dimblk, style.dimblk1, style.dimblk2, style.dimldrblk] {
|
||||
push_dependency_use(&mut uses, document, handle, BlockRole::ArrowHead);
|
||||
}
|
||||
for name in [&style.dimblk_name, &style.dimblk1_name, &style.dimblk2_name] {
|
||||
if let Some(record) = block_record_by_name(document, name) {
|
||||
push_dependency_use(
|
||||
&mut uses,
|
||||
document,
|
||||
record.handle,
|
||||
BlockRole::ArrowHead,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
for object in document.objects.values() {
|
||||
let acadrust::objects::ObjectType::MultiLeaderStyle(style) = object else {
|
||||
continue;
|
||||
};
|
||||
if let Some(handle) = style.arrowhead_handle {
|
||||
push_dependency_use(&mut uses, document, handle, BlockRole::ArrowHead);
|
||||
}
|
||||
if let Some(handle) = style.block_content_handle {
|
||||
push_dependency_use(
|
||||
&mut uses,
|
||||
document,
|
||||
handle,
|
||||
BlockRole::MultiLeaderContent,
|
||||
);
|
||||
}
|
||||
}
|
||||
for line_type in document.line_types.iter() {
|
||||
for symbol in
|
||||
crate::scene::convert::dgn_linestyle::symbol_blocks(document, &line_type.name)
|
||||
{
|
||||
push_dependency_use(
|
||||
&mut uses,
|
||||
document,
|
||||
symbol.block,
|
||||
BlockRole::LineStyleSymbol,
|
||||
);
|
||||
}
|
||||
}
|
||||
uses
|
||||
}
|
||||
|
||||
pub fn array_offsets(insert: &Insert) -> Vec<[f64; 3]> {
|
||||
@@ -636,8 +981,9 @@ pub fn insert_instance_transform(
|
||||
document: &CadDocument,
|
||||
insert: &Insert,
|
||||
offset: [f64; 3],
|
||||
annotation_scale: f32,
|
||||
) -> Transform {
|
||||
let transform = insert_transform(document, insert);
|
||||
let transform = insert_transform_at_scale(document, insert, annotation_scale);
|
||||
if offset == [0.0; 3] {
|
||||
transform
|
||||
} else {
|
||||
@@ -651,51 +997,26 @@ pub fn block_contains_hatch(
|
||||
block_name: &str,
|
||||
memo: &mut std::collections::HashMap<String, bool>,
|
||||
) -> bool {
|
||||
if let Some(&contains) = memo.get(block_name) {
|
||||
let key = block_name.to_ascii_lowercase();
|
||||
if let Some(&contains) = memo.get(&key) {
|
||||
return contains;
|
||||
}
|
||||
memo.insert(block_name.to_string(), false);
|
||||
let contains = document
|
||||
.block_records
|
||||
.get(block_name)
|
||||
.is_some_and(|record| {
|
||||
record
|
||||
.entity_handles
|
||||
.iter()
|
||||
.any(|&handle| match document.get_entity(handle) {
|
||||
Some(EntityType::Hatch(_)) => true,
|
||||
Some(EntityType::Insert(insert)) => {
|
||||
block_contains_hatch(document, &insert.block_name, memo)
|
||||
}
|
||||
Some(EntityType::Dimension(dimension)) => {
|
||||
let name = dimension.base().block_name.trim();
|
||||
!name.is_empty() && block_contains_hatch(document, name, memo)
|
||||
}
|
||||
Some(EntityType::Table(table)) => table
|
||||
.block_record_handle
|
||||
.and_then(|handle| {
|
||||
document
|
||||
.block_records
|
||||
.iter()
|
||||
.find(|record| record.handle == handle)
|
||||
})
|
||||
.is_some_and(|record| {
|
||||
block_contains_hatch(document, &record.name, memo)
|
||||
}),
|
||||
Some(EntityType::MultiLeader(multileader)) => multileader
|
||||
.block_content_handle
|
||||
.and_then(|handle| {
|
||||
document
|
||||
.block_records
|
||||
.iter()
|
||||
.find(|record| record.handle == handle)
|
||||
})
|
||||
.is_some_and(|record| {
|
||||
block_contains_hatch(document, &record.name, memo)
|
||||
}),
|
||||
_ => false,
|
||||
})
|
||||
});
|
||||
memo.insert(block_name.to_string(), contains);
|
||||
memo.insert(key.clone(), false);
|
||||
let contains = block_record_by_name(document, block_name).is_some_and(|record| {
|
||||
record
|
||||
.entity_handles
|
||||
.iter()
|
||||
.any(|&handle| match document.get_entity(handle) {
|
||||
Some(EntityType::Hatch(_)) => true,
|
||||
Some(entity) => entity_render_block_uses(document, entity, 1.0)
|
||||
.into_iter()
|
||||
.filter(|block_use| block_use.active)
|
||||
.any(|block_use| {
|
||||
block_contains_hatch(document, &block_use.insert.block_name, memo)
|
||||
}),
|
||||
_ => false,
|
||||
})
|
||||
});
|
||||
memo.insert(key, contains);
|
||||
contains
|
||||
}
|
||||
Reference in new issue
Block a user