forked from 1b-agent/opencadstudio-upstream
feat(render): draw DGN line-style symbols on linetypes
Linetypes converted from MicroStation DGN keep their pattern in DGN line-style objects (empty standard LTYPE), so entities using them drew as a plain line and lost the symbols (e.g. a pipe's end circles). Walk the line-style tree (resolved by name to its LSDefinition), collect the symbol components' anonymous blocks, and draw each at the host polyline's endpoints, scaled by the symbol's decoded scale (geometry / scale) so it matches the drawing (verified: a 0.75 ellipse renders at the file's intended 0.375 radius). Bumps acadrust to 8e18c6b (DGN line-style symbol-scale decode). First pass: the stroke dash pattern and exact per-vertex placement need more of the line-style leaf decoded; the base line still draws solid. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
7647fb427c
commit
1d6a04183e
4 files changed
+486
-288
No files matched your search
Generated
+299
-288
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,156 @@
|
||||
//! DGN line-style rendering (first pass).
|
||||
//!
|
||||
//! Linetypes converted from MicroStation DGN 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
|
||||
//! `objects/dgn_linestyle.rs` in acadrust and `~/Documents/OCS/DGN_LINESTYLE_PLAN.md`.
|
||||
//!
|
||||
//! The visible content is the **symbol components**, each of which references an
|
||||
//! anonymous block (e.g. a pipe's end circle). This renders those blocks at the
|
||||
//! host polyline's endpoints. The exact placement / scale / dash pattern live in
|
||||
//! the components' leaf data-stream fields, which are not decoded yet, so this is
|
||||
//! an approximation: symbols at native scale on the first and last vertices.
|
||||
|
||||
use acadrust::objects::DgnLsComponentType;
|
||||
use acadrust::types::{Handle, Transform, Vector3};
|
||||
use acadrust::{CadDocument, EntityType};
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::scene::model::wire_model::WireModel;
|
||||
|
||||
/// A symbol placement in a linetype's DGN line-style tree: the anonymous block
|
||||
/// to draw and the scale divisor to draw it at (`geometry / scale`).
|
||||
pub struct DgnSymbol {
|
||||
pub block: Handle,
|
||||
pub scale: f64,
|
||||
}
|
||||
|
||||
/// Symbol placements referenced by a linetype's DGN line-style tree, in tree
|
||||
/// order. Empty when the linetype is not a DGN line style.
|
||||
pub fn symbol_blocks(doc: &CadDocument, lt_name: &str) -> Vec<DgnSymbol> {
|
||||
let Some(def) = doc
|
||||
.dgn_ls_definitions
|
||||
.values()
|
||||
.find(|d| d.name.eq_ignore_ascii_case(lt_name))
|
||||
else {
|
||||
return Vec::new();
|
||||
};
|
||||
let mut out = Vec::new();
|
||||
let mut seen = HashSet::new();
|
||||
walk(doc, def.root_component, &mut out, &mut seen);
|
||||
out
|
||||
}
|
||||
|
||||
fn walk(doc: &CadDocument, h: Handle, out: &mut Vec<DgnSymbol>, seen: &mut HashSet<Handle>) {
|
||||
if !seen.insert(h) {
|
||||
return;
|
||||
}
|
||||
let Some(c) = doc.dgn_ls_components.get(&h) else {
|
||||
return;
|
||||
};
|
||||
match c.component_type {
|
||||
DgnLsComponentType::Compound | DgnLsComponentType::Point => {
|
||||
for r in &c.refs {
|
||||
let Some(sub) = doc.dgn_ls_components.get(r) else {
|
||||
continue;
|
||||
};
|
||||
if sub.component_type == DgnLsComponentType::Symbol {
|
||||
if let Some(block) = sub.symbol_block() {
|
||||
if !out.iter().any(|s| s.block == block) {
|
||||
out.push(DgnSymbol {
|
||||
block,
|
||||
scale: sub.scale,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
walk(doc, *r, out, seen);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Host entity's polyline vertices in WCS f64 (consecutive duplicates dropped).
|
||||
pub fn polyline_points(e: &EntityType) -> Vec<[f64; 3]> {
|
||||
let mut v: Vec<[f64; 3]> = match e {
|
||||
EntityType::LwPolyline(p) => p
|
||||
.vertices
|
||||
.iter()
|
||||
.map(|w| [w.location.x, w.location.y, 0.0])
|
||||
.collect(),
|
||||
EntityType::Polyline2D(p) => p
|
||||
.vertices
|
||||
.iter()
|
||||
.map(|w| [w.location.x, w.location.y, 0.0])
|
||||
.collect(),
|
||||
EntityType::Line(l) => vec![
|
||||
[l.start.x, l.start.y, l.start.z],
|
||||
[l.end.x, l.end.y, l.end.z],
|
||||
],
|
||||
_ => Vec::new(),
|
||||
};
|
||||
v.dedup();
|
||||
v
|
||||
}
|
||||
|
||||
/// Tessellate a symbol block's entities, translated so the block origin lands at
|
||||
/// `at`, in the host entity's colour. Reuses the normal entity tessellator on
|
||||
/// translated clones — the symbol geometry (ellipses, lines, …) renders exactly
|
||||
/// as it would anywhere else.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn place_block_wires(
|
||||
doc: &CadDocument,
|
||||
block: Handle,
|
||||
scale_divisor: f64,
|
||||
at: [f64; 3],
|
||||
color: [f32; 4],
|
||||
line_weight_px: f32,
|
||||
anno_scale: f32,
|
||||
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 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,
|
||||
world_per_pixel,
|
||||
bg_color,
|
||||
false,
|
||||
));
|
||||
}
|
||||
out
|
||||
}
|
||||
@@ -18,6 +18,7 @@ pub mod acis_to_truck {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub mod dgn_linestyle;
|
||||
pub mod truck_tess;
|
||||
pub mod tessellate;
|
||||
pub(crate) mod tess;
|
||||
|
||||
@@ -702,6 +702,36 @@ pub(crate) fn tessellate_entity(
|
||||
}
|
||||
}
|
||||
|
||||
// DGN line-style: the linetype's real pattern lives in DGN line-style objects
|
||||
// (empty standard LTYPE), so `resolve_complex_lt` sees nothing. Render its
|
||||
// symbol blocks (e.g. a pipe's end circles) at the polyline endpoints. First
|
||||
// pass — exact dash pattern / placement need the undecoded leaf data.
|
||||
let dgn_syms = convert::dgn_linestyle::symbol_blocks(document, lt_name);
|
||||
if !dgn_syms.is_empty() {
|
||||
let verts = convert::dgn_linestyle::polyline_points(e);
|
||||
if verts.len() >= 2 {
|
||||
let last = *verts.last().unwrap();
|
||||
for (i, sym) in dgn_syms.iter().enumerate() {
|
||||
let at = if i == 0 { verts[0] } else { last };
|
||||
let mut wires = convert::dgn_linestyle::place_block_wires(
|
||||
document,
|
||||
sym.block,
|
||||
sym.scale,
|
||||
at,
|
||||
entity_color,
|
||||
line_weight_px,
|
||||
anno_scale,
|
||||
world_per_pixel,
|
||||
bg_color,
|
||||
);
|
||||
for w in &mut wires {
|
||||
w.aabb = aabb;
|
||||
}
|
||||
bases.extend(wires);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bases
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user