feat(proxy): decode shell, transformed geometry and text previews
Extends the proxy-graphics decoder past the door/wall's absolute polylines so an ACAD_TABLE preview renders in full: - type 29 transform: a 4x4 local->world matrix now applies to every following primitive (identity for the door/wall, which are absolute). - type 9 shell: vertices + a face list -> each face as a closed boundary (the table's cell rectangles). - type 32: a normal-tagged polyline (grid lines). - type 38 text: position + direction (its length is the glyph height) + UTF-16 content and font, drawn as glyph strokes (simplex.shx and kin are single-stroke fonts, so the outline is the character). True-colour (22) and lineweight (23) traits now reach real geometry too. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
1d117f514c
commit
69354eca07
2 changed files with 359 additions and 91 deletions
|
|
@ -1,16 +1,14 @@
|
|||
//! Proxy entity graphics — the cached vector preview an application stores
|
||||
//! alongside a custom entity so viewers without its object enabler can still
|
||||
//! draw something. AutoCAD falls back to exactly this; so do we (e.g. an
|
||||
//! AutoCAD Architecture door/wall arrives as an `Unknown` entity we cannot
|
||||
//! interpret, but it ships this preview).
|
||||
//! AutoCAD Architecture door/wall, or an ACAD_TABLE, arrives as an `Unknown`
|
||||
//! entity we cannot interpret, but it ships this preview).
|
||||
//!
|
||||
//! LibreDWG and ACadSharp both keep the blob raw and never parse it, so there
|
||||
//! is no reference decoder to copy. The layout below was reverse-engineered
|
||||
//! from real previews (a Raster Design image frame, an ACA door and wall) and
|
||||
//! is deliberately conservative: it reads only the primitive records it has
|
||||
//! verified and treats every other record as an opaque trait to skip. Phase 1
|
||||
//! covers geometry (poly-lines, poly-gons and circular arcs); colour/lineweight
|
||||
//! traits are ignored, so everything renders in the entity's own colour.
|
||||
//! from real previews (a Raster Design image frame; an ACA door, wall; an
|
||||
//! ACAD_TABLE) and is deliberately conservative: it reads only the primitive
|
||||
//! records it has verified and treats every other record as an opaque trait.
|
||||
//!
|
||||
//! Blob grammar (all little-endian):
|
||||
//! ```text
|
||||
|
|
@ -23,32 +21,83 @@
|
|||
//! }
|
||||
//! ```
|
||||
//! Record types decoded here:
|
||||
//! * 6 poly-line / 7 poly-gon: `u32 point_count`, then `[f64;3] × point_count`
|
||||
//! (type 7 closes back to the first point).
|
||||
//! * 4 / 5 circular arc: `[f64;3] centre`, `f64 radius`, `[f64;3] normal`,
|
||||
//! `[f64;3] start_dir`, `f64 sweep` (radians), then a trailing flag.
|
||||
//! * geometry — 6 poly-line / 7 poly-gon (closed): `u32 n`, then `[f64;3]×n`;
|
||||
//! 4/5 circular arc (centre, radius, normal, start dir, sweep); 9 shell
|
||||
//! (`u32 n` verts, then a face list of `[u32 count, u32 idx…]`); 32 a
|
||||
//! normal-tagged poly-line (`u32 n`, `[f64;3]×n`, then a normal).
|
||||
//! * text — 38: position, normal, direction (its length is the glyph height),
|
||||
//! then a UTF-16 content string and a UTF-16 font (`*.shx`).
|
||||
//! * traits — 14 colour (plain ACI); 22 colour (encoded: 0xC2 RGB / 0xC3 ACI);
|
||||
//! 23 lineweight (0.01 mm); 29 a 4×4 transform whose local→world matrix
|
||||
//! applies to every following primitive (identity by default).
|
||||
|
||||
/// A poly-line lifted from a proxy-graphics blob, in world coordinates. Arcs
|
||||
/// and closed poly-gons are pre-flattened into points so callers only draw
|
||||
/// line strips.
|
||||
pub struct ProxyPolyline {
|
||||
pub points: Vec<[f64; 3]>,
|
||||
/// The colour in force when this primitive was emitted, as an AutoCAD
|
||||
/// Color Index. 256 = ByLayer / 0 = ByBlock (inherit the entity's colour);
|
||||
/// 1..=255 override it. Set from the preview's colour traits.
|
||||
pub color: i32,
|
||||
/// The colour a preview primitive draws in, from its colour traits.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum ProxyColor {
|
||||
/// ByLayer / ByBlock — inherit the entity's own colour.
|
||||
Inherit,
|
||||
/// A specific AutoCAD Color Index (1..=255).
|
||||
Aci(u8),
|
||||
/// A specific true colour.
|
||||
Rgb(u8, u8, u8),
|
||||
}
|
||||
|
||||
/// AutoCAD Color Index meaning "inherit from the layer" — the default until a
|
||||
/// colour trait says otherwise.
|
||||
const COLOR_BYLAYER: i32 = 256;
|
||||
/// A poly-line lifted from a proxy-graphics blob, in world coordinates. Arcs,
|
||||
/// closed poly-gons and shell faces are pre-flattened into points.
|
||||
pub struct ProxyPolyline {
|
||||
pub points: Vec<[f64; 3]>,
|
||||
/// Colour in force when this primitive was emitted.
|
||||
pub color: ProxyColor,
|
||||
/// Lineweight in force, 0.01 mm units; negative = inherit the entity's.
|
||||
pub lineweight: i16,
|
||||
}
|
||||
|
||||
/// A single-line text label from a proxy-graphics blob, in world coordinates.
|
||||
pub struct ProxyText {
|
||||
pub position: [f64; 3],
|
||||
pub height: f64,
|
||||
pub rotation: f64,
|
||||
pub text: String,
|
||||
pub font: String,
|
||||
pub color: ProxyColor,
|
||||
}
|
||||
|
||||
/// Everything a preview decodes to.
|
||||
#[derive(Default)]
|
||||
pub struct Decoded {
|
||||
pub polylines: Vec<ProxyPolyline>,
|
||||
pub texts: Vec<ProxyText>,
|
||||
}
|
||||
|
||||
const REC_ARC: u32 = 4;
|
||||
const REC_ARC5: u32 = 5;
|
||||
const REC_POLYLINE: u32 = 6;
|
||||
const REC_POLYGON: u32 = 7;
|
||||
/// Trait record that sets the current colour (ACI) for following primitives.
|
||||
const REC_SHELL: u32 = 9;
|
||||
/// Trait: current colour as a plain ACI (256 ByLayer / 0 ByBlock / 1..=255).
|
||||
const REC_COLOR: u32 = 14;
|
||||
/// Trait: current colour in encoded form (0xC2 true colour / 0xC3 indexed).
|
||||
const REC_COLOR_ENC: u32 = 22;
|
||||
/// Trait: current lineweight (0.01 mm; negative = ByLayer/ByBlock/Default).
|
||||
const REC_LINEWEIGHT: u32 = 23;
|
||||
/// Trait: a 4×4 local→world transform for the following primitives.
|
||||
const REC_TRANSFORM: u32 = 29;
|
||||
/// A normal-tagged poly-line (points then a trailing normal vector).
|
||||
const REC_LINE_N: u32 = 32;
|
||||
/// A text label (position/normal/direction, content string, font).
|
||||
const REC_TEXT: u32 = 38;
|
||||
|
||||
/// Row-major 3×4 local→world transform (the bottom row of the 4×4 is 0,0,0,1).
|
||||
type Xform = [f64; 12];
|
||||
const IDENTITY: Xform = [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0];
|
||||
|
||||
fn apply(m: &Xform, p: [f64; 3]) -> [f64; 3] {
|
||||
[
|
||||
m[0] * p[0] + m[1] * p[1] + m[2] * p[2] + m[3],
|
||||
m[4] * p[0] + m[5] * p[1] + m[6] * p[2] + m[7],
|
||||
m[8] * p[0] + m[9] * p[1] + m[10] * p[2] + m[11],
|
||||
]
|
||||
}
|
||||
|
||||
fn u32_at(b: &[u8], o: usize) -> Option<u32> {
|
||||
b.get(o..o + 4).map(|s| u32::from_le_bytes([s[0], s[1], s[2], s[3]]))
|
||||
|
|
@ -64,17 +113,33 @@ fn pt3_at(b: &[u8], o: usize) -> Option<[f64; 3]> {
|
|||
p.iter().all(|v| v.is_finite() && v.abs() < 1e12).then_some(p)
|
||||
}
|
||||
|
||||
/// Decode every geometry record in a proxy-graphics `blob` into world-space
|
||||
/// poly-lines. Returns an empty vec when the blob is absent, malformed, or
|
||||
/// carries no geometry this decoder models — never any invented shape.
|
||||
pub fn decode(blob: &[u8]) -> Vec<ProxyPolyline> {
|
||||
let mut out = Vec::new();
|
||||
fn color_from_aci(v: i32) -> ProxyColor {
|
||||
match v {
|
||||
1..=255 => ProxyColor::Aci(v as u8),
|
||||
_ => ProxyColor::Inherit,
|
||||
}
|
||||
}
|
||||
|
||||
fn color_from_encoded(v: u32) -> ProxyColor {
|
||||
match (v >> 24) & 0xFF {
|
||||
0xC2 => ProxyColor::Rgb(
|
||||
((v >> 16) & 0xFF) as u8,
|
||||
((v >> 8) & 0xFF) as u8,
|
||||
(v & 0xFF) as u8,
|
||||
),
|
||||
_ => color_from_aci((v & 0x00FF_FFFF) as i32),
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode every geometry / text record in a proxy-graphics `blob` into
|
||||
/// world-space primitives. Returns an empty result when the blob is absent,
|
||||
/// malformed, or carries nothing this decoder models — never invented shapes.
|
||||
pub fn decode(blob: &[u8]) -> Decoded {
|
||||
let mut out = Decoded::default();
|
||||
let total = match u32_at(blob, 0) {
|
||||
Some(t) => t as usize,
|
||||
None => return out,
|
||||
};
|
||||
// Trust the length field only when it fits; a mismatch means a layout this
|
||||
// decoder does not model, so bail rather than walk off the end.
|
||||
if total > blob.len() || total < 8 {
|
||||
return out;
|
||||
}
|
||||
|
|
@ -84,7 +149,10 @@ pub fn decode(blob: &[u8]) -> Vec<ProxyPolyline> {
|
|||
}
|
||||
|
||||
let mut pos = 8usize;
|
||||
let mut color = COLOR_BYLAYER;
|
||||
let mut color = ProxyColor::Inherit;
|
||||
let mut lineweight: i16 = -1;
|
||||
let mut xform = IDENTITY;
|
||||
|
||||
for _ in 0..count {
|
||||
let Some(rsize) = u32_at(blob, pos) else { break };
|
||||
let rsize = rsize as usize;
|
||||
|
|
@ -95,40 +163,51 @@ pub fn decode(blob: &[u8]) -> Vec<ProxyPolyline> {
|
|||
match rtype {
|
||||
REC_COLOR => {
|
||||
if let Some(c) = u32_at(blob, pos + 8) {
|
||||
color = c as i32;
|
||||
color = color_from_aci(c as i32);
|
||||
}
|
||||
}
|
||||
REC_POLYLINE | REC_POLYGON => {
|
||||
if let Some(n) = u32_at(blob, pos + 8) {
|
||||
let n = n as usize;
|
||||
if n >= 2 && 12 + n * 24 <= rsize {
|
||||
let mut pts = Vec::with_capacity(n + 1);
|
||||
let ok = (0..n).all(|i| match pt3_at(blob, pos + 12 + i * 24) {
|
||||
Some(p) => {
|
||||
pts.push(p);
|
||||
true
|
||||
}
|
||||
None => false,
|
||||
});
|
||||
if ok {
|
||||
// A poly-gon is a closed loop.
|
||||
if rtype == REC_POLYGON {
|
||||
if let Some(&first) = pts.first() {
|
||||
pts.push(first);
|
||||
}
|
||||
}
|
||||
out.push(ProxyPolyline { points: pts, color });
|
||||
}
|
||||
}
|
||||
REC_COLOR_ENC => {
|
||||
if let Some(c) = u32_at(blob, pos + 8) {
|
||||
color = color_from_encoded(c);
|
||||
}
|
||||
}
|
||||
REC_LINEWEIGHT => {
|
||||
if let Some(w) = u32_at(blob, pos + 8) {
|
||||
lineweight = w as i32 as i16;
|
||||
}
|
||||
}
|
||||
REC_TRANSFORM => {
|
||||
// 4×4 row-major; keep the top 3 rows (local→world).
|
||||
let mut m = IDENTITY;
|
||||
if (0..12).all(|k| {
|
||||
f64_at(blob, pos + 8 + 8 * k).map(|v| m[k] = v).is_some()
|
||||
}) {
|
||||
xform = m;
|
||||
}
|
||||
}
|
||||
REC_POLYLINE | REC_POLYGON | REC_LINE_N => {
|
||||
if let Some(pts) = decode_points(blob, pos, rsize, rtype == REC_POLYGON) {
|
||||
push_line(&mut out, pts, color, lineweight, &xform);
|
||||
}
|
||||
}
|
||||
REC_SHELL => {
|
||||
for face in decode_shell(blob, pos, rsize) {
|
||||
push_line(&mut out, face, color, lineweight, &xform);
|
||||
}
|
||||
}
|
||||
REC_ARC | REC_ARC5 => {
|
||||
if let Some(points) = decode_arc(blob, pos) {
|
||||
out.push(ProxyPolyline { points, color });
|
||||
if let Some(pts) = decode_arc(blob, pos) {
|
||||
push_line(&mut out, pts, color, lineweight, &xform);
|
||||
}
|
||||
}
|
||||
// Every other record is a trait (layer, lineweight, …) — skipped
|
||||
// for now; `rsize` still advances us past it.
|
||||
REC_TEXT => {
|
||||
if let Some(mut t) = decode_text(blob, pos, rsize) {
|
||||
t.position = apply(&xform, t.position);
|
||||
t.color = color;
|
||||
out.texts.push(t);
|
||||
}
|
||||
}
|
||||
// Any other record is an unmodelled trait; `rsize` skips it.
|
||||
_ => {}
|
||||
}
|
||||
pos += rsize;
|
||||
|
|
@ -136,9 +215,87 @@ pub fn decode(blob: &[u8]) -> Vec<ProxyPolyline> {
|
|||
out
|
||||
}
|
||||
|
||||
/// Flatten a circular-arc record (centre, radius, normal, start direction,
|
||||
/// sweep) into a strip of points. The normal's Z sign gives the sweep
|
||||
/// direction.
|
||||
/// Transform a run of local points to world and push it as one poly-line.
|
||||
fn push_line(out: &mut Decoded, local: Vec<[f64; 3]>, color: ProxyColor, lineweight: i16, xform: &Xform) {
|
||||
if local.len() >= 2 {
|
||||
out.polylines.push(ProxyPolyline {
|
||||
points: local.iter().map(|&p| apply(xform, p)).collect(),
|
||||
color,
|
||||
lineweight,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Read `[u32 n, [f64;3]×n]` at a record; close it if `closed`.
|
||||
fn decode_points(blob: &[u8], pos: usize, rsize: usize, closed: bool) -> Option<Vec<[f64; 3]>> {
|
||||
let n = u32_at(blob, pos + 8)? as usize;
|
||||
if n < 2 || 12 + n * 24 > rsize {
|
||||
return None;
|
||||
}
|
||||
let mut pts = Vec::with_capacity(n + 1);
|
||||
for i in 0..n {
|
||||
pts.push(pt3_at(blob, pos + 12 + i * 24)?);
|
||||
}
|
||||
if closed {
|
||||
if let Some(&first) = pts.first() {
|
||||
pts.push(first);
|
||||
}
|
||||
}
|
||||
Some(pts)
|
||||
}
|
||||
|
||||
/// Read a shell record — `n` verts then a face list of `[count, idx…]` — and
|
||||
/// return each face as a closed boundary poly-line.
|
||||
fn decode_shell(blob: &[u8], pos: usize, rsize: usize) -> Vec<Vec<[f64; 3]>> {
|
||||
let mut faces = Vec::new();
|
||||
let Some(n) = u32_at(blob, pos + 8) else {
|
||||
return faces;
|
||||
};
|
||||
let n = n as usize;
|
||||
let vbase = pos + 12;
|
||||
if n < 2 || vbase + n * 24 > pos + rsize {
|
||||
return faces;
|
||||
}
|
||||
let verts: Vec<[f64; 3]> = match (0..n).map(|i| pt3_at(blob, vbase + i * 24)).collect() {
|
||||
Some(v) => v,
|
||||
None => return faces,
|
||||
};
|
||||
// Face list: [list_len, then (count, idx×count)…]. Walk indices, tolerating
|
||||
// the trailing edge/visibility data by stopping at the first bad count.
|
||||
let mut fp = vbase + n * 24;
|
||||
let end = pos + rsize;
|
||||
let _list_len = u32_at(blob, fp); // total face-list longs (unused)
|
||||
fp += 4;
|
||||
while fp + 4 <= end {
|
||||
let Some(fc) = u32_at(blob, fp) else { break };
|
||||
let fc = fc as usize;
|
||||
if fc < 2 || fc > n || fp + 4 + fc * 4 > end {
|
||||
break;
|
||||
}
|
||||
let mut loop_pts = Vec::with_capacity(fc + 1);
|
||||
let mut ok = true;
|
||||
for k in 0..fc {
|
||||
match u32_at(blob, fp + 4 + k * 4) {
|
||||
Some(idx) if (idx as usize) < n => loop_pts.push(verts[idx as usize]),
|
||||
_ => {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
break;
|
||||
}
|
||||
if let Some(&first) = loop_pts.first() {
|
||||
loop_pts.push(first);
|
||||
}
|
||||
faces.push(loop_pts);
|
||||
fp += 4 + fc * 4;
|
||||
}
|
||||
faces
|
||||
}
|
||||
|
||||
/// Flatten a circular-arc record into a strip of points (local coords).
|
||||
fn decode_arc(blob: &[u8], pos: usize) -> Option<Vec<[f64; 3]>> {
|
||||
let center = pt3_at(blob, pos + 8)?;
|
||||
let radius = f64_at(blob, pos + 32)?;
|
||||
|
|
@ -150,7 +307,6 @@ fn decode_arc(blob: &[u8], pos: usize) -> Option<Vec<[f64; 3]>> {
|
|||
}
|
||||
let start = start_dir[1].atan2(start_dir[0]);
|
||||
let dir = if normal[2] < 0.0 { -1.0 } else { 1.0 };
|
||||
// Segment the sweep — ~64 per full turn, at least 2.
|
||||
let segs = ((sweep.abs() / std::f64::consts::TAU * 64.0).ceil() as usize).clamp(2, 512);
|
||||
let mut points = Vec::with_capacity(segs + 1);
|
||||
for i in 0..=segs {
|
||||
|
|
@ -164,6 +320,60 @@ fn decode_arc(blob: &[u8], pos: usize) -> Option<Vec<[f64; 3]>> {
|
|||
Some(points)
|
||||
}
|
||||
|
||||
/// Decode a text record: position + direction (its length is the height) + a
|
||||
/// content string and font, all in local space (caller applies the transform).
|
||||
fn decode_text(blob: &[u8], pos: usize, rsize: usize) -> Option<ProxyText> {
|
||||
let position = pt3_at(blob, pos + 8)?;
|
||||
// doubles: pos(3), normal(3), direction(3) — direction length = height.
|
||||
let dir = pt3_at(blob, pos + 8 + 48)?;
|
||||
let height = (dir[0] * dir[0] + dir[1] * dir[1]).sqrt();
|
||||
let rotation = dir[1].atan2(dir[0]);
|
||||
// UTF-16LE strings live at the record's tail: the content, then the font.
|
||||
let data = blob.get(pos + 8..pos + rsize)?;
|
||||
let mut strings = utf16_strings(data);
|
||||
let font = strings
|
||||
.iter()
|
||||
.position(|s| s.to_ascii_lowercase().ends_with(".shx"))
|
||||
.map(|i| strings.remove(i))
|
||||
.unwrap_or_default();
|
||||
let text = strings.into_iter().next().unwrap_or_default();
|
||||
if text.trim().is_empty() || !height.is_finite() || height <= 0.0 {
|
||||
return None;
|
||||
}
|
||||
Some(ProxyText {
|
||||
position,
|
||||
height,
|
||||
rotation,
|
||||
text,
|
||||
font,
|
||||
color: ProxyColor::Inherit,
|
||||
})
|
||||
}
|
||||
|
||||
/// Pull the printable-ASCII UTF-16LE runs (≥ 2 chars) out of a byte slice.
|
||||
fn utf16_strings(data: &[u8]) -> Vec<String> {
|
||||
let mut out = Vec::new();
|
||||
let mut cur = String::new();
|
||||
let mut i = 0;
|
||||
while i + 1 < data.len() {
|
||||
let (lo, hi) = (data[i], data[i + 1]);
|
||||
if hi == 0 && (0x20..0x7f).contains(&lo) {
|
||||
cur.push(lo as char);
|
||||
} else {
|
||||
if cur.len() >= 2 {
|
||||
out.push(std::mem::take(&mut cur));
|
||||
} else {
|
||||
cur.clear();
|
||||
}
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
if cur.len() >= 2 {
|
||||
out.push(cur);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
@ -186,19 +396,19 @@ mod tests {
|
|||
.map(|c| u8::from_str_radix(std::str::from_utf8(c).unwrap(), 16).unwrap())
|
||||
.collect();
|
||||
assert_eq!(blob.len(), 140);
|
||||
let polys = decode(&blob);
|
||||
assert_eq!(polys.len(), 1);
|
||||
assert_eq!(polys[0].points.len(), 5);
|
||||
assert!((polys[0].points[2][0] - 1192.1490).abs() < 1e-3);
|
||||
assert!((polys[0].points[2][1] - 877.8240).abs() < 1e-3);
|
||||
let dec = decode(&blob);
|
||||
assert_eq!(dec.polylines.len(), 1);
|
||||
assert_eq!(dec.polylines[0].points.len(), 5);
|
||||
assert!((dec.polylines[0].points[2][0] - 1192.1490).abs() < 1e-3);
|
||||
assert!((dec.polylines[0].points[2][1] - 877.8240).abs() < 1e-3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_anything_it_does_not_model() {
|
||||
assert!(decode(&[]).is_empty());
|
||||
assert!(decode(&[0u8; 140]).is_empty()); // size field wrong
|
||||
assert!(decode(&[]).polylines.is_empty());
|
||||
assert!(decode(&[0u8; 140]).polylines.is_empty());
|
||||
let mut b = vec![0u8; 140];
|
||||
b[0] = 140; // right size, zero records
|
||||
assert!(decode(&b).is_empty());
|
||||
b[0] = 140;
|
||||
assert!(decode(&b).polylines.is_empty());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -244,41 +244,99 @@ pub(crate) fn tessellate_entity(
|
|||
// occupies its real place instead of silently disappearing.
|
||||
if let EntityType::Unknown(_) = e {
|
||||
if let Some(blob) = e.common().graphic_data.as_ref() {
|
||||
let polys = convert::proxy_graphics::decode(blob);
|
||||
if !polys.is_empty() {
|
||||
// Group primitives by their preview colour so each colour draws
|
||||
// in its own wire (the poly-lines within share a wire, joined by
|
||||
// NaN separators; arcs are already flattened to points).
|
||||
let dec = convert::proxy_graphics::decode(blob);
|
||||
if !dec.polylines.is_empty() || !dec.texts.is_empty() {
|
||||
use crate::scene::convert::proxy_graphics::ProxyColor;
|
||||
use std::collections::BTreeMap;
|
||||
let nan = [f64::NAN; 3];
|
||||
let mut by_color: BTreeMap<i32, Vec<[f64; 3]>> = BTreeMap::new();
|
||||
for poly in &polys {
|
||||
let buf = by_color.entry(poly.color).or_default();
|
||||
// A specific ACI / RGB overrides the entity colour; ByLayer /
|
||||
// ByBlock inherit it.
|
||||
let resolve = |pc: ProxyColor| -> ([f32; 4], u8) {
|
||||
match pc {
|
||||
ProxyColor::Aci(a) => (
|
||||
view::render::adapt_to_bg(
|
||||
convert::tess_util::aci_to_rgba(&acadrust::types::Color::Index(a)),
|
||||
bg_color,
|
||||
),
|
||||
a,
|
||||
),
|
||||
ProxyColor::Rgb(r, g, b) => (
|
||||
view::render::adapt_to_bg(
|
||||
[r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, 1.0],
|
||||
bg_color,
|
||||
),
|
||||
0,
|
||||
),
|
||||
ProxyColor::Inherit => (entity_color, aci),
|
||||
}
|
||||
};
|
||||
let mut wires = Vec::new();
|
||||
// Lines / shells: group by (colour, lineweight), one wire each.
|
||||
let mut groups: BTreeMap<(ProxyColor, i16), Vec<[f64; 3]>> = BTreeMap::new();
|
||||
for poly in &dec.polylines {
|
||||
let buf = groups.entry((poly.color, poly.lineweight)).or_default();
|
||||
if !buf.is_empty() {
|
||||
buf.push(nan);
|
||||
}
|
||||
buf.extend_from_slice(&poly.points);
|
||||
}
|
||||
let mut wires = Vec::with_capacity(by_color.len());
|
||||
for (pcolor, pts64) in by_color {
|
||||
// A real ACI index overrides the entity colour; ByLayer
|
||||
// (256) / ByBlock (0) inherit it.
|
||||
let (col, w_aci) = if (1..=255).contains(&pcolor) {
|
||||
let rgba = convert::tess_util::aci_to_rgba(
|
||||
&acadrust::types::Color::Index(pcolor as u8),
|
||||
);
|
||||
(view::render::adapt_to_bg(rgba, bg_color), pcolor as u8)
|
||||
for ((pcolor, plw), pts64) in groups {
|
||||
let (col, w_aci) = resolve(pcolor);
|
||||
let lw_px = if plw >= 0 {
|
||||
view::render::lineweight_to_px(&acadrust::types::LineWeight::Value(plw))
|
||||
} else {
|
||||
(entity_color, aci)
|
||||
line_weight_px
|
||||
};
|
||||
let (pts, pts_low) = convert::tessellate::points_to_ds(pts64);
|
||||
let mut w = WireModel::solid(h.value().to_string(), pts, col, sel);
|
||||
w.points_low = pts_low;
|
||||
w.line_weight_px = line_weight_px;
|
||||
w.line_weight_px = lw_px;
|
||||
w.aci = w_aci;
|
||||
wires.push(w);
|
||||
}
|
||||
return wires;
|
||||
// Text labels: draw the glyph strokes (simplex.shx etc. are
|
||||
// single-stroke fonts, so the outline is the character).
|
||||
for t in &dec.texts {
|
||||
let font = t.font.trim().trim_end_matches(".shx").trim_end_matches(".SHX");
|
||||
let font = if font.is_empty() { "standard" } else { font };
|
||||
let (strokes, _) = crate::scene::text::lff::tessellate_text_ex(
|
||||
[0.0, 0.0],
|
||||
t.height as f32,
|
||||
t.rotation as f32,
|
||||
1.0,
|
||||
0.0,
|
||||
font,
|
||||
&t.text,
|
||||
);
|
||||
let mut pts64: Vec<[f64; 3]> = Vec::new();
|
||||
for stroke in &strokes {
|
||||
if stroke.len() < 2 {
|
||||
continue;
|
||||
}
|
||||
if !pts64.is_empty() {
|
||||
pts64.push(nan);
|
||||
}
|
||||
for &[x, y] in stroke {
|
||||
pts64.push([
|
||||
t.position[0] + x as f64,
|
||||
t.position[1] + y as f64,
|
||||
t.position[2],
|
||||
]);
|
||||
}
|
||||
}
|
||||
if pts64.len() >= 2 {
|
||||
let (col, w_aci) = resolve(t.color);
|
||||
let (pts, pts_low) = convert::tessellate::points_to_ds(pts64);
|
||||
let mut w = WireModel::solid(h.value().to_string(), pts, col, sel);
|
||||
w.points_low = pts_low;
|
||||
w.line_weight_px = line_weight_px;
|
||||
w.aci = w_aci;
|
||||
wires.push(w);
|
||||
}
|
||||
}
|
||||
if !wires.is_empty() {
|
||||
return wires;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue