feat: plinegen + SAB parsing for Solid3D/Region/Body

LwPolyline PLINEGEN=0 (DXF default): emit SegmentedLines with NaN
separators between vertex-to-vertex segments; wire_gpu resets
arc-length to 0 after each NaN so linetype pattern restarts per vertex.
PLINEGEN=1: existing Contour path unchanged (continuous pattern).
WireModel gains plinegen field propagated through tessellate pipeline.

Solid3D/Region/Body: call SabReader::read() when parse_sat() returns
None due to binary SAB format (DWG files / newer AutoCAD exports).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-05-06 06:55:28 +03:00
commit 27aca41dd1
39 changed files with 168 additions and 15 deletions

View file

@ -14,8 +14,8 @@ use std::collections::HashSet;
use std::f64::consts::TAU;
use acadrust::entities::acis::{
SatCoedge, SatConeSurface, SatDocument, SatEdge, SatFace, SatLoop, SatPlaneSurface, SatPoint,
SatPointer, SatSphereSurface, SatTorusSurface, SatVertex,
SabReader, SatCoedge, SatConeSurface, SatDocument, SatEdge, SatFace, SatLoop,
SatPlaneSurface, SatPoint, SatPointer, SatSphereSurface, SatTorusSurface, SatVertex,
};
use acadrust::entities::acis::types::Sense;
use acadrust::entities::{Body, Region, Solid3D};
@ -67,16 +67,26 @@ fn tessellate_sat(sat: &SatDocument, name: String, color: [f32; 4]) -> Option<Me
Some(MeshModel { name, verts, normals, indices, color, selected: false })
}
fn parse_acis(sat_fn: impl FnOnce() -> Option<SatDocument>, is_binary: bool, sab_data: &[u8]) -> Option<SatDocument> {
if let Some(doc) = sat_fn() {
return Some(doc);
}
if is_binary && !sab_data.is_empty() {
return SabReader::read(sab_data).ok();
}
None
}
/// Tessellate a `Region` entity (2D planar ACIS body) into a `MeshModel`.
pub fn tessellate_region(region: &Region, color: [f32; 4]) -> Option<MeshModel> {
let sat = region.parse_sat()?;
let sat = parse_acis(|| region.parse_sat(), region.acis_data.is_binary, &region.acis_data.sab_data)?;
let name = region.common.handle.value().to_string();
tessellate_sat(&sat, name, color)
}
/// Tessellate a `Body` entity (3D ACIS body) into a `MeshModel`.
pub fn tessellate_body(body: &Body, color: [f32; 4]) -> Option<MeshModel> {
let sat = body.parse_sat()?;
let sat = parse_acis(|| body.parse_sat(), body.acis_data.is_binary, &body.acis_data.sab_data)?;
let name = body.common.handle.value().to_string();
tessellate_sat(&sat, name, color)
}
@ -86,7 +96,7 @@ pub fn tessellate_body(body: &Body, color: [f32; 4]) -> Option<MeshModel> {
/// Returns `None` when the entity has no parseable SAT data or produces no
/// triangles (e.g. the solid uses only unsupported surface types).
pub fn tessellate_solid3d(solid: &Solid3D, color: [f32; 4]) -> Option<MeshModel> {
let sat = solid.parse_sat()?;
let sat = parse_acis(|| solid.parse_sat(), solid.acis_data.is_binary, &solid.acis_data.sab_data)?;
let name = solid.common.handle.value().to_string();
tessellate_sat(&sat, name, color)
}