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

@ -32,7 +32,7 @@ Legend: ✅ Done · ⚠️ Partial · ❌ Not done
| Status | Entity | Ignored Field(s) | Effect | | Status | Entity | Ignored Field(s) | Effect |
|---|---|---|---| |---|---|---|---|
| ✅ | **Arc / Circle / Line / LwPolyline / Polyline2D** | `thickness` | Extrudes along WCS normal — `src/entities/{arc,circle,line,lwpolyline,polyline}.rs` | | ✅ | **Arc / Circle / Line / LwPolyline / Polyline2D** | `thickness` | Extrudes along WCS normal — `src/entities/{arc,circle,line,lwpolyline,polyline}.rs` |
| ❌ | **LWPolyline** | `plinegen` flag | Linetype pattern resets at each vertex instead of continuing | | ✅ | **LWPolyline** | `plinegen` flag | `plinegen=false` → `SegmentedLines` with NaN separators + distance reset in `wire_gpu.rs`; `plinegen=true` → `Contour` (continuous) |
--- ---
@ -49,7 +49,7 @@ Legend: ✅ Done · ⚠️ Partial · ❌ Not done
| Status | Entity | Missing | | Status | Entity | Missing |
|---|---|---| |---|---|---|
| ⚠️ | **Solid3D / Region / Body** | `src/scene/solid3d_tess.rs` exists with MeshModel support; no real mesh from ACIS data | | ✅ | **Solid3D / Region / Body** | SAT (text) + SAB (binary) both parsed — `SabReader::read` called when `is_binary` — tessellates plane/cone/sphere/torus faces — `src/scene/solid3d_tess.rs` |
### Partial Render ### Partial Render
@ -207,13 +207,13 @@ These are fixed in our post-load `fix_dxf_dimension_rotations()` in `src/io/mod.
| Status | Count | | Status | Count |
|---|---| |---|---|
| ✅ Done | 48 | | ✅ Done | 50 |
| ⚠️ Partial | 2 | | ⚠️ Partial | 1 |
| ❌ Not done | 0 | | ❌ Not done | 0 |
| **Total** | **50** | | **Total** | **51** |
### Remaining gaps by priority ### Remaining gaps by priority
**Medium:** Viewport GPU scissor rect (pixel-level boundary clipping for overlapping viewports) **Medium:** Viewport GPU scissor rect (pixel-level boundary clipping for overlapping viewports)
**Low:** LWPolyline plinegen — GPU shader change needed; current behavior is continuous arc-length across vertices (equivalent to plinegen=true). Segment-restart mode (plinegen=false) requires distance reset logic in the wire shader. No remaining low-priority gaps.

View file

@ -1366,6 +1366,7 @@ impl H7CAD {
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: vec![], key_vertices: vec![],
aabb: crate::scene::WireModel::UNBOUNDED_AABB, aabb: crate::scene::WireModel::UNBOUNDED_AABB,
plinegen: true,
}; };
previews.push(guide); previews.push(guide);
} }

View file

@ -184,6 +184,62 @@ fn to_truck(pline: &LwPolyline) -> TruckEntity {
return thick_segments(&seg_data, &path, pline.thickness, normal, kv, tgs); return thick_segments(&seg_data, &path, pline.thickness, normal, kv, tgs);
} }
// plinegen=false: NaN-separated segments so the linetype pattern restarts per vertex.
if !pline.plinegen {
let mut pts: Vec<[f32; 3]> = Vec::new();
let mut tgs: Vec<TangentGeom> = Vec::new();
let mut kv: Vec<[f32; 3]> = Vec::new();
for i in 0..seg_count {
let va = &verts[i];
let vb = &verts[(i + 1) % count];
let (ox0, oy0) = (va.location.x, va.location.y);
let (ox1, oy1) = (vb.location.x, vb.location.y);
let bulge = va.bulge;
let (wx0, wy0, wz0) = to_wcs(ox0, oy0);
let p_start = [wx0 as f32, wy0 as f32, wz0 as f32];
pts.push(p_start);
if i == 0 { kv.push(p_start); }
if bulge.abs() < 1e-9 {
let (wx1, wy1, wz1) = to_wcs(ox1, oy1);
let p_end = [wx1 as f32, wy1 as f32, wz1 as f32];
pts.push(p_end);
kv.push(p_end);
tgs.push(TangentGeom::Line { p1: p_start, p2: p_end });
} else {
let angle = 4.0 * bulge.atan();
let dx = ox1 - ox0; let dy = oy1 - oy0;
let d = (dx * dx + dy * dy).sqrt().max(1e-12);
let r = (d / 2.0) / (angle / 2.0).sin().abs();
let mx = (ox0 + ox1) * 0.5; let my = (oy0 + oy1) * 0.5;
let px = -dy / d; let py = dx / d;
let ss = if bulge > 0.0 { 1.0_f64 } else { -1.0_f64 };
let h = r - (r * r - d * d / 4.0).max(0.0).sqrt();
let ocx = mx + ss * px * (r - h); let ocy = my + ss * py * (r - h);
let a0 = (oy0 - ocy).atan2(ox0 - ocx);
let mut a1 = (oy1 - ocy).atan2(ox1 - ocx);
if bulge > 0.0 { if a1 < a0 { a1 += TAU; } } else { if a1 > a0 { a1 -= TAU; } }
for j in 1..=16usize {
let a = a0 + (a1 - a0) * (j as f64 / 16.0);
let (wx, wy, wz) = to_wcs(ocx + r * a.cos(), ocy + r * a.sin());
pts.push([wx as f32, wy as f32, wz as f32]);
}
let (wx1, wy1, wz1) = to_wcs(ox1, oy1);
kv.push([wx1 as f32, wy1 as f32, wz1 as f32]);
let (wcx, wcy, wcz) = to_wcs(ocx, ocy);
tgs.push(TangentGeom::Circle { center: [wcx as f32, wcy as f32, wcz as f32], radius: r as f32 });
}
if i + 1 < seg_count {
pts.push([f32::NAN; 3]);
}
}
return TruckEntity {
object: TruckObject::SegmentedLines(pts),
snap_pts: vec![],
tangent_geoms: tgs,
key_vertices: kv,
};
}
for i in 0..seg_count { for i in 0..seg_count {
let v0 = &verts[i]; let v0 = &verts[i];
let v1 = &verts[(i + 1) % count]; let v1 = &verts[(i + 1) % count];

View file

@ -102,6 +102,7 @@ impl CadCommand for AlignedDimensionCommand {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}) })
} }
} }
@ -136,5 +137,6 @@ fn preview_aligned(p1: Vec3, p2: Vec3, dim_pt: Vec3) -> WireModel {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
} }
} }

View file

@ -125,6 +125,7 @@ fn preview_wire(points: Vec<Vec3>) -> WireModel {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
} }
} }

View file

@ -101,5 +101,6 @@ fn preview_line(a: Vec3, b: Vec3) -> WireModel {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
} }
} }

View file

@ -143,6 +143,7 @@ impl CadCommand for DimBaselineCommand {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}) })
} }
} }

View file

@ -140,6 +140,7 @@ impl CadCommand for DimContinueCommand {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}) })
} }
} }

View file

@ -94,6 +94,7 @@ impl CadCommand for DimJogLineCommand {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}) })
} }
} }

View file

@ -102,6 +102,7 @@ impl CadCommand for DimTeditCommand {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}) })
} }

View file

@ -202,6 +202,7 @@ fn preview_wire(pts: &[Vec3]) -> WireModel {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
} }
} }

View file

@ -113,6 +113,7 @@ fn preview_wire(points: Vec<Vec3>) -> WireModel {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
} }
} }

View file

@ -155,6 +155,7 @@ fn preview_wire(pts: &[Vec3]) -> WireModel {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
} }
} }

View file

@ -366,6 +366,7 @@ fn preview_wire(pts: &[Vec3]) -> WireModel {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
} }
} }

View file

@ -99,6 +99,7 @@ impl CadCommand for QdimCommand {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}) })
} }
} }

View file

@ -112,5 +112,6 @@ fn preview_wire(points: Vec<Vec3>) -> WireModel {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
} }
} }

View file

@ -145,6 +145,7 @@ impl CadCommand for TableCommand {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}) })
} else { } else {
None None

View file

@ -98,6 +98,7 @@ impl CadCommand for ToleranceCommand {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}) })
} }
} }

View file

@ -134,6 +134,7 @@ impl CadCommand for AttdefCommand {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}) })
} }
} }

View file

@ -527,5 +527,6 @@ fn line_wire(from: Vec3, to: Vec3) -> WireModel {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
} }
} }

View file

@ -88,6 +88,7 @@ impl CadCommand for LineCommand {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}) })
} }
} }

View file

@ -113,6 +113,7 @@ impl CadCommand for MlineCommand {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}) })
} }
} }

View file

@ -112,6 +112,7 @@ impl CadCommand for ImageCommand {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}) })
} }
} }

View file

@ -86,6 +86,7 @@ impl CadCommand for RayCommand {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}) })
} }
} }
@ -164,6 +165,7 @@ impl CadCommand for XLineCommand {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}) })
} }
} }

View file

@ -83,6 +83,7 @@ impl CadCommand for RevCloudCommand {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}) })
} }
} }

View file

@ -119,6 +119,7 @@ impl CadCommand for WipeoutCommand {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}) })
} }
WipeoutMode::Polygonal => { WipeoutMode::Polygonal => {
@ -140,6 +141,7 @@ impl CadCommand for WipeoutCommand {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}) })
} }
} }

View file

@ -76,6 +76,7 @@ impl CadCommand for AreaCommand {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}) })
} }
} }

View file

@ -71,6 +71,7 @@ impl CadCommand for DistCommand {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}) })
} }
} }

View file

@ -134,6 +134,7 @@ impl CadCommand for StretchCommand {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}) })
} }
_ => None, _ => None,

View file

@ -98,6 +98,7 @@ impl CadCommand for MviewCommand {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}) })
} }
} }

View file

@ -63,6 +63,7 @@ impl CadCommand for PlotWindowCommand {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}) })
} }
} }

View file

@ -69,6 +69,7 @@ impl CadCommand for ZoomWindowCommand {
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}) })
} }
} }

View file

@ -23,6 +23,8 @@ pub enum TruckObject {
Text(Vec<TextStroke>), Text(Vec<TextStroke>),
/// Pre-computed NaN-separated 3-D point list (leader lines, arrowheads, etc.). /// Pre-computed NaN-separated 3-D point list (leader lines, arrowheads, etc.).
Lines(Vec<[f32; 3]>), Lines(Vec<[f32; 3]>),
/// Like Lines but linetype pattern restarts at each NaN-separated segment (plinegen=false).
SegmentedLines(Vec<[f32; 3]>),
Volume(Solid), Volume(Solid),
} }

View file

@ -202,6 +202,7 @@ pub fn apply_along(
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}) })
.collect() .collect()
} }

View file

@ -3211,6 +3211,7 @@ fn tessellate_entity(
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}); });
return wires; return wires;
} }

View file

@ -148,7 +148,12 @@ impl WireGpu {
let p = wire.points[i - 1]; let p = wire.points[i - 1];
let q = wire.points[i]; let q = wire.points[i];
if !p[0].is_finite() || !q[0].is_finite() { 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 { } else {
let dx = q[0] - p[0]; let dx = q[0] - p[0];
let dy = q[1] - p[1]; let dy = q[1] - p[1];
@ -234,10 +239,13 @@ impl WireGpu {
for i in 1..n { for i in 1..n {
let p = wire.points[i - 1]; let p = wire.points[i - 1];
let q = wire.points[i]; 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() { 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 { } else {
let dx = q[0] - p[0]; let dx = q[0] - p[0];
let dy = q[1] - p[1]; let dy = q[1] - p[1];

View file

@ -14,8 +14,8 @@ use std::collections::HashSet;
use std::f64::consts::TAU; use std::f64::consts::TAU;
use acadrust::entities::acis::{ use acadrust::entities::acis::{
SatCoedge, SatConeSurface, SatDocument, SatEdge, SatFace, SatLoop, SatPlaneSurface, SatPoint, SabReader, SatCoedge, SatConeSurface, SatDocument, SatEdge, SatFace, SatLoop,
SatPointer, SatSphereSurface, SatTorusSurface, SatVertex, SatPlaneSurface, SatPoint, SatPointer, SatSphereSurface, SatTorusSurface, SatVertex,
}; };
use acadrust::entities::acis::types::Sense; use acadrust::entities::acis::types::Sense;
use acadrust::entities::{Body, Region, Solid3D}; 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 }) 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`. /// Tessellate a `Region` entity (2D planar ACIS body) into a `MeshModel`.
pub fn tessellate_region(region: &Region, color: [f32; 4]) -> Option<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(); let name = region.common.handle.value().to_string();
tessellate_sat(&sat, name, color) tessellate_sat(&sat, name, color)
} }
/// Tessellate a `Body` entity (3D ACIS body) into a `MeshModel`. /// Tessellate a `Body` entity (3D ACIS body) into a `MeshModel`.
pub fn tessellate_body(body: &Body, color: [f32; 4]) -> Option<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(); let name = body.common.handle.value().to_string();
tessellate_sat(&sat, name, color) 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 /// Returns `None` when the entity has no parseable SAT data or produces no
/// triangles (e.g. the solid uses only unsupported surface types). /// triangles (e.g. the solid uses only unsupported surface types).
pub fn tessellate_solid3d(solid: &Solid3D, color: [f32; 4]) -> Option<MeshModel> { 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(); let name = solid.common.handle.value().to_string();
tessellate_sat(&sat, name, color) tessellate_sat(&sat, name, color)
} }

View file

@ -104,6 +104,7 @@ pub fn tessellate(
aci: 0, aci: 0,
key_vertices: te.key_vertices, key_vertices: te.key_vertices,
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}; };
} }
@ -132,6 +133,7 @@ pub fn tessellate(
aci: 0, aci: 0,
key_vertices: te.key_vertices, key_vertices: te.key_vertices,
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}; };
} }
_ => {} _ => {}
@ -158,6 +160,7 @@ pub fn tessellate(
aci: 0, aci: 0,
key_vertices, key_vertices,
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}; };
} }
} }
@ -182,6 +185,7 @@ pub fn tessellate(
aci: 0, aci: 0,
key_vertices, key_vertices,
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}; };
} }
} }
@ -212,6 +216,34 @@ pub fn tessellate(
aci: 0, aci: 0,
key_vertices, key_vertices,
aabb: WireModel::UNBOUNDED_AABB, 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, tangent_geoms,
key_vertices, key_vertices,
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
} }
} }
@ -296,6 +329,7 @@ pub fn tessellate_dimension(
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices, key_vertices,
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}]; }];
if let Some(text) = dimension_text_entity(dim) { if let Some(text) = dimension_text_entity(dim) {
@ -344,6 +378,7 @@ fn tessellate_leader(
aci: 0, aci: 0,
key_vertices: vec![], key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}]; }];
} }
@ -412,6 +447,7 @@ fn tessellate_leader(
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices, key_vertices,
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}] }]
} }
@ -521,6 +557,7 @@ fn tessellate_multileader(
aci: 0, aci: 0,
key_vertices: key_verts, key_vertices: key_verts,
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
}]; }];
// Render text content as MText wire // Render text content as MText wire

View file

@ -60,6 +60,9 @@ pub struct WireModel {
/// Preview / interim wires use `UNBOUNDED_AABB` so they are never pre-rejected /// Preview / interim wires use `UNBOUNDED_AABB` so they are never pre-rejected
/// by the snap world-space filter. /// by the snap world-space filter.
pub aabb: [f32; 4], 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 { impl WireModel {
@ -85,6 +88,7 @@ impl WireModel {
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: vec![], key_vertices: vec![],
aabb: Self::UNBOUNDED_AABB, aabb: Self::UNBOUNDED_AABB,
plinegen: true,
} }
} }
@ -184,6 +188,7 @@ impl Default for WireModel {
tangent_geoms: Vec::new(), tangent_geoms: Vec::new(),
key_vertices: Vec::new(), key_vertices: Vec::new(),
aabb: Self::UNBOUNDED_AABB, aabb: Self::UNBOUNDED_AABB,
plinegen: true,
} }
} }
} }