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:
parent
63b17547f9
commit
27aca41dd1
39 changed files with 168 additions and 15 deletions
|
|
@ -23,6 +23,8 @@ pub enum TruckObject {
|
|||
Text(Vec<TextStroke>),
|
||||
/// Pre-computed NaN-separated 3-D point list (leader lines, arrowheads, etc.).
|
||||
Lines(Vec<[f32; 3]>),
|
||||
/// Like Lines but linetype pattern restarts at each NaN-separated segment (plinegen=false).
|
||||
SegmentedLines(Vec<[f32; 3]>),
|
||||
Volume(Solid),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -202,6 +202,7 @@ pub fn apply_along(
|
|||
aci: 0,
|
||||
key_vertices: vec![],
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3211,6 +3211,7 @@ fn tessellate_entity(
|
|||
tangent_geoms: vec![],
|
||||
key_vertices: vec![],
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
});
|
||||
return wires;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -148,7 +148,12 @@ impl WireGpu {
|
|||
let p = wire.points[i - 1];
|
||||
let q = wire.points[i];
|
||||
if !p[0].is_finite() || !q[0].is_finite() {
|
||||
dists[i] = dists[i - 1];
|
||||
// plinegen=false: reset to 0 at the first real point after a NaN separator.
|
||||
dists[i] = if !wire.plinegen && !p[0].is_finite() && q[0].is_finite() {
|
||||
0.0
|
||||
} else {
|
||||
dists[i - 1]
|
||||
};
|
||||
} else {
|
||||
let dx = q[0] - p[0];
|
||||
let dy = q[1] - p[1];
|
||||
|
|
@ -234,10 +239,13 @@ impl WireGpu {
|
|||
for i in 1..n {
|
||||
let p = wire.points[i - 1];
|
||||
let q = wire.points[i];
|
||||
// If either point is non-finite (NaN sentinel or ±inf from overflow),
|
||||
// keep the same distance — the segment will be skipped anyway.
|
||||
if !p[0].is_finite() || !q[0].is_finite() {
|
||||
dists[i] = dists[i - 1];
|
||||
// plinegen=false: reset to 0 at the first real point after a NaN separator.
|
||||
dists[i] = if !wire.plinegen && !p[0].is_finite() && q[0].is_finite() {
|
||||
0.0
|
||||
} else {
|
||||
dists[i - 1]
|
||||
};
|
||||
} else {
|
||||
let dx = q[0] - p[0];
|
||||
let dy = q[1] - p[1];
|
||||
|
|
|
|||
|
|
@ -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, ®ion.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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ pub fn tessellate(
|
|||
aci: 0,
|
||||
key_vertices: te.key_vertices,
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -132,6 +133,7 @@ pub fn tessellate(
|
|||
aci: 0,
|
||||
key_vertices: te.key_vertices,
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
|
|
@ -158,6 +160,7 @@ pub fn tessellate(
|
|||
aci: 0,
|
||||
key_vertices,
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -182,6 +185,7 @@ pub fn tessellate(
|
|||
aci: 0,
|
||||
key_vertices,
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -212,6 +216,34 @@ pub fn tessellate(
|
|||
aci: 0,
|
||||
key_vertices,
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
};
|
||||
}
|
||||
|
||||
TruckObject::SegmentedLines(points) => {
|
||||
let [ox, oy, oz] = world_offset;
|
||||
let local_pts: Vec<[f32; 3]> = points.into_iter().map(|[x, y, z]| {
|
||||
if x.is_nan() { [x, y, z] }
|
||||
else { [x - ox as f32, y - oy as f32, z - oz as f32] }
|
||||
}).collect();
|
||||
let snap_pts = offset_snap_pts(te.snap_pts, world_offset);
|
||||
let key_vertices: Vec<[f32; 3]> = te.key_vertices.into_iter()
|
||||
.map(|[x, y, z]| [x - ox as f32, y - oy as f32, z - oz as f32])
|
||||
.collect();
|
||||
return WireModel {
|
||||
name,
|
||||
points: local_pts,
|
||||
color,
|
||||
selected,
|
||||
pattern_length,
|
||||
pattern,
|
||||
line_weight_px,
|
||||
snap_pts,
|
||||
tangent_geoms: te.tangent_geoms,
|
||||
aci: 0,
|
||||
key_vertices,
|
||||
plinegen: false,
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -241,6 +273,7 @@ pub fn tessellate(
|
|||
tangent_geoms,
|
||||
key_vertices,
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -296,6 +329,7 @@ pub fn tessellate_dimension(
|
|||
tangent_geoms: vec![],
|
||||
key_vertices,
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
}];
|
||||
|
||||
if let Some(text) = dimension_text_entity(dim) {
|
||||
|
|
@ -344,6 +378,7 @@ fn tessellate_leader(
|
|||
aci: 0,
|
||||
key_vertices: vec![],
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
}];
|
||||
}
|
||||
|
||||
|
|
@ -412,6 +447,7 @@ fn tessellate_leader(
|
|||
tangent_geoms: vec![],
|
||||
key_vertices,
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
}]
|
||||
}
|
||||
|
||||
|
|
@ -521,6 +557,7 @@ fn tessellate_multileader(
|
|||
aci: 0,
|
||||
key_vertices: key_verts,
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
}];
|
||||
|
||||
// Render text content as MText wire
|
||||
|
|
|
|||
|
|
@ -60,6 +60,9 @@ pub struct WireModel {
|
|||
/// Preview / interim wires use `UNBOUNDED_AABB` so they are never pre-rejected
|
||||
/// by the snap world-space filter.
|
||||
pub aabb: [f32; 4],
|
||||
/// When false the linetype pattern restarts at each NaN-separated segment
|
||||
/// (DXF PLINEGEN=0). When true the pattern runs continuously (PLINEGEN=1).
|
||||
pub plinegen: bool,
|
||||
}
|
||||
|
||||
impl WireModel {
|
||||
|
|
@ -85,6 +88,7 @@ impl WireModel {
|
|||
tangent_geoms: vec![],
|
||||
key_vertices: vec![],
|
||||
aabb: Self::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -184,6 +188,7 @@ impl Default for WireModel {
|
|||
tangent_geoms: Vec::new(),
|
||||
key_vertices: Vec::new(),
|
||||
aabb: Self::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue