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
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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];
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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];
|
||||||
|
|
|
||||||
|
|
@ -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, ®ion.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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue