forked from 1b-agent/opencadstudio-upstream
feat(xref+block): bind xref tables, fade rendered geometry, per-defn precision offset
Xref work:
- merge_xref_into_block now copies the xref's layer / linetype / nested
block_record tables into the host under `{xref}|{name}` prefixes and
remaps entity references, so ByLayer/ByBlock resolution lands on the
right entry instead of falling back to WHITE + 1 px.
- expand_insert (and the explode fallback) detect xref BlockRecords by
flag and apply `fade_toward_bg(color, bg, 0.5)` to every emitted final
color (selection bypasses the fade). ExpandCtx propagates `is_xref` +
`bg_color` through nested expansions.
Block precision groundwork:
- BlockDefn gains `local_offset: [f64; 3]` set in build_defn from the
AABB centre of the defn's direct entities. tessellate_sub_local now
passes that offset to tessellate(), so LocalWire.points / fill_tris /
aabb_local / text_obb_local are stored in offset-frame f32 and don't
lose precision for distant content.
- emit_wire / emit_greeked_text / transform_tangent take the defn offset
and re-add it in f64 before composing with accum_xform. defn_aabb_
recursive transforms nested children from their offset frame into the
parent's. transform_offset_aabb_xy replaces transform_aabb_xy.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
1 parent
ea87b3db76
commit
2f0220b1a0
3 files changed
+395
-69
No files matched your search
+128
-6
@@ -2,8 +2,10 @@
|
||||
// populate them with geometry from the referenced DWG/DXF files.
|
||||
|
||||
use acadrust::entities::{Block, BlockEnd};
|
||||
use acadrust::tables::TableEntry;
|
||||
use acadrust::types::{Handle, Vector3};
|
||||
use acadrust::{CadDocument, EntityType};
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Status of an external reference block.
|
||||
@@ -61,7 +63,7 @@ pub fn resolve_xrefs(doc: &mut CadDocument, base_dir: &Path) -> Vec<XrefInfo> {
|
||||
Err(_) => XrefStatus::NotFound,
|
||||
Ok(xref_doc) => {
|
||||
ensure_block_entities(doc, &block_name);
|
||||
merge_xref_into_block(doc, br_handle, xref_doc);
|
||||
merge_xref_into_block(doc, &block_name, br_handle, xref_doc);
|
||||
XrefStatus::Loaded
|
||||
}
|
||||
},
|
||||
@@ -130,9 +132,91 @@ fn ensure_block_entities(doc: &mut CadDocument, block_name: &str) {
|
||||
let _ = doc.add_entity(EntityType::BlockEnd(BlockEnd::new()));
|
||||
}
|
||||
|
||||
/// Copy model-space entities from `xref_doc` into the xref block (`br_handle`)
|
||||
/// of `doc`.
|
||||
fn merge_xref_into_block(doc: &mut CadDocument, br_handle: Handle, xref_doc: CadDocument) {
|
||||
/// Merge an external-reference document into `doc`'s xref block.
|
||||
///
|
||||
/// Copies the xref's model-space entities into the host xref block, AND
|
||||
/// (crucially for correct rendering) merges the xref's layer / linetype
|
||||
/// tables and any *nested* block records into the host doc under prefixed
|
||||
/// names ("{xref_name}|{symbol_name}"). Without this remapping, every
|
||||
/// xref entity using ByLayer resolves against the host doc's layer table
|
||||
/// — which doesn't know about the xref's layers — and silently falls
|
||||
/// back to WHITE / 1 px line weight. AutoCAD's BIND command uses the
|
||||
/// same naming scheme.
|
||||
fn merge_xref_into_block(
|
||||
doc: &mut CadDocument,
|
||||
xref_block_name: &str,
|
||||
br_handle: Handle,
|
||||
xref_doc: CadDocument,
|
||||
) {
|
||||
let prefix = xref_block_name;
|
||||
|
||||
// ── Layers ──────────────────────────────────────────────────────────
|
||||
// Prefix every xref layer (including "0"). Entity layer references are
|
||||
// remapped below so the resolver finds the merged copy. Host layers
|
||||
// (incl. its own "0") are untouched — no collisions.
|
||||
let mut layer_map: HashMap<String, String> = HashMap::new();
|
||||
for layer in xref_doc.layers.iter() {
|
||||
let old = layer.name.clone();
|
||||
let new = format!("{}|{}", prefix, old);
|
||||
let mut cloned = layer.clone();
|
||||
cloned.name = new.clone();
|
||||
cloned.set_handle(doc.allocate_handle());
|
||||
doc.layers.add_or_replace(cloned);
|
||||
layer_map.insert(old.to_uppercase(), new);
|
||||
}
|
||||
|
||||
// ── Linetypes ───────────────────────────────────────────────────────
|
||||
// Skip the three sentinel names — "ByLayer" / "ByBlock" / "Continuous"
|
||||
// are magic strings the resolver matches verbatim in both docs.
|
||||
let mut linetype_map: HashMap<String, String> = HashMap::new();
|
||||
for lt in xref_doc.line_types.iter() {
|
||||
let old = lt.name.clone();
|
||||
if is_sentinel_linetype(&old) {
|
||||
continue;
|
||||
}
|
||||
let new = format!("{}|{}", prefix, old);
|
||||
let mut cloned = lt.clone();
|
||||
cloned.name = new.clone();
|
||||
cloned.set_handle(doc.allocate_handle());
|
||||
doc.line_types.add_or_replace(cloned);
|
||||
linetype_map.insert(old.to_uppercase(), new);
|
||||
}
|
||||
|
||||
// ── Block records (nested blocks inside the xref) ───────────────────
|
||||
// First pass: create each prefixed BR with its host handle + empty
|
||||
// entity_handles. Second pass (in the entity loop below) populates
|
||||
// them by inserting entities with `owner_handle` set.
|
||||
//
|
||||
// Tracks (xref-doc BR handle → host BR handle) so entities owned by
|
||||
// a nested xref block can be routed to the right host block_record.
|
||||
let mut br_handle_map: HashMap<Handle, Handle> = HashMap::new();
|
||||
let mut block_name_map: HashMap<String, String> = HashMap::new();
|
||||
for br in xref_doc.block_records.iter() {
|
||||
// Skip layout block records (*Model_Space, *Paper_Space, *Paper_Space0…)
|
||||
// and any further-nested xrefs (we don't recurse into xref-of-xref).
|
||||
if br.name.starts_with('*') || br.flags.is_xref || br.flags.is_xref_overlay {
|
||||
continue;
|
||||
}
|
||||
let old = br.name.clone();
|
||||
let new = format!("{}|{}", prefix, old);
|
||||
let mut cloned = br.clone();
|
||||
cloned.name = new.clone();
|
||||
cloned.entity_handles.clear();
|
||||
cloned.insert_handles.clear();
|
||||
// Detach foreign layout pointer — it refers to a Layout in xref_doc
|
||||
// we don't import.
|
||||
cloned.layout = Handle::NULL;
|
||||
let new_h = doc.allocate_handle();
|
||||
cloned.set_handle(new_h);
|
||||
cloned.block_entity_handle = doc.allocate_handle();
|
||||
cloned.block_end_handle = doc.allocate_handle();
|
||||
br_handle_map.insert(br.handle, new_h);
|
||||
block_name_map.insert(old.to_uppercase(), new);
|
||||
doc.block_records.add_or_replace(cloned);
|
||||
}
|
||||
|
||||
// ── Entities ────────────────────────────────────────────────────────
|
||||
let xref_ms_handle = xref_doc.header.model_space_block_handle;
|
||||
let entities: Vec<EntityType> = xref_doc
|
||||
.entities()
|
||||
.filter(|e| !matches!(e, EntityType::Block(_) | EntityType::BlockEnd(_)))
|
||||
@@ -140,14 +224,52 @@ fn merge_xref_into_block(doc: &mut CadDocument, br_handle: Handle, xref_doc: Cad
|
||||
.collect();
|
||||
|
||||
for mut entity in entities {
|
||||
// Remap layer / linetype names so the host's resolver hits the
|
||||
// copies we just inserted.
|
||||
{
|
||||
let c = entity.common_mut();
|
||||
if let Some(new_layer) = layer_map.get(&c.layer.to_uppercase()) {
|
||||
c.layer = new_layer.clone();
|
||||
}
|
||||
if !is_sentinel_linetype(&c.linetype) {
|
||||
if let Some(new_lt) = linetype_map.get(&c.linetype.to_uppercase()) {
|
||||
c.linetype = new_lt.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// INSERTs reference blocks by name, not handle — remap.
|
||||
if let EntityType::Insert(ins) = &mut entity {
|
||||
if let Some(new_name) = block_name_map.get(&ins.block_name.to_uppercase()) {
|
||||
ins.block_name = new_name.clone();
|
||||
}
|
||||
}
|
||||
|
||||
// Route entity to the correct host block record. Entities owned by
|
||||
// the xref's model_space land in the host xref block (br_handle);
|
||||
// entities owned by one of the nested BRs land in its prefixed
|
||||
// counterpart. Paper-space / unknown owners are skipped.
|
||||
let old_owner = entity.common().owner_handle;
|
||||
let new_owner = if old_owner == xref_ms_handle {
|
||||
br_handle
|
||||
} else if let Some(&h) = br_handle_map.get(&old_owner) {
|
||||
h
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
entity.common_mut().owner_handle = new_owner;
|
||||
// Clear the foreign handle so acadrust assigns a new one.
|
||||
set_handle(&mut entity, Handle::NULL);
|
||||
// Route to the xref block record.
|
||||
entity.common_mut().owner_handle = br_handle;
|
||||
let _ = doc.add_entity(entity);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_sentinel_linetype(name: &str) -> bool {
|
||||
name.eq_ignore_ascii_case("ByLayer")
|
||||
|| name.eq_ignore_ascii_case("ByBlock")
|
||||
|| name.eq_ignore_ascii_case("Continuous")
|
||||
}
|
||||
|
||||
/// Set the handle field of any entity variant.
|
||||
fn set_handle(entity: &mut EntityType, h: Handle) {
|
||||
entity.common_mut().handle = h;
|
||||
|
||||
Reference in new issue
Block a user