feat(scene): integrate standard DWG data

This commit is contained in:
Hakan Seven committed 2026-07-26 22:30:14 +03:00
1 parent 731df4a725
commit eb23715605
38 files changed
+8376 -618

No files matched your search

+66 -3
View File
@@ -34,6 +34,7 @@ use crate::scene::convert::solid3d_tess::{
TRUCK_CHORD_FRAC,
};
use crate::scene::model::mesh_model::MeshLodSet;
use rustc_hash::FxHashMap;
/// Slightly over 2π so revolution builders close the loop.
const FULL: f64 = std::f64::consts::TAU + 0.2;
@@ -91,9 +92,53 @@ pub fn tessellate_sat_truck(
let mut verts: Vec<[f64; 3]> = Vec::new();
let mut normals: Vec<[f32; 3]> = Vec::new();
let mut indices: Vec<u32> = Vec::new();
let face_materials: FxHashMap<i32, acadrust::Handle> = sat
.records
.iter()
.filter(|record| record.entity_type == "material-adesk-attrib")
.filter_map(|record| {
let owner = record.token_pointer(2)?.0;
let handle = record.token(3)?.as_integer()?;
(owner >= 0 && handle > 0)
.then(|| (owner, acadrust::Handle::new(handle as u64)))
})
.collect();
let face_colors: FxHashMap<i32, [f32; 4]> = sat
.records
.iter()
.filter(|record| record.entity_type == "color-adesk-attrib")
.filter_map(|record| {
let owner = record.token_pointer(2)?.0;
let value = record.token(3)?.as_integer()?;
let face_color = if (1..=255).contains(&value) {
acadrust::Color::from_index(value as i16)
} else if value > 257 {
acadrust::Color::from_true_color_value(value as i32)
} else {
return None;
};
Some((
owner,
{
let mut rgba =
crate::scene::convert::tess_util::aci_to_rgba(&face_color);
rgba[3] = color[3];
rgba
},
))
})
.collect();
let mut triangle_material_handles: Vec<Option<acadrust::Handle>> = Vec::new();
let mut triangle_colors: Vec<Option<[f32; 4]>> = Vec::new();
let face_record_indices: Vec<i32> = sat
.records
.iter()
.filter(|record| record.is_a("face"))
.map(|record| record.index)
.collect();
let mut complete = true;
for face in sat.faces().into_iter() {
for (face, face_record_index) in sat.faces().into_iter().zip(face_record_indices) {
let Some(surf_rec) = sat.resolve(face.surface()) else {
complete = false;
continue;
@@ -121,6 +166,15 @@ pub fn tessellate_sat_truck(
}
if !appended {
complete = false;
} else {
let material = face_materials.get(&face_record_index).copied();
triangle_material_handles.extend(
std::iter::repeat(material).take((indices.len() - before) / 3),
);
let face_color = face_colors.get(&face_record_index).copied();
triangle_colors.extend(
std::iter::repeat(face_color).take((indices.len() - before) / 3),
);
}
}
@@ -128,7 +182,16 @@ pub fn tessellate_sat_truck(
return None;
}
let mesh = finalize_mesh(name, verts, normals, indices, color, body_transform(sat));
let mesh = finalize_mesh(
name,
verts,
normals,
indices,
triangle_material_handles,
triangle_colors,
color,
body_transform(sat),
);
let mut set = MeshLodSet::from_lods(vec![mesh]);
set.complete = complete;
Some(set)
@@ -166,7 +229,7 @@ fn bespoke_face(
tess_torus_face(sat, face, &t, LodConfig::HIGH, v, n, i);
}
}
"spline-surface" => {
"spline-surface" | "meshsurf-surface" | "bs3-surface" => {
crate::scene::convert::spline_tess::tess_spline_face(
sat,
face,
+185 -30
View File
@@ -12,12 +12,13 @@
// editing never mistakes them for closed topology.
use rustc_hash::FxHashSet as HashSet;
use rustc_hash::FxHashMap;
use std::f64::consts::TAU;
use acadrust::entities::acis::types::Sense;
use acadrust::entities::acis::{
SabReader, SatCoedge, SatConeSurface, SatDocument, SatEdge, SatEllipseCurve, SatFace,
SatIntCurve, SatLoop, SatPlaneSurface, SatPoint, SatPointer, SatSphereSurface, SatToken,
SatIntCurve, SatLoop, SatPlaneSurface, SatPoint, SatPointer, SatSphereSurface,
SatTorusSurface, SatVertex,
};
use acadrust::entities::{Body, Region, Solid3D};
@@ -127,9 +128,48 @@ fn tessellate_sat(
let mut verts: Vec<[f64; 3]> = Vec::new();
let mut normals: Vec<[f32; 3]> = Vec::new();
let mut indices: Vec<u32> = Vec::new();
let face_materials: FxHashMap<i32, acadrust::Handle> = sat
.records
.iter()
.filter(|record| record.entity_type == "material-adesk-attrib")
.filter_map(|record| {
let owner = record.token_pointer(2)?.0;
let handle = record.token(3)?.as_integer()?;
(owner >= 0 && handle > 0)
.then(|| (owner, acadrust::Handle::new(handle as u64)))
})
.collect();
let face_colors: FxHashMap<i32, [f32; 4]> = sat
.records
.iter()
.filter(|record| record.entity_type == "color-adesk-attrib")
.filter_map(|record| {
let owner = record.token_pointer(2)?.0;
let value = record.token(3)?.as_integer()?;
let color_value = if (1..=255).contains(&value) {
Some(acadrust::Color::from_index(value as i16))
} else if value > 257 {
Some(acadrust::Color::from_true_color_value(value as i32))
} else {
None
}?;
let mut rgba =
crate::scene::convert::tess_util::aci_to_rgba(&color_value);
rgba[3] = color[3];
Some((owner, rgba))
})
.collect();
let mut triangle_material_handles: Vec<Option<acadrust::Handle>> = Vec::new();
let mut triangle_colors: Vec<Option<[f32; 4]>> = Vec::new();
let face_record_indices: Vec<i32> = sat
.records
.iter()
.filter(|record| record.is_a("face"))
.map(|record| record.index)
.collect();
let mut complete = true;
for face in sat.faces() {
for (face, face_record_index) in sat.faces().into_iter().zip(face_record_indices) {
let surf_ptr = face.surface();
let Some(surf_rec) = sat.resolve(surf_ptr) else {
complete = false;
@@ -189,7 +229,7 @@ fn tessellate_sat(
);
}
}
"spline-surface" => {
"spline-surface" | "meshsurf-surface" | "bs3-surface" => {
crate::scene::convert::spline_tess::tess_spline_face(
sat,
&face,
@@ -203,13 +243,31 @@ fn tessellate_sat(
}
if indices.len() == before {
complete = false;
} else {
let material = face_materials.get(&face_record_index).copied();
triangle_material_handles.extend(
std::iter::repeat(material).take((indices.len() - before) / 3),
);
let face_color = face_colors.get(&face_record_index).copied();
triangle_colors.extend(
std::iter::repeat(face_color).take((indices.len() - before) / 3),
);
}
}
if indices.is_empty() {
return None;
}
Some((
finalize_mesh(name, verts, normals, indices, color, xform),
finalize_mesh(
name,
verts,
normals,
indices,
triangle_material_handles,
triangle_colors,
color,
xform,
),
complete,
))
}
@@ -937,34 +995,43 @@ fn tessellate_sat_lods(
/// record (`<3×3> <tx ty tz> <scale> rotate reflect shear`). `None` when the
/// document has no transform (treated as identity).
pub(crate) fn body_transform(sat: &SatDocument) -> Option<([f64; 9], [f64; 3], f64)> {
let t = sat.records.iter().find(|r| r.entity_type == "transform")?;
// The transform record's numeric payload is its first 13 numeric values:
// 3×3 matrix, translation, scale. A leading book-keeping pointer (`$-1`)
// and the trailing rotate/reflect/shear flags aren't numeric, so
// collecting numeric tokens skips them — reading by raw token index would
// be thrown off by the leading pointer. SAT text tokenizes the payload as
// 13 individual floats, but the SAB reader groups the matrix rows and the
// translation into `Position` triplets, so those must be flattened too.
let mut v: Vec<f64> = Vec::with_capacity(13);
for tok in &t.tokens {
if v.len() >= 13 {
let transform = sat
.records
.iter()
.find(|record| record.entity_type == "transform")?;
let mut values = Vec::with_capacity(13);
for token in &transform.tokens {
if values.len() >= 13 {
break;
}
match tok {
SatToken::Position(x, y, z) => v.extend([*x, *y, *z]),
_ => {
if let Some(f) = tok.as_float() {
v.push(f);
if let Some((components, len)) = token.coordinate_components() {
values.extend_from_slice(&components[..len]);
} else if let Some(value) = token.as_float() {
values.push(value);
} else if let Some(text) = token.as_string() {
// Some ASM SAB bodies pack the complete SAT transform payload in
// one long-string token instead of individual numeric tokens.
for word in text.split_ascii_whitespace() {
let Ok(value) = word.parse::<f64>() else {
break;
};
values.push(value);
if values.len() >= 13 {
break;
}
}
}
}
if v.len() < 13 {
return None;
}
let m = [v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7], v[8]];
let tr = [v[9], v[10], v[11]];
Some((m, tr, v[12]))
(values.len() >= 13).then(|| {
(
[
values[0], values[1], values[2], values[3], values[4], values[5], values[6],
values[7], values[8],
],
[values[9], values[10], values[11]],
values[12],
)
})
}
/// Apply a body placement transform to a mesh. ACIS treats points as row
@@ -985,6 +1052,8 @@ pub(crate) fn finalize_mesh(
verts: Vec<[f64; 3]>,
normals: Vec<[f32; 3]>,
indices: Vec<u32>,
triangle_material_handles: Vec<Option<acadrust::Handle>>,
triangle_colors: Vec<Option<[f32; 4]>>,
color: [f32; 4],
xform: Option<([f64; 9], [f64; 3], f64)>,
) -> MeshModel {
@@ -1031,6 +1100,8 @@ pub(crate) fn finalize_mesh(
verts_low: lo,
normals,
indices,
triangle_material_handles,
triangle_colors,
color,
selected: false,
}
@@ -1088,6 +1159,76 @@ fn parse_acis(
None
}
fn remap_acis_material_bindings(
set: &mut MeshLodSet,
acis: &acadrust::entities::AcisData,
) {
if acis.materials.is_empty() {
return;
}
for lod in &mut set.lods {
for handle in lod.triangle_material_handles.iter_mut().flatten() {
let reference = handle.value() as i32;
if let Some(material_handle) = acis.materials.iter().find_map(|binding| {
(binding.absolute_reference == reference || binding.array_index == reference)
.then_some(binding.material_handle)
.flatten()
}) {
*handle = material_handle;
}
}
}
}
fn attach_stored_silhouettes(
set: &mut MeshLodSet,
silhouettes: &[acadrust::entities::Silhouette],
) {
use crate::scene::model::mesh_model::StoredSilhouette;
set.stored_silhouettes = silhouettes
.iter()
.filter_map(|silhouette| {
let mut edge_verts = Vec::new();
let mut edge_verts_low = Vec::new();
for wire in &silhouette.wires {
for segment in wire.points.windows(2) {
for point in segment {
let point = crate::entities::solid3d::wire_point(wire, point);
let high = [point.x as f32, point.y as f32, point.z as f32];
edge_verts.push(high);
edge_verts_low.push([
(point.x - high[0] as f64) as f32,
(point.y - high[1] as f64) as f32,
(point.z - high[2] as f64) as f32,
]);
}
}
}
(!edge_verts.is_empty()).then_some(StoredSilhouette {
viewport_id: silhouette.viewport_id,
view_direction: [
silhouette.view_direction.x as f32,
silhouette.view_direction.y as f32,
silhouette.view_direction.z as f32,
],
up_vector: [
silhouette.up_vector.x as f32,
silhouette.up_vector.y as f32,
silhouette.up_vector.z as f32,
],
target: [
silhouette.target.x as f32,
silhouette.target.y as f32,
silhouette.target.z as f32,
],
is_perspective: silhouette.is_perspective,
edge_verts,
edge_verts_low,
})
})
.collect();
}
/// Tessellate a `Region` entity (2D planar ACIS body) at all three LOD levels.
pub fn tessellate_region(
region: &Region,
@@ -1101,7 +1242,10 @@ pub fn tessellate_region(
&region.acis_data.sab_data,
)?;
let name = region.common.handle.value().to_string();
tessellate_acis(&sat, name, color, facet_res, isolines)
let mut set = tessellate_acis(&sat, name, color, facet_res, isolines)?;
remap_acis_material_bindings(&mut set, &region.acis_data);
attach_stored_silhouettes(&mut set, &region.silhouettes);
Some(set)
}
/// Tessellate a `Body` entity (3D ACIS body) at all three LOD levels.
@@ -1117,7 +1261,10 @@ pub fn tessellate_body(
&body.acis_data.sab_data,
)?;
let name = body.common.handle.value().to_string();
tessellate_acis(&sat, name, color, facet_res, isolines)
let mut set = tessellate_acis(&sat, name, color, facet_res, isolines)?;
remap_acis_material_bindings(&mut set, &body.acis_data);
attach_stored_silhouettes(&mut set, &body.silhouettes);
Some(set)
}
/// Tessellate a `Surface` entity (ACAD_SURFACE family) at all three LOD
@@ -1136,7 +1283,12 @@ pub fn tessellate_surface(
&surface.acis_data.sab_data,
)?;
let name = surface.common.handle.value().to_string();
tessellate_acis(&sat, name, color, facet_res, isolines)
let surface_isolines = usize::from(surface.u_isolines.max(surface.v_isolines).max(0) as u16);
let mut set =
tessellate_acis(&sat, name, color, facet_res, surface_isolines.max(isolines))?;
remap_acis_material_bindings(&mut set, &surface.acis_data);
attach_stored_silhouettes(&mut set, &surface.silhouettes);
Some(set)
}
/// Tessellate a `Solid3D` entity at all three LOD levels.
@@ -1156,7 +1308,10 @@ pub fn tessellate_solid3d(
&solid.acis_data.sab_data,
)?;
let name = solid.common.handle.value().to_string();
tessellate_acis(&sat, name, color, facet_res, isolines)
let mut set = tessellate_acis(&sat, name, color, facet_res, isolines)?;
remap_acis_material_bindings(&mut set, &solid.acis_data);
attach_stored_silhouettes(&mut set, &solid.silhouettes);
Some(set)
}
// ── Topology helpers ──────────────────────────────────────────────────────────
+46 -10
View File
@@ -357,6 +357,8 @@ pub(crate) fn tessellate_entity(
// by the viewport's GPU uniform so it never changes resident wire content.
paper_space: bool,
) -> Vec<WireModel> {
let contextual = crate::scene::annotative::entity_for_active_context(document, e);
let e = contextual.as_ref();
let h = e.common().handle;
let sel = selected.contains(&h);
@@ -518,9 +520,22 @@ pub(crate) fn tessellate_entity(
// blob, the vector preview its author cached for exactly this case. AutoCAD
// draws that when the object enabler is missing; draw it too, so the 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 dec = convert::proxy_graphics::decode(blob);
let proxy_blob: Option<std::borrow::Cow<'_, [u8]>> = match e {
EntityType::Unknown(_) => e
.common()
.graphic_data
.as_deref()
.map(std::borrow::Cow::Borrowed),
EntityType::Extended(entity) => match &entity.data {
acadrust::entities::ExtendedEntityData::Proxy(proxy) => {
Some(std::borrow::Cow::Owned(proxy.graphics.data()))
}
_ => None,
},
_ => None,
};
if let Some(blob) = proxy_blob {
let dec = convert::proxy_graphics::decode(blob.as_ref());
if !dec.polylines.is_empty() || !dec.texts.is_empty() {
use crate::scene::convert::proxy_graphics::ProxyColor;
use std::collections::BTreeMap;
@@ -618,7 +633,6 @@ pub(crate) fn tessellate_entity(
return wires;
}
}
}
}
// ── Section symbol (AcDbSectionSymbol): draw the "A-A" cut mark ──────────
@@ -744,10 +758,13 @@ pub(crate) fn tessellate_entity(
// Sub-entities inside *D### / DIMBLOCK## blocks
// typically use ByBlock color/linetype/lineweight —
// they should inherit from the Dimension entity.
let sub_color_is_byblock =
sub.common().color == acadrust::types::Color::ByBlock;
let has_book_color =
view::render::has_resolved_book_color(document, sub);
let sub_color_is_byblock = !has_book_color
&& sub.common().color == acadrust::types::Color::ByBlock;
let sub_is_l0_bylayer =
view::render::is_effective_layer_zero(&sub.common().layer)
!has_book_color
&& view::render::is_effective_layer_zero(&sub.common().layer)
&& sub.common().color == acadrust::types::Color::ByLayer;
let sub_wires = tessellate_entity(
document,
@@ -898,10 +915,13 @@ pub(crate) fn tessellate_entity(
let Some(sub) = document.get_entity(eh) else {
continue;
};
let sub_color_is_byblock =
sub.common().color == acadrust::types::Color::ByBlock;
let has_book_color =
view::render::has_resolved_book_color(document, sub);
let sub_color_is_byblock = !has_book_color
&& sub.common().color == acadrust::types::Color::ByBlock;
let sub_is_l0_bylayer =
view::render::is_effective_layer_zero(&sub.common().layer)
!has_book_color
&& view::render::is_effective_layer_zero(&sub.common().layer)
&& sub.common().color == acadrust::types::Color::ByLayer;
let mut placed = sub.clone();
{
@@ -976,6 +996,22 @@ pub(crate) fn tessellate_entity(
line_weight_px,
table_anno,
);
for insert in
crate::entities::table::block_cell_inserts(tab, document, table_anno)
{
wires.extend(tessellate_entity(
document,
selected,
active_viewport,
bg_color,
1.0,
&EntityType::Insert(insert),
block_cache,
view_aabb,
world_per_pixel,
paper_space,
));
}
if !wires.is_empty() {
let aabb = entity_aabb(e);
for w in &mut wires {
+2 -1
View File
@@ -1723,7 +1723,8 @@ fn solid_wire_fallback(entity: &EntityType) -> Vec<[f64; 3]> {
continue;
}
for v in &wire.points {
pts.push([v.x, v.y, v.z]);
let transformed = crate::entities::solid3d::wire_point(wire, v);
pts.push([transformed.x, transformed.y, transformed.z]);
}
// NaN sentinel separates distinct wire segments.
pts.push([f64::NAN, f64::NAN, f64::NAN]);