From 1d117f514c6c62147e6551b076d9ae509072fcf7 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Sat, 18 Jul 2026 20:44:47 +0300 Subject: [PATCH] feat(proxy): honour proxy-graphics colour traits The preview stream sets a current colour (record type 14, an ACI) before its geometry; track it and tag each decoded primitive. The Unknown-entity path groups primitives by colour and draws one wire per colour, resolving a real index (1..=255) to its RGB and letting ByLayer/ByBlock inherit the entity colour. So a custom entity's preview now renders in its own colours (e.g. an ACA door's blue swing) instead of a single flat colour. Co-Authored-By: Claude Opus 4.8 --- src/scene/convert/proxy_graphics.rs | 33 +++++++++++++++++------ src/scene/convert/tess.rs | 41 ++++++++++++++++++++--------- 2 files changed, 54 insertions(+), 20 deletions(-) diff --git a/src/scene/convert/proxy_graphics.rs b/src/scene/convert/proxy_graphics.rs index 20e4aaddb..dcf16a338 100644 --- a/src/scene/convert/proxy_graphics.rs +++ b/src/scene/convert/proxy_graphics.rs @@ -33,12 +33,22 @@ /// 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, } +/// AutoCAD Color Index meaning "inherit from the layer" — the default until a +/// colour trait says otherwise. +const COLOR_BYLAYER: i32 = 256; + 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_COLOR: u32 = 14; fn u32_at(b: &[u8], o: usize) -> Option { b.get(o..o + 4).map(|s| u32::from_le_bytes([s[0], s[1], s[2], s[3]])) @@ -74,6 +84,7 @@ pub fn decode(blob: &[u8]) -> Vec { } let mut pos = 8usize; + let mut color = COLOR_BYLAYER; for _ in 0..count { let Some(rsize) = u32_at(blob, pos) else { break }; let rsize = rsize as usize; @@ -82,6 +93,11 @@ pub fn decode(blob: &[u8]) -> Vec { break; } match rtype { + REC_COLOR => { + if let Some(c) = u32_at(blob, pos + 8) { + color = c as i32; + } + } REC_POLYLINE | REC_POLYGON => { if let Some(n) = u32_at(blob, pos + 8) { let n = n as usize; @@ -101,18 +117,18 @@ pub fn decode(blob: &[u8]) -> Vec { pts.push(first); } } - out.push(ProxyPolyline { points: pts }); + out.push(ProxyPolyline { points: pts, color }); } } } } REC_ARC | REC_ARC5 => { - if let Some(poly) = decode_arc(blob, pos) { - out.push(poly); + if let Some(points) = decode_arc(blob, pos) { + out.push(ProxyPolyline { points, color }); } } - // Every other record is a trait (colour, layer, lineweight, …) — - // skipped in phase 1; `rsize` still advances us past it. + // Every other record is a trait (layer, lineweight, …) — skipped + // for now; `rsize` still advances us past it. _ => {} } pos += rsize; @@ -121,8 +137,9 @@ pub fn decode(blob: &[u8]) -> Vec { } /// Flatten a circular-arc record (centre, radius, normal, start direction, -/// sweep) into a poly-line. The normal's Z sign gives the sweep direction. -fn decode_arc(blob: &[u8], pos: usize) -> Option { +/// sweep) into a strip of points. The normal's Z sign gives the sweep +/// direction. +fn decode_arc(blob: &[u8], pos: usize) -> Option> { let center = pt3_at(blob, pos + 8)?; let radius = f64_at(blob, pos + 32)?; let normal = pt3_at(blob, pos + 40)?; @@ -144,7 +161,7 @@ fn decode_arc(blob: &[u8], pos: usize) -> Option { center[2], ]); } - Some(ProxyPolyline { points }) + Some(points) } #[cfg(test)] diff --git a/src/scene/convert/tess.rs b/src/scene/convert/tess.rs index 5a79ddcde..98e18289c 100644 --- a/src/scene/convert/tess.rs +++ b/src/scene/convert/tess.rs @@ -246,22 +246,39 @@ pub(crate) fn tessellate_entity( if let Some(blob) = e.common().graphic_data.as_ref() { let polys = convert::proxy_graphics::decode(blob); if !polys.is_empty() { - // One wire for the whole preview: concatenate the poly-lines - // with NaN separators (arcs are already flattened to points). + // 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). + use std::collections::BTreeMap; let nan = [f64::NAN; 3]; - let mut pts64: Vec<[f64; 3]> = Vec::new(); + let mut by_color: BTreeMap> = BTreeMap::new(); for poly in &polys { - if !pts64.is_empty() { - pts64.push(nan); + let buf = by_color.entry(poly.color).or_default(); + if !buf.is_empty() { + buf.push(nan); } - pts64.extend_from_slice(&poly.points); + buf.extend_from_slice(&poly.points); } - let (pts, pts_low) = convert::tessellate::points_to_ds(pts64); - let mut w = WireModel::solid(h.value().to_string(), pts, entity_color, sel); - w.points_low = pts_low; - w.line_weight_px = line_weight_px; - w.aci = aci; - return vec![w]; + 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) + } else { + (entity_color, aci) + }; + 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); + } + return wires; } } }