feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
use acadrust::entities::{mesh::Mesh, polygon_mesh::PolygonMesh, Face3D, PolyfaceMesh};
|
2026-04-02 19:13:37 +03:00
|
|
|
|
use glam::Vec3;
|
2026-08-02 22:09:25 +03:00
|
|
|
|
use crate::t;
|
2026-04-02 19:13:37 +03:00
|
|
|
|
|
|
|
|
|
|
use crate::command::EntityTransform;
|
2026-07-26 22:30:14 +03:00
|
|
|
|
use crate::entities::common::{parse_f64, ro_prop as ro, square_grip};
|
refactor: drop truck, and evaluate every curve through the kernel
The last four truck crates are gone from the manifest. What they were doing
falls into three parts, and each now has one answer instead of two.
NURBS evaluation. truck was carrying B-spline curves and surfaces for the
SPLINE entity, hatch boundaries, BLEND's endpoint frames, the spline preview
and ACIS spline-surface faces. The kernel now has both, over one de Boor
written across however many coordinates a control point holds — so a plane
curve and a space curve cannot drift apart, and a surface is the same
algorithm applied twice. The rational and the polynomial cases stop being
separate types: weights absent means polynomial.
The entity conversion. Every LINE, ARC, ELLIPSE, SPLINE and polyline was
built into truck topology purely so it could be sampled back into points.
They are sampled through `entities::curve` now, which is where each one's
geometry is already defined once and what EXTRUDE and REVOLVE read — so a
circle drawn on screen and a circle handed to the Model tab come from the
same definition. That retires four of TruckObject's variants and the module
is renamed for what it does.
SWEEP and LOFT. Both only ever produced a mesh, so both are built from point
lists: a band of quads per span, and a lid where a profile closes. Lofting
profiles of different densities resamples them by distance rather than by
index, so a circle lofted to a square no longer twists.
Two things worth noting for anyone reading the old comments. The tolerance
globals that lived in the tessellation module were never about truck at all —
they are the per-frame chord height, and they move to `curve_tol`. And the
rule that a profile handed over as `Lines` silently broke EXTRUDE and REVOLVE
no longer holds: those read the curve directly and never look at this channel.
The two `automation` test failures are unchanged from before this and are not
caused by it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 01:08:50 +03:00
|
|
|
|
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, RenderConvertible};
|
|
|
|
|
|
use crate::scene::convert::acad_to_render::{RenderEntity, RenderObject};
|
2026-07-26 22:30:14 +03:00
|
|
|
|
use crate::scene::model::object::{GripApply, GripDef, PropSection, PropValue, Property};
|
2026-06-16 11:33:46 +03:00
|
|
|
|
use crate::scene::model::wire_model::SnapHint;
|
2026-04-02 19:13:37 +03:00
|
|
|
|
|
2026-07-04 23:01:59 +03:00
|
|
|
|
/// Triangulate a planar (possibly concave) polygon into a flat triangle-soup
|
|
|
|
|
|
/// (3 vertices per triangle), preserving the polygon's winding. A simple fan
|
|
|
|
|
|
/// from vertex 0 is only valid for convex faces — a concave face (e.g. an
|
|
|
|
|
|
/// L-shaped mesh face) fans into triangles that spill outside the outline. Ear
|
|
|
|
|
|
/// clipping handles both. Falls back to a fan when the polygon is degenerate.
|
fix(pick): catch a wide polyline on its band, not just its centreline
A wide polyline draws a solid band, but hit-testing only ever saw the
centreline 8 px out, so a donut could only be selected by clicking the
phantom circle inside the disc it paints. `self.hatches` — the map the
hatch pick channel walks — is filled from HATCH and SOLID alone, so the
band `wide_fills` produces was drawn (via `synced_hatch_models`) and never
picked.
The band rides in on `pick_tris`, the same channel a thickness wall uses:
`wide_fills` already computes it, so it costs one tessellation and inherits
the block transform. It ear clips rather than fans — an arc band is an
annular sector, concave on its inner edge — reusing `triangulate_planar`.
Wiring `pick_tris` through `Lines` alone was not enough: a plain wide
polyline leaves `to_truck` as `Contour`, and a PLINEGEN=0 one as
`SegmentedLines`. Both now carry it.
This needs the wire's cullable box to cover the band, and it did not.
`bounding_box()` boxes a polyline's stored vertices, knowing nothing of the
width painted around them: a donut's two vertices sit on one horizontal
line, so the box had zero height and rejected every click on the disc
before any triangle was tested. Every site that stamps the entity box onto
a wire now goes through `set_wire_aabb`, which widens it over `pick_tris`
(a no-op for the entities that have none, which is nearly all), and the
block cache chains `pick_tris` into its own box for the same reason.
That box was already too small for the centreline itself — an arc leaves
the vertex box — so a donut was unselectable at its top and bottom at
enough zoom. Widening fixes that too.
Verified end-to-end: tessellate a width-0.5 donut on a 0.05 centreline
radius, then `click_hit` five points 20 px clear of the centreline — all
land, and a click outside the disc still misses.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 15:22:56 +03:00
|
|
|
|
pub(crate) fn triangulate_planar(poly: &[[f64; 3]]) -> Vec<[f64; 3]> {
|
2026-07-04 23:01:59 +03:00
|
|
|
|
let n = poly.len();
|
|
|
|
|
|
if n < 3 {
|
|
|
|
|
|
return Vec::new();
|
|
|
|
|
|
}
|
|
|
|
|
|
if n == 3 {
|
|
|
|
|
|
return vec![poly[0], poly[1], poly[2]];
|
|
|
|
|
|
}
|
|
|
|
|
|
let cross = |a: [f64; 3], b: [f64; 3]| {
|
|
|
|
|
|
[
|
|
|
|
|
|
a[1] * b[2] - a[2] * b[1],
|
|
|
|
|
|
a[2] * b[0] - a[0] * b[2],
|
|
|
|
|
|
a[0] * b[1] - a[1] * b[0],
|
|
|
|
|
|
]
|
|
|
|
|
|
};
|
|
|
|
|
|
let dot = |a: [f64; 3], b: [f64; 3]| a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
|
|
|
|
let fan = || {
|
|
|
|
|
|
let mut out = Vec::new();
|
|
|
|
|
|
for i in 1..n - 1 {
|
|
|
|
|
|
out.push(poly[0]);
|
|
|
|
|
|
out.push(poly[i]);
|
|
|
|
|
|
out.push(poly[i + 1]);
|
|
|
|
|
|
}
|
|
|
|
|
|
out
|
|
|
|
|
|
};
|
|
|
|
|
|
// Face normal via Newell's method (robust for near-planar polygons).
|
|
|
|
|
|
let mut normal = [0.0f64; 3];
|
|
|
|
|
|
for i in 0..n {
|
|
|
|
|
|
let a = poly[i];
|
|
|
|
|
|
let b = poly[(i + 1) % n];
|
|
|
|
|
|
normal[0] += (a[1] - b[1]) * (a[2] + b[2]);
|
|
|
|
|
|
normal[1] += (a[2] - b[2]) * (a[0] + b[0]);
|
|
|
|
|
|
normal[2] += (a[0] - b[0]) * (a[1] + b[1]);
|
|
|
|
|
|
}
|
|
|
|
|
|
let nlen = dot(normal, normal).sqrt();
|
|
|
|
|
|
if nlen < 1e-12 {
|
|
|
|
|
|
return fan();
|
|
|
|
|
|
}
|
|
|
|
|
|
let normal = [normal[0] / nlen, normal[1] / nlen, normal[2] / nlen];
|
|
|
|
|
|
// Orthonormal in-plane basis.
|
|
|
|
|
|
let seed = if normal[0].abs() < 0.9 { [1.0, 0.0, 0.0] } else { [0.0, 1.0, 0.0] };
|
|
|
|
|
|
let mut u = cross(seed, normal);
|
|
|
|
|
|
let ul = dot(u, u).sqrt();
|
|
|
|
|
|
if ul < 1e-12 {
|
|
|
|
|
|
return fan();
|
|
|
|
|
|
}
|
|
|
|
|
|
u = [u[0] / ul, u[1] / ul, u[2] / ul];
|
|
|
|
|
|
let v = cross(normal, u);
|
|
|
|
|
|
let p2: Vec<[f64; 2]> = poly.iter().map(|&p| [dot(p, u), dot(p, v)]).collect();
|
|
|
|
|
|
// Signed area → winding (CCW when positive in the (u, v) frame).
|
|
|
|
|
|
let mut area = 0.0;
|
|
|
|
|
|
for i in 0..n {
|
|
|
|
|
|
let a = p2[i];
|
|
|
|
|
|
let b = p2[(i + 1) % n];
|
|
|
|
|
|
area += a[0] * b[1] - b[0] * a[1];
|
|
|
|
|
|
}
|
|
|
|
|
|
let ccw = area > 0.0;
|
|
|
|
|
|
let tri_area2 = |a: [f64; 2], b: [f64; 2], c: [f64; 2]| {
|
|
|
|
|
|
(b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0])
|
|
|
|
|
|
};
|
|
|
|
|
|
let in_tri = |p: [f64; 2], a: [f64; 2], b: [f64; 2], c: [f64; 2]| {
|
|
|
|
|
|
let d1 = tri_area2(a, b, p);
|
|
|
|
|
|
let d2 = tri_area2(b, c, p);
|
|
|
|
|
|
let d3 = tri_area2(c, a, p);
|
|
|
|
|
|
let neg = d1 < 0.0 || d2 < 0.0 || d3 < 0.0;
|
|
|
|
|
|
let pos = d1 > 0.0 || d2 > 0.0 || d3 > 0.0;
|
|
|
|
|
|
!(neg && pos)
|
|
|
|
|
|
};
|
|
|
|
|
|
let mut idx: Vec<usize> = (0..n).collect();
|
|
|
|
|
|
let mut out: Vec<[f64; 3]> = Vec::with_capacity((n - 2) * 3);
|
|
|
|
|
|
let mut guard = 0usize;
|
|
|
|
|
|
while idx.len() > 3 && guard < n * n {
|
|
|
|
|
|
guard += 1;
|
|
|
|
|
|
let m = idx.len();
|
|
|
|
|
|
let mut clipped = false;
|
|
|
|
|
|
for k in 0..m {
|
|
|
|
|
|
let i0 = idx[(k + m - 1) % m];
|
|
|
|
|
|
let i1 = idx[k];
|
|
|
|
|
|
let i2 = idx[(k + 1) % m];
|
|
|
|
|
|
let (a, b, c) = (p2[i0], p2[i1], p2[i2]);
|
|
|
|
|
|
let convex = if ccw { tri_area2(a, b, c) > 0.0 } else { tri_area2(a, b, c) < 0.0 };
|
|
|
|
|
|
if !convex {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
let mut contains = false;
|
|
|
|
|
|
for &j in &idx {
|
|
|
|
|
|
if j == i0 || j == i1 || j == i2 {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
if in_tri(p2[j], a, b, c) {
|
|
|
|
|
|
contains = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if contains {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
out.push(poly[i0]);
|
|
|
|
|
|
out.push(poly[i1]);
|
|
|
|
|
|
out.push(poly[i2]);
|
|
|
|
|
|
idx.remove(k);
|
|
|
|
|
|
clipped = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
if !clipped {
|
|
|
|
|
|
// No ear found (self-intersecting / numerically degenerate) — bail
|
|
|
|
|
|
// to a fan of the remainder rather than loop forever.
|
|
|
|
|
|
for i in 1..idx.len() - 1 {
|
|
|
|
|
|
out.push(poly[idx[0]]);
|
|
|
|
|
|
out.push(poly[idx[i]]);
|
|
|
|
|
|
out.push(poly[idx[i + 1]]);
|
|
|
|
|
|
}
|
|
|
|
|
|
return out;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if idx.len() == 3 {
|
|
|
|
|
|
out.push(poly[idx[0]]);
|
|
|
|
|
|
out.push(poly[idx[1]]);
|
|
|
|
|
|
out.push(poly[idx[2]]);
|
|
|
|
|
|
}
|
|
|
|
|
|
out
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-02 19:13:37 +03:00
|
|
|
|
// ── Face3D ────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
2026-05-12 23:47:40 +03:00
|
|
|
|
fn v3(v: &acadrust::types::Vector3) -> [f64; 3] {
|
|
|
|
|
|
[v.x, v.y, v.z]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-11 00:04:37 +03:00
|
|
|
|
fn dvec3(v: &acadrust::types::Vector3) -> glam::DVec3 {
|
|
|
|
|
|
glam::DVec3::new(v.x, v.y, v.z)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-12 23:47:40 +03:00
|
|
|
|
fn v3f32(v: &acadrust::types::Vector3) -> [f32; 3] {
|
2026-04-02 19:13:37 +03:00
|
|
|
|
[v.x as f32, v.y as f32, v.z as f32]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
refactor: drop truck, and evaluate every curve through the kernel
The last four truck crates are gone from the manifest. What they were doing
falls into three parts, and each now has one answer instead of two.
NURBS evaluation. truck was carrying B-spline curves and surfaces for the
SPLINE entity, hatch boundaries, BLEND's endpoint frames, the spline preview
and ACIS spline-surface faces. The kernel now has both, over one de Boor
written across however many coordinates a control point holds — so a plane
curve and a space curve cannot drift apart, and a surface is the same
algorithm applied twice. The rational and the polynomial cases stop being
separate types: weights absent means polynomial.
The entity conversion. Every LINE, ARC, ELLIPSE, SPLINE and polyline was
built into truck topology purely so it could be sampled back into points.
They are sampled through `entities::curve` now, which is where each one's
geometry is already defined once and what EXTRUDE and REVOLVE read — so a
circle drawn on screen and a circle handed to the Model tab come from the
same definition. That retires four of TruckObject's variants and the module
is renamed for what it does.
SWEEP and LOFT. Both only ever produced a mesh, so both are built from point
lists: a band of quads per span, and a lid where a profile closes. Lofting
profiles of different densities resamples them by distance rather than by
index, so a circle lofted to a square no longer twists.
Two things worth noting for anyone reading the old comments. The tolerance
globals that lived in the tessellation module were never about truck at all —
they are the per-frame chord height, and they move to `curve_tol`. And the
rule that a profile handed over as `Lines` silently broke EXTRUDE and REVOLVE
no longer holds: those read the curve directly and never look at this channel.
The two `automation` test failures are unchanged from before this and are not
caused by it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 01:08:50 +03:00
|
|
|
|
impl RenderConvertible for Face3D {
|
|
|
|
|
|
fn to_render(&self, _document: &acadrust::CadDocument) -> Option<RenderEntity> {
|
2026-04-02 19:13:37 +03:00
|
|
|
|
let p0 = v3(&self.first_corner);
|
|
|
|
|
|
let p1 = v3(&self.second_corner);
|
|
|
|
|
|
let p2 = v3(&self.third_corner);
|
|
|
|
|
|
let p3 = v3(&self.fourth_corner);
|
2026-05-12 23:47:40 +03:00
|
|
|
|
let p0f = v3f32(&self.first_corner);
|
|
|
|
|
|
let p1f = v3f32(&self.second_corner);
|
|
|
|
|
|
let p2f = v3f32(&self.third_corner);
|
|
|
|
|
|
let p3f = v3f32(&self.fourth_corner);
|
2026-04-02 19:13:37 +03:00
|
|
|
|
let inv = self.invisible_edges;
|
|
|
|
|
|
|
|
|
|
|
|
// Add edge as a line segment (separated by NaN from previous edges).
|
2026-05-12 23:47:40 +03:00
|
|
|
|
let mut pts: Vec<[f64; 3]> = Vec::new();
|
|
|
|
|
|
let mut add_edge = |a: [f64; 3], b: [f64; 3]| {
|
2026-04-02 19:13:37 +03:00
|
|
|
|
if !pts.is_empty() {
|
2026-05-12 23:47:40 +03:00
|
|
|
|
pts.push([f64::NAN; 3]);
|
2026-04-02 19:13:37 +03:00
|
|
|
|
}
|
|
|
|
|
|
pts.push(a);
|
|
|
|
|
|
pts.push(b);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if !inv.is_first_invisible() {
|
|
|
|
|
|
add_edge(p0, p1);
|
|
|
|
|
|
}
|
|
|
|
|
|
if !inv.is_second_invisible() {
|
|
|
|
|
|
add_edge(p1, p2);
|
|
|
|
|
|
}
|
|
|
|
|
|
if !inv.is_third_invisible() {
|
|
|
|
|
|
add_edge(p2, p3);
|
|
|
|
|
|
}
|
|
|
|
|
|
if !inv.is_fourth_invisible() {
|
|
|
|
|
|
add_edge(p3, p0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if pts.is_empty() {
|
|
|
|
|
|
// All edges invisible — show a tiny cross at centroid.
|
|
|
|
|
|
let cx = (p0[0] + p1[0] + p2[0] + p3[0]) / 4.0;
|
|
|
|
|
|
let cy = (p0[1] + p1[1] + p2[1] + p3[1]) / 4.0;
|
|
|
|
|
|
let cz = (p0[2] + p1[2] + p2[2] + p3[2]) / 4.0;
|
2026-05-12 23:47:40 +03:00
|
|
|
|
let s = 0.1_f64;
|
2026-04-02 19:13:37 +03:00
|
|
|
|
pts = vec![[cx - s, cy, cz], [cx + s, cy, cz]];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
refactor: drop truck, and evaluate every curve through the kernel
The last four truck crates are gone from the manifest. What they were doing
falls into three parts, and each now has one answer instead of two.
NURBS evaluation. truck was carrying B-spline curves and surfaces for the
SPLINE entity, hatch boundaries, BLEND's endpoint frames, the spline preview
and ACIS spline-surface faces. The kernel now has both, over one de Boor
written across however many coordinates a control point holds — so a plane
curve and a space curve cannot drift apart, and a surface is the same
algorithm applied twice. The rational and the polynomial cases stop being
separate types: weights absent means polynomial.
The entity conversion. Every LINE, ARC, ELLIPSE, SPLINE and polyline was
built into truck topology purely so it could be sampled back into points.
They are sampled through `entities::curve` now, which is where each one's
geometry is already defined once and what EXTRUDE and REVOLVE read — so a
circle drawn on screen and a circle handed to the Model tab come from the
same definition. That retires four of TruckObject's variants and the module
is renamed for what it does.
SWEEP and LOFT. Both only ever produced a mesh, so both are built from point
lists: a band of quads per span, and a lid where a profile closes. Lofting
profiles of different densities resamples them by distance rather than by
index, so a circle lofted to a square no longer twists.
Two things worth noting for anyone reading the old comments. The tolerance
globals that lived in the tessellation module were never about truck at all —
they are the per-frame chord height, and they move to `curve_tol`. And the
rule that a profile handed over as `Lines` silently broke EXTRUDE and REVOLVE
no longer holds: those read the curve directly and never look at this channel.
The two `automation` test failures are unchanged from before this and are not
caused by it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 01:08:50 +03:00
|
|
|
|
Some(RenderEntity {
|
fix(pick): hit-test lineweight width and thickness walls
Selection tested a flat 8 px radius around each wire's centreline and
nothing else, so two things the user can see were not pickable.
A wire renders as a band `line_weight_px` wide, so pick now widens to that
rendered half-width via `pick_tolerance_px`, mirroring the wire shader's
`select(0.5, half_width, lwdisplay_enable)`. Behaviour is unchanged for
every standard weight (the widest, 2.11 mm, renders 7.97 px half-width —
just under the 8 px threshold); this keeps the two sides from drifting
apart if the display boost in `lineweight_to_px` ever changes.
Entities extruded by a DXF thickness draw their wall as four edges with
nothing in between, so the interior was a hole the cursor fell through.
WireModel gains `pick_tris`: triangles hit-testing treats as solid and the
renderer never uploads. Kept apart from `fill_tris` because that channel
reaches the GPU, and a wall that rendered shaded would change how every
thickness drawing looks. Ranked below `fill_tris` — those are drawn, these
are not, so where they overlap the drawn face is what the user means.
Walls are dropped when a wire is projected into a floating viewport:
`points` are reprojected and clipped into paper coords there, so cloned
model-space triangles would hit-test at a model-scale offset. Extruded
entities stay selectable by their edges, which do get projected.
The wall loop runs on every hover that misses everything else, so it
rejects on the wire's AABB first — an extruded circle is ~128 triangles
and a drawing full of thickness would otherwise project every wall on
each mouse move.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 14:58:55 +03:00
|
|
|
|
pick_tris: Vec::new(),
|
refactor: drop truck, and evaluate every curve through the kernel
The last four truck crates are gone from the manifest. What they were doing
falls into three parts, and each now has one answer instead of two.
NURBS evaluation. truck was carrying B-spline curves and surfaces for the
SPLINE entity, hatch boundaries, BLEND's endpoint frames, the spline preview
and ACIS spline-surface faces. The kernel now has both, over one de Boor
written across however many coordinates a control point holds — so a plane
curve and a space curve cannot drift apart, and a surface is the same
algorithm applied twice. The rational and the polynomial cases stop being
separate types: weights absent means polynomial.
The entity conversion. Every LINE, ARC, ELLIPSE, SPLINE and polyline was
built into truck topology purely so it could be sampled back into points.
They are sampled through `entities::curve` now, which is where each one's
geometry is already defined once and what EXTRUDE and REVOLVE read — so a
circle drawn on screen and a circle handed to the Model tab come from the
same definition. That retires four of TruckObject's variants and the module
is renamed for what it does.
SWEEP and LOFT. Both only ever produced a mesh, so both are built from point
lists: a band of quads per span, and a lid where a profile closes. Lofting
profiles of different densities resamples them by distance rather than by
index, so a circle lofted to a square no longer twists.
Two things worth noting for anyone reading the old comments. The tolerance
globals that lived in the tessellation module were never about truck at all —
they are the per-frame chord height, and they move to `curve_tol`. And the
rule that a profile handed over as `Lines` silently broke EXTRUDE and REVOLVE
no longer holds: those read the curve directly and never look at this channel.
The two `automation` test failures are unchanged from before this and are not
caused by it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 01:08:50 +03:00
|
|
|
|
object: RenderObject::Lines(pts),
|
2026-05-06 10:00:37 +03:00
|
|
|
|
snap_pts: vec![
|
refactor(snap): producers emit f64 snap points (world_offset removal P3)
TruckEntity.snap_pts is now DVec3 and every entity producer builds snap
candidates straight from the acadrust f64 coordinates instead of casting
to f32 first (text, mtext, arc, circle, ellipse, point, shape, solid,
mline, mtext, attribute, tolerance, table, underlay, ole2frame, mesh,
multileader, dimension, block-cache). offset_snap_pts stays f64
throughout. Still offset-relative (inert) — but snap precision is no
longer capped at ~0.5 m by an early f32 cast, which fixes a latent
UTM-scale snap error and sets up the absolute-coordinate switch.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-23 22:10:29 +03:00
|
|
|
|
(Vec3::from(p0f).as_dvec3(), SnapHint::Node),
|
|
|
|
|
|
(Vec3::from(p1f).as_dvec3(), SnapHint::Node),
|
|
|
|
|
|
(Vec3::from(p2f).as_dvec3(), SnapHint::Node),
|
|
|
|
|
|
(Vec3::from(p3f).as_dvec3(), SnapHint::Node),
|
2026-05-06 10:00:37 +03:00
|
|
|
|
],
|
2026-04-02 19:13:37 +03:00
|
|
|
|
tangent_geoms: vec![],
|
|
|
|
|
|
key_vertices: vec![p0, p1, p2, p3],
|
2026-05-06 23:24:26 +03:00
|
|
|
|
fill_tris: vec![],
|
2026-04-02 19:13:37 +03:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl Grippable for Face3D {
|
|
|
|
|
|
fn grips(&self) -> Vec<GripDef> {
|
|
|
|
|
|
vec![
|
2026-06-11 00:04:37 +03:00
|
|
|
|
square_grip(0, dvec3(&self.first_corner)),
|
|
|
|
|
|
square_grip(1, dvec3(&self.second_corner)),
|
|
|
|
|
|
square_grip(2, dvec3(&self.third_corner)),
|
|
|
|
|
|
square_grip(3, dvec3(&self.fourth_corner)),
|
2026-04-02 19:13:37 +03:00
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
|
|
|
|
|
let corner = match grip_id {
|
|
|
|
|
|
0 => &mut self.first_corner,
|
|
|
|
|
|
1 => &mut self.second_corner,
|
|
|
|
|
|
2 => &mut self.third_corner,
|
|
|
|
|
|
3 => &mut self.fourth_corner,
|
|
|
|
|
|
_ => return,
|
|
|
|
|
|
};
|
|
|
|
|
|
match apply {
|
|
|
|
|
|
GripApply::Translate(d) => {
|
|
|
|
|
|
corner.x += d.x as f64;
|
|
|
|
|
|
corner.y += d.y as f64;
|
|
|
|
|
|
corner.z += d.z as f64;
|
|
|
|
|
|
}
|
|
|
|
|
|
GripApply::Absolute(p) => {
|
|
|
|
|
|
corner.x = p.x as f64;
|
|
|
|
|
|
corner.y = p.y as f64;
|
|
|
|
|
|
corner.z = p.z as f64;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl PropertyEditable for Face3D {
|
2026-07-02 22:46:19 +03:00
|
|
|
|
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
|
2026-04-02 19:13:37 +03:00
|
|
|
|
use crate::entities::common::edit_prop as edit;
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
|
let inv = self.invisible_edges;
|
|
|
|
|
|
let edge = |hidden: bool| if hidden { "Invisible" } else { "Visible" };
|
2026-07-02 22:46:19 +03:00
|
|
|
|
vec![PropSection {
|
2026-08-02 22:09:25 +03:00
|
|
|
|
title: t!("Geometry").into_owned(),
|
2026-04-02 19:13:37 +03:00
|
|
|
|
props: vec![
|
2026-08-02 22:09:25 +03:00
|
|
|
|
ro(t!("Current vertex").as_ref(), "f3_current", String::new()),
|
|
|
|
|
|
edit(t!("Vertex 1 X").as_ref(), "f3_p1x", self.first_corner.x),
|
|
|
|
|
|
edit(t!("Vertex 1 Y").as_ref(), "f3_p1y", self.first_corner.y),
|
|
|
|
|
|
edit(t!("Vertex 1 Z").as_ref(), "f3_p1z", self.first_corner.z),
|
|
|
|
|
|
edit(t!("Vertex 2 X").as_ref(), "f3_p2x", self.second_corner.x),
|
|
|
|
|
|
edit(t!("Vertex 2 Y").as_ref(), "f3_p2y", self.second_corner.y),
|
|
|
|
|
|
edit(t!("Vertex 2 Z").as_ref(), "f3_p2z", self.second_corner.z),
|
|
|
|
|
|
edit(t!("Vertex 3 X").as_ref(), "f3_p3x", self.third_corner.x),
|
|
|
|
|
|
edit(t!("Vertex 3 Y").as_ref(), "f3_p3y", self.third_corner.y),
|
|
|
|
|
|
edit(t!("Vertex 3 Z").as_ref(), "f3_p3z", self.third_corner.z),
|
|
|
|
|
|
edit(t!("Vertex 4 X").as_ref(), "f3_p4x", self.fourth_corner.x),
|
|
|
|
|
|
edit(t!("Vertex 4 Y").as_ref(), "f3_p4y", self.fourth_corner.y),
|
|
|
|
|
|
edit(t!("Vertex 4 Z").as_ref(), "f3_p4z", self.fourth_corner.z),
|
|
|
|
|
|
ro(t!("Edge 1").as_ref(), "f3_edge1", edge(inv.is_first_invisible())),
|
|
|
|
|
|
ro(t!("Edge 2").as_ref(), "f3_edge2", edge(inv.is_second_invisible())),
|
|
|
|
|
|
ro(t!("Edge 3").as_ref(), "f3_edge3", edge(inv.is_third_invisible())),
|
|
|
|
|
|
ro(t!("Edge 4").as_ref(), "f3_edge4", edge(inv.is_fourth_invisible())),
|
2026-04-02 19:13:37 +03:00
|
|
|
|
],
|
2026-07-02 22:46:19 +03:00
|
|
|
|
}]
|
2026-04-02 19:13:37 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn apply_geom_prop(&mut self, field: &str, value: &str) {
|
2026-05-12 10:36:15 +03:00
|
|
|
|
let Ok(v) = value.trim().parse::<f64>() else {
|
|
|
|
|
|
return;
|
|
|
|
|
|
};
|
2026-04-02 19:13:37 +03:00
|
|
|
|
match field {
|
|
|
|
|
|
"f3_p1x" => self.first_corner.x = v,
|
|
|
|
|
|
"f3_p1y" => self.first_corner.y = v,
|
|
|
|
|
|
"f3_p1z" => self.first_corner.z = v,
|
|
|
|
|
|
"f3_p2x" => self.second_corner.x = v,
|
|
|
|
|
|
"f3_p2y" => self.second_corner.y = v,
|
|
|
|
|
|
"f3_p2z" => self.second_corner.z = v,
|
|
|
|
|
|
"f3_p3x" => self.third_corner.x = v,
|
|
|
|
|
|
"f3_p3y" => self.third_corner.y = v,
|
|
|
|
|
|
"f3_p3z" => self.third_corner.z = v,
|
|
|
|
|
|
"f3_p4x" => self.fourth_corner.x = v,
|
|
|
|
|
|
"f3_p4y" => self.fourth_corner.y = v,
|
|
|
|
|
|
"f3_p4z" => self.fourth_corner.z = v,
|
|
|
|
|
|
_ => {}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl Transformable for Face3D {
|
|
|
|
|
|
fn apply_transform(&mut self, t: &EntityTransform) {
|
2026-06-16 11:33:46 +03:00
|
|
|
|
crate::scene::view::transform::apply_standard_entity_transform(self, t, |entity, p1, p2| {
|
2026-04-02 19:13:37 +03:00
|
|
|
|
for corner in [
|
|
|
|
|
|
&mut entity.first_corner,
|
|
|
|
|
|
&mut entity.second_corner,
|
|
|
|
|
|
&mut entity.third_corner,
|
|
|
|
|
|
&mut entity.fourth_corner,
|
|
|
|
|
|
] {
|
2026-06-16 11:33:46 +03:00
|
|
|
|
crate::scene::view::transform::reflect_xy_point(&mut corner.x, &mut corner.y, p1, p2);
|
2026-04-02 19:13:37 +03:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── PolygonMesh (N×M grid) ────────────────────────────────────────────────────
|
|
|
|
|
|
|
refactor: drop truck, and evaluate every curve through the kernel
The last four truck crates are gone from the manifest. What they were doing
falls into three parts, and each now has one answer instead of two.
NURBS evaluation. truck was carrying B-spline curves and surfaces for the
SPLINE entity, hatch boundaries, BLEND's endpoint frames, the spline preview
and ACIS spline-surface faces. The kernel now has both, over one de Boor
written across however many coordinates a control point holds — so a plane
curve and a space curve cannot drift apart, and a surface is the same
algorithm applied twice. The rational and the polynomial cases stop being
separate types: weights absent means polynomial.
The entity conversion. Every LINE, ARC, ELLIPSE, SPLINE and polyline was
built into truck topology purely so it could be sampled back into points.
They are sampled through `entities::curve` now, which is where each one's
geometry is already defined once and what EXTRUDE and REVOLVE read — so a
circle drawn on screen and a circle handed to the Model tab come from the
same definition. That retires four of TruckObject's variants and the module
is renamed for what it does.
SWEEP and LOFT. Both only ever produced a mesh, so both are built from point
lists: a band of quads per span, and a lid where a profile closes. Lofting
profiles of different densities resamples them by distance rather than by
index, so a circle lofted to a square no longer twists.
Two things worth noting for anyone reading the old comments. The tolerance
globals that lived in the tessellation module were never about truck at all —
they are the per-frame chord height, and they move to `curve_tol`. And the
rule that a profile handed over as `Lines` silently broke EXTRUDE and REVOLVE
no longer holds: those read the curve directly and never look at this channel.
The two `automation` test failures are unchanged from before this and are not
caused by it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 01:08:50 +03:00
|
|
|
|
impl RenderConvertible for PolygonMesh {
|
|
|
|
|
|
fn to_render(&self, _document: &acadrust::CadDocument) -> Option<RenderEntity> {
|
2026-04-02 19:13:37 +03:00
|
|
|
|
let m = self.m_vertex_count as usize;
|
|
|
|
|
|
let n = self.n_vertex_count as usize;
|
|
|
|
|
|
if m == 0 || n == 0 || self.vertices.len() < m * n {
|
|
|
|
|
|
return None;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
refactor: drop truck, and evaluate every curve through the kernel
The last four truck crates are gone from the manifest. What they were doing
falls into three parts, and each now has one answer instead of two.
NURBS evaluation. truck was carrying B-spline curves and surfaces for the
SPLINE entity, hatch boundaries, BLEND's endpoint frames, the spline preview
and ACIS spline-surface faces. The kernel now has both, over one de Boor
written across however many coordinates a control point holds — so a plane
curve and a space curve cannot drift apart, and a surface is the same
algorithm applied twice. The rational and the polynomial cases stop being
separate types: weights absent means polynomial.
The entity conversion. Every LINE, ARC, ELLIPSE, SPLINE and polyline was
built into truck topology purely so it could be sampled back into points.
They are sampled through `entities::curve` now, which is where each one's
geometry is already defined once and what EXTRUDE and REVOLVE read — so a
circle drawn on screen and a circle handed to the Model tab come from the
same definition. That retires four of TruckObject's variants and the module
is renamed for what it does.
SWEEP and LOFT. Both only ever produced a mesh, so both are built from point
lists: a band of quads per span, and a lid where a profile closes. Lofting
profiles of different densities resamples them by distance rather than by
index, so a circle lofted to a square no longer twists.
Two things worth noting for anyone reading the old comments. The tolerance
globals that lived in the tessellation module were never about truck at all —
they are the per-frame chord height, and they move to `curve_tol`. And the
rule that a profile handed over as `Lines` silently broke EXTRUDE and REVOLVE
no longer holds: those read the curve directly and never look at this channel.
The two `automation` test failures are unchanged from before this and are not
caused by it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 01:08:50 +03:00
|
|
|
|
Some(RenderEntity {
|
fix(pick): hit-test lineweight width and thickness walls
Selection tested a flat 8 px radius around each wire's centreline and
nothing else, so two things the user can see were not pickable.
A wire renders as a band `line_weight_px` wide, so pick now widens to that
rendered half-width via `pick_tolerance_px`, mirroring the wire shader's
`select(0.5, half_width, lwdisplay_enable)`. Behaviour is unchanged for
every standard weight (the widest, 2.11 mm, renders 7.97 px half-width —
just under the 8 px threshold); this keeps the two sides from drifting
apart if the display boost in `lineweight_to_px` ever changes.
Entities extruded by a DXF thickness draw their wall as four edges with
nothing in between, so the interior was a hole the cursor fell through.
WireModel gains `pick_tris`: triangles hit-testing treats as solid and the
renderer never uploads. Kept apart from `fill_tris` because that channel
reaches the GPU, and a wall that rendered shaded would change how every
thickness drawing looks. Ranked below `fill_tris` — those are drawn, these
are not, so where they overlap the drawn face is what the user means.
Walls are dropped when a wire is projected into a floating viewport:
`points` are reprojected and clipped into paper coords there, so cloned
model-space triangles would hit-test at a model-scale offset. Extruded
entities stay selectable by their edges, which do get projected.
The wall loop runs on every hover that misses everything else, so it
rejects on the wire's AABB first — an extruded circle is ~128 triangles
and a drawing full of thickness would otherwise project every wall on
each mouse move.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 14:58:55 +03:00
|
|
|
|
pick_tris: Vec::new(),
|
refactor: drop truck, and evaluate every curve through the kernel
The last four truck crates are gone from the manifest. What they were doing
falls into three parts, and each now has one answer instead of two.
NURBS evaluation. truck was carrying B-spline curves and surfaces for the
SPLINE entity, hatch boundaries, BLEND's endpoint frames, the spline preview
and ACIS spline-surface faces. The kernel now has both, over one de Boor
written across however many coordinates a control point holds — so a plane
curve and a space curve cannot drift apart, and a surface is the same
algorithm applied twice. The rational and the polynomial cases stop being
separate types: weights absent means polynomial.
The entity conversion. Every LINE, ARC, ELLIPSE, SPLINE and polyline was
built into truck topology purely so it could be sampled back into points.
They are sampled through `entities::curve` now, which is where each one's
geometry is already defined once and what EXTRUDE and REVOLVE read — so a
circle drawn on screen and a circle handed to the Model tab come from the
same definition. That retires four of TruckObject's variants and the module
is renamed for what it does.
SWEEP and LOFT. Both only ever produced a mesh, so both are built from point
lists: a band of quads per span, and a lid where a profile closes. Lofting
profiles of different densities resamples them by distance rather than by
index, so a circle lofted to a square no longer twists.
Two things worth noting for anyone reading the old comments. The tolerance
globals that lived in the tessellation module were never about truck at all —
they are the per-frame chord height, and they move to `curve_tol`. And the
rule that a profile handed over as `Lines` silently broke EXTRUDE and REVOLVE
no longer holds: those read the curve directly and never look at this channel.
The two `automation` test failures are unchanged from before this and are not
caused by it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 01:08:50 +03:00
|
|
|
|
object: RenderObject::Lines(Vec::new()),
|
2026-04-02 19:13:37 +03:00
|
|
|
|
snap_pts: vec![],
|
|
|
|
|
|
tangent_geoms: vec![],
|
|
|
|
|
|
key_vertices: vec![],
|
2026-07-26 22:30:14 +03:00
|
|
|
|
fill_tris: vec![],
|
2026-04-02 19:13:37 +03:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl Grippable for PolygonMesh {
|
|
|
|
|
|
fn grips(&self) -> Vec<GripDef> {
|
|
|
|
|
|
self.vertices
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.enumerate()
|
|
|
|
|
|
.map(|(i, v)| {
|
|
|
|
|
|
square_grip(
|
|
|
|
|
|
i,
|
2026-06-11 00:04:37 +03:00
|
|
|
|
glam::DVec3::new(v.location.x, v.location.y, v.location.z),
|
2026-04-02 19:13:37 +03:00
|
|
|
|
)
|
|
|
|
|
|
})
|
|
|
|
|
|
.collect()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
|
|
|
|
|
if let Some(v) = self.vertices.get_mut(grip_id) {
|
|
|
|
|
|
match apply {
|
|
|
|
|
|
GripApply::Translate(d) => {
|
|
|
|
|
|
v.location.x += d.x as f64;
|
|
|
|
|
|
v.location.y += d.y as f64;
|
|
|
|
|
|
v.location.z += d.z as f64;
|
|
|
|
|
|
}
|
|
|
|
|
|
GripApply::Absolute(p) => {
|
|
|
|
|
|
v.location.x = p.x as f64;
|
|
|
|
|
|
v.location.y = p.y as f64;
|
|
|
|
|
|
v.location.z = p.z as f64;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl PropertyEditable for PolygonMesh {
|
2026-07-02 22:46:19 +03:00
|
|
|
|
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
|
feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
let smooth = match self.smooth_type {
|
|
|
|
|
|
acadrust::entities::polygon_mesh::SurfaceSmoothType::NoSmooth => "None",
|
|
|
|
|
|
acadrust::entities::polygon_mesh::SurfaceSmoothType::Quadratic => "Quadratic",
|
|
|
|
|
|
acadrust::entities::polygon_mesh::SurfaceSmoothType::Cubic => "Cubic",
|
|
|
|
|
|
acadrust::entities::polygon_mesh::SurfaceSmoothType::Bezier => "Bezier",
|
|
|
|
|
|
};
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
|
let yesno = |b: bool| if b { "Yes" } else { "No" };
|
|
|
|
|
|
let first = self.vertices.first();
|
|
|
|
|
|
// Grid faces: one quad per cell; closed direction adds a wrap row/column.
|
|
|
|
|
|
let m = self.m_vertex_count.max(0) as i64;
|
|
|
|
|
|
let n = self.n_vertex_count.max(0) as i64;
|
|
|
|
|
|
let cells_m = if self.is_closed_m() { m } else { (m - 1).max(0) };
|
|
|
|
|
|
let cells_n = if self.is_closed_n() { n } else { (n - 1).max(0) };
|
|
|
|
|
|
let face_count = cells_m * cells_n;
|
|
|
|
|
|
vec![
|
|
|
|
|
|
PropSection {
|
2026-08-02 22:09:25 +03:00
|
|
|
|
title: t!("Geometry").into_owned(),
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
|
props: vec![
|
2026-08-02 22:09:25 +03:00
|
|
|
|
ro(t!("Vertex").as_ref(), "pm_vertex", String::new()),
|
|
|
|
|
|
ro(t!("Vertex X").as_ref(),
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
|
"pm_vx",
|
|
|
|
|
|
first.map(|v| format!("{:.4}", v.location.x)).unwrap_or_default(),
|
feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
),
|
2026-08-02 22:09:25 +03:00
|
|
|
|
ro(t!("Vertex Y").as_ref(),
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
|
"pm_vy",
|
|
|
|
|
|
first.map(|v| format!("{:.4}", v.location.y)).unwrap_or_default(),
|
|
|
|
|
|
),
|
2026-08-02 22:09:25 +03:00
|
|
|
|
ro(t!("Vertex Z").as_ref(),
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
|
"pm_vz",
|
|
|
|
|
|
first.map(|v| format!("{:.4}", v.location.z)).unwrap_or_default(),
|
|
|
|
|
|
),
|
2026-08-02 22:09:25 +03:00
|
|
|
|
ro(t!("M vertex count").as_ref(), "pm_m", self.m_vertex_count.to_string()),
|
|
|
|
|
|
ro(t!("N vertex count").as_ref(), "pm_n", self.n_vertex_count.to_string()),
|
|
|
|
|
|
ro(t!("M closed").as_ref(), "pm_closed_m", yesno(self.is_closed_m())),
|
|
|
|
|
|
ro(t!("N closed").as_ref(), "pm_closed_n", yesno(self.is_closed_n())),
|
|
|
|
|
|
ro(t!("M density").as_ref(), "pm_smooth_m", self.m_smooth_density.to_string()),
|
|
|
|
|
|
ro(t!("N density").as_ref(), "pm_smooth_n", self.n_smooth_density.to_string()),
|
|
|
|
|
|
ro(t!("Vertex count").as_ref(), "pm_v", self.vertices.len().to_string()),
|
|
|
|
|
|
ro(t!("Face count").as_ref(), "pm_faces", face_count.to_string()),
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
PropSection {
|
2026-08-02 22:09:25 +03:00
|
|
|
|
title: t!("Misc").into_owned(),
|
|
|
|
|
|
props: vec![ro(t!("Fit/smooth").as_ref(), "pm_smooth", smooth)],
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
|
},
|
|
|
|
|
|
]
|
2026-04-02 19:13:37 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn apply_geom_prop(&mut self, _field: &str, _value: &str) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl Transformable for PolygonMesh {
|
|
|
|
|
|
fn apply_transform(&mut self, t: &EntityTransform) {
|
2026-06-16 11:33:46 +03:00
|
|
|
|
crate::scene::view::transform::apply_standard_entity_transform(self, t, |entity, p1, p2| {
|
2026-04-02 19:13:37 +03:00
|
|
|
|
for v in &mut entity.vertices {
|
2026-06-16 11:33:46 +03:00
|
|
|
|
crate::scene::view::transform::reflect_xy_point(
|
2026-04-02 19:13:37 +03:00
|
|
|
|
&mut v.location.x,
|
|
|
|
|
|
&mut v.location.y,
|
|
|
|
|
|
p1,
|
|
|
|
|
|
p2,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── PolyfaceMesh (arbitrary faces with 1-based vertex indices) ────────────────
|
|
|
|
|
|
|
refactor: drop truck, and evaluate every curve through the kernel
The last four truck crates are gone from the manifest. What they were doing
falls into three parts, and each now has one answer instead of two.
NURBS evaluation. truck was carrying B-spline curves and surfaces for the
SPLINE entity, hatch boundaries, BLEND's endpoint frames, the spline preview
and ACIS spline-surface faces. The kernel now has both, over one de Boor
written across however many coordinates a control point holds — so a plane
curve and a space curve cannot drift apart, and a surface is the same
algorithm applied twice. The rational and the polynomial cases stop being
separate types: weights absent means polynomial.
The entity conversion. Every LINE, ARC, ELLIPSE, SPLINE and polyline was
built into truck topology purely so it could be sampled back into points.
They are sampled through `entities::curve` now, which is where each one's
geometry is already defined once and what EXTRUDE and REVOLVE read — so a
circle drawn on screen and a circle handed to the Model tab come from the
same definition. That retires four of TruckObject's variants and the module
is renamed for what it does.
SWEEP and LOFT. Both only ever produced a mesh, so both are built from point
lists: a band of quads per span, and a lid where a profile closes. Lofting
profiles of different densities resamples them by distance rather than by
index, so a circle lofted to a square no longer twists.
Two things worth noting for anyone reading the old comments. The tolerance
globals that lived in the tessellation module were never about truck at all —
they are the per-frame chord height, and they move to `curve_tol`. And the
rule that a profile handed over as `Lines` silently broke EXTRUDE and REVOLVE
no longer holds: those read the curve directly and never look at this channel.
The two `automation` test failures are unchanged from before this and are not
caused by it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 01:08:50 +03:00
|
|
|
|
impl RenderConvertible for PolyfaceMesh {
|
|
|
|
|
|
fn to_render(&self, _document: &acadrust::CadDocument) -> Option<RenderEntity> {
|
2026-04-02 19:13:37 +03:00
|
|
|
|
if self.vertices.is_empty() || self.faces.is_empty() {
|
|
|
|
|
|
return None;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
refactor: drop truck, and evaluate every curve through the kernel
The last four truck crates are gone from the manifest. What they were doing
falls into three parts, and each now has one answer instead of two.
NURBS evaluation. truck was carrying B-spline curves and surfaces for the
SPLINE entity, hatch boundaries, BLEND's endpoint frames, the spline preview
and ACIS spline-surface faces. The kernel now has both, over one de Boor
written across however many coordinates a control point holds — so a plane
curve and a space curve cannot drift apart, and a surface is the same
algorithm applied twice. The rational and the polynomial cases stop being
separate types: weights absent means polynomial.
The entity conversion. Every LINE, ARC, ELLIPSE, SPLINE and polyline was
built into truck topology purely so it could be sampled back into points.
They are sampled through `entities::curve` now, which is where each one's
geometry is already defined once and what EXTRUDE and REVOLVE read — so a
circle drawn on screen and a circle handed to the Model tab come from the
same definition. That retires four of TruckObject's variants and the module
is renamed for what it does.
SWEEP and LOFT. Both only ever produced a mesh, so both are built from point
lists: a band of quads per span, and a lid where a profile closes. Lofting
profiles of different densities resamples them by distance rather than by
index, so a circle lofted to a square no longer twists.
Two things worth noting for anyone reading the old comments. The tolerance
globals that lived in the tessellation module were never about truck at all —
they are the per-frame chord height, and they move to `curve_tol`. And the
rule that a profile handed over as `Lines` silently broke EXTRUDE and REVOLVE
no longer holds: those read the curve directly and never look at this channel.
The two `automation` test failures are unchanged from before this and are not
caused by it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 01:08:50 +03:00
|
|
|
|
Some(RenderEntity {
|
fix(pick): hit-test lineweight width and thickness walls
Selection tested a flat 8 px radius around each wire's centreline and
nothing else, so two things the user can see were not pickable.
A wire renders as a band `line_weight_px` wide, so pick now widens to that
rendered half-width via `pick_tolerance_px`, mirroring the wire shader's
`select(0.5, half_width, lwdisplay_enable)`. Behaviour is unchanged for
every standard weight (the widest, 2.11 mm, renders 7.97 px half-width —
just under the 8 px threshold); this keeps the two sides from drifting
apart if the display boost in `lineweight_to_px` ever changes.
Entities extruded by a DXF thickness draw their wall as four edges with
nothing in between, so the interior was a hole the cursor fell through.
WireModel gains `pick_tris`: triangles hit-testing treats as solid and the
renderer never uploads. Kept apart from `fill_tris` because that channel
reaches the GPU, and a wall that rendered shaded would change how every
thickness drawing looks. Ranked below `fill_tris` — those are drawn, these
are not, so where they overlap the drawn face is what the user means.
Walls are dropped when a wire is projected into a floating viewport:
`points` are reprojected and clipped into paper coords there, so cloned
model-space triangles would hit-test at a model-scale offset. Extruded
entities stay selectable by their edges, which do get projected.
The wall loop runs on every hover that misses everything else, so it
rejects on the wire's AABB first — an extruded circle is ~128 triangles
and a drawing full of thickness would otherwise project every wall on
each mouse move.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 14:58:55 +03:00
|
|
|
|
pick_tris: Vec::new(),
|
refactor: drop truck, and evaluate every curve through the kernel
The last four truck crates are gone from the manifest. What they were doing
falls into three parts, and each now has one answer instead of two.
NURBS evaluation. truck was carrying B-spline curves and surfaces for the
SPLINE entity, hatch boundaries, BLEND's endpoint frames, the spline preview
and ACIS spline-surface faces. The kernel now has both, over one de Boor
written across however many coordinates a control point holds — so a plane
curve and a space curve cannot drift apart, and a surface is the same
algorithm applied twice. The rational and the polynomial cases stop being
separate types: weights absent means polynomial.
The entity conversion. Every LINE, ARC, ELLIPSE, SPLINE and polyline was
built into truck topology purely so it could be sampled back into points.
They are sampled through `entities::curve` now, which is where each one's
geometry is already defined once and what EXTRUDE and REVOLVE read — so a
circle drawn on screen and a circle handed to the Model tab come from the
same definition. That retires four of TruckObject's variants and the module
is renamed for what it does.
SWEEP and LOFT. Both only ever produced a mesh, so both are built from point
lists: a band of quads per span, and a lid where a profile closes. Lofting
profiles of different densities resamples them by distance rather than by
index, so a circle lofted to a square no longer twists.
Two things worth noting for anyone reading the old comments. The tolerance
globals that lived in the tessellation module were never about truck at all —
they are the per-frame chord height, and they move to `curve_tol`. And the
rule that a profile handed over as `Lines` silently broke EXTRUDE and REVOLVE
no longer holds: those read the curve directly and never look at this channel.
The two `automation` test failures are unchanged from before this and are not
caused by it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 01:08:50 +03:00
|
|
|
|
object: RenderObject::Lines(Vec::new()),
|
2026-04-02 19:13:37 +03:00
|
|
|
|
snap_pts: vec![],
|
|
|
|
|
|
tangent_geoms: vec![],
|
|
|
|
|
|
key_vertices: vec![],
|
2026-07-26 22:30:14 +03:00
|
|
|
|
fill_tris: vec![],
|
2026-04-02 19:13:37 +03:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl Grippable for PolyfaceMesh {
|
|
|
|
|
|
fn grips(&self) -> Vec<GripDef> {
|
|
|
|
|
|
self.vertices
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.enumerate()
|
|
|
|
|
|
.map(|(i, v)| {
|
|
|
|
|
|
square_grip(
|
|
|
|
|
|
i,
|
2026-06-11 00:04:37 +03:00
|
|
|
|
glam::DVec3::new(v.location.x, v.location.y, v.location.z),
|
2026-04-02 19:13:37 +03:00
|
|
|
|
)
|
|
|
|
|
|
})
|
|
|
|
|
|
.collect()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
|
|
|
|
|
if let Some(v) = self.vertices.get_mut(grip_id) {
|
|
|
|
|
|
match apply {
|
|
|
|
|
|
GripApply::Translate(d) => {
|
|
|
|
|
|
v.location.x += d.x as f64;
|
|
|
|
|
|
v.location.y += d.y as f64;
|
|
|
|
|
|
v.location.z += d.z as f64;
|
|
|
|
|
|
}
|
|
|
|
|
|
GripApply::Absolute(p) => {
|
|
|
|
|
|
v.location.x = p.x as f64;
|
|
|
|
|
|
v.location.y = p.y as f64;
|
|
|
|
|
|
v.location.z = p.z as f64;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl PropertyEditable for PolyfaceMesh {
|
2026-07-02 22:46:19 +03:00
|
|
|
|
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
|
feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
let smooth = match self.smooth_surface {
|
|
|
|
|
|
acadrust::entities::PolyfaceSmoothType::None => "None",
|
|
|
|
|
|
acadrust::entities::PolyfaceSmoothType::Quadratic => "Quadratic",
|
|
|
|
|
|
acadrust::entities::PolyfaceSmoothType::Cubic => "Cubic",
|
|
|
|
|
|
acadrust::entities::PolyfaceSmoothType::Bezier => "Bezier",
|
|
|
|
|
|
};
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
|
let first = self.vertices.first();
|
|
|
|
|
|
vec![
|
|
|
|
|
|
PropSection {
|
2026-08-02 22:09:25 +03:00
|
|
|
|
title: t!("Geometry").into_owned(),
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
|
props: vec![
|
2026-08-02 22:09:25 +03:00
|
|
|
|
ro(t!("Vertex").as_ref(), "pfm_vertex", String::new()),
|
|
|
|
|
|
ro(t!("Vertex X").as_ref(),
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
|
"pfm_vx",
|
|
|
|
|
|
first.map(|v| format!("{:.4}", v.location.x)).unwrap_or_default(),
|
feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
),
|
2026-08-02 22:09:25 +03:00
|
|
|
|
ro(t!("Vertex Y").as_ref(),
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
|
"pfm_vy",
|
|
|
|
|
|
first.map(|v| format!("{:.4}", v.location.y)).unwrap_or_default(),
|
|
|
|
|
|
),
|
2026-08-02 22:09:25 +03:00
|
|
|
|
ro(t!("Vertex Z").as_ref(),
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
|
"pfm_vz",
|
|
|
|
|
|
first.map(|v| format!("{:.4}", v.location.z)).unwrap_or_default(),
|
|
|
|
|
|
),
|
|
|
|
|
|
// Polyface meshes store an explicit vertex/face list rather
|
|
|
|
|
|
// than an M×N grid, so the grid-only rows are not applicable.
|
2026-08-02 22:09:25 +03:00
|
|
|
|
ro(t!("M vertex count").as_ref(), "pfm_m", String::new()),
|
|
|
|
|
|
ro(t!("N vertex count").as_ref(), "pfm_n", String::new()),
|
|
|
|
|
|
ro(t!("M closed").as_ref(), "pfm_closed_m", String::new()),
|
|
|
|
|
|
ro(t!("N closed").as_ref(), "pfm_closed_n", String::new()),
|
|
|
|
|
|
ro(t!("M density").as_ref(), "pfm_density_m", String::new()),
|
|
|
|
|
|
ro(t!("N density").as_ref(), "pfm_density_n", String::new()),
|
|
|
|
|
|
ro(t!("Vertex count").as_ref(), "pfm_v", self.vertices.len().to_string()),
|
|
|
|
|
|
ro(t!("Face count").as_ref(), "pfm_f", self.faces.len().to_string()),
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
PropSection {
|
2026-08-02 22:09:25 +03:00
|
|
|
|
title: t!("Misc").into_owned(),
|
|
|
|
|
|
props: vec![ro(t!("Fit/smooth").as_ref(), "pfm_smooth", smooth)],
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
|
},
|
|
|
|
|
|
]
|
2026-04-02 19:13:37 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn apply_geom_prop(&mut self, _field: &str, _value: &str) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl Transformable for PolyfaceMesh {
|
|
|
|
|
|
fn apply_transform(&mut self, t: &EntityTransform) {
|
2026-06-16 11:33:46 +03:00
|
|
|
|
crate::scene::view::transform::apply_standard_entity_transform(self, t, |entity, p1, p2| {
|
2026-04-02 19:13:37 +03:00
|
|
|
|
for v in &mut entity.vertices {
|
2026-06-16 11:33:46 +03:00
|
|
|
|
crate::scene::view::transform::reflect_xy_point(
|
2026-04-02 19:13:37 +03:00
|
|
|
|
&mut v.location.x,
|
|
|
|
|
|
&mut v.location.y,
|
|
|
|
|
|
p1,
|
|
|
|
|
|
p2,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
|
|
|
|
|
|
// ── Mesh (SubD mesh) ──────────────────────────────────────────────────────────
|
|
|
|
|
|
//
|
|
|
|
|
|
// Modern subdivision mesh — distinct from PolygonMesh. The render path emits
|
2026-07-26 22:30:14 +03:00
|
|
|
|
// the refined per-edge wireframe and triangulates each face into fill_tris so
|
|
|
|
|
|
// solid views draw the same Catmull-Clark surface described by the DWG.
|
feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
|
2026-07-26 22:30:14 +03:00
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
|
struct RefinedMesh {
|
|
|
|
|
|
vertices: Vec<[f64; 3]>,
|
|
|
|
|
|
faces: Vec<Vec<usize>>,
|
|
|
|
|
|
creases: std::collections::HashMap<(usize, usize), f64>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn edge_key(a: usize, b: usize) -> (usize, usize) {
|
|
|
|
|
|
if a < b { (a, b) } else { (b, a) }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn add3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
|
|
|
|
|
|
[a[0] + b[0], a[1] + b[1], a[2] + b[2]]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn mul3(a: [f64; 3], scale: f64) -> [f64; 3] {
|
|
|
|
|
|
[a[0] * scale, a[1] * scale, a[2] * scale]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn mix3(a: [f64; 3], b: [f64; 3], amount: f64) -> [f64; 3] {
|
|
|
|
|
|
add3(mul3(a, 1.0 - amount), mul3(b, amount))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn mean_points<'a>(points: impl Iterator<Item = &'a [f64; 3]>) -> [f64; 3] {
|
|
|
|
|
|
let mut sum = [0.0; 3];
|
|
|
|
|
|
let mut count = 0usize;
|
|
|
|
|
|
for point in points {
|
|
|
|
|
|
sum = add3(sum, *point);
|
|
|
|
|
|
count += 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if count == 0 {
|
|
|
|
|
|
sum
|
|
|
|
|
|
} else {
|
|
|
|
|
|
mul3(sum, 1.0 / count as f64)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn base_refined_mesh(mesh: &Mesh) -> RefinedMesh {
|
|
|
|
|
|
let vertices: Vec<[f64; 3]> = mesh.vertices.iter().map(|v| [v.x, v.y, v.z]).collect();
|
|
|
|
|
|
let faces = mesh
|
|
|
|
|
|
.faces
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.filter_map(|face| {
|
|
|
|
|
|
let valid: Vec<usize> = face
|
|
|
|
|
|
.vertices
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.copied()
|
|
|
|
|
|
.filter(|&index| index < vertices.len())
|
|
|
|
|
|
.collect();
|
|
|
|
|
|
(valid.len() >= 3).then_some(valid)
|
|
|
|
|
|
})
|
|
|
|
|
|
.collect();
|
|
|
|
|
|
let creases = mesh
|
|
|
|
|
|
.edges
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.filter_map(|edge| {
|
|
|
|
|
|
let sharpness = edge.crease?;
|
|
|
|
|
|
(edge.start < vertices.len() && edge.end < vertices.len() && sharpness > 0.0)
|
|
|
|
|
|
.then_some((edge_key(edge.start, edge.end), sharpness))
|
|
|
|
|
|
})
|
|
|
|
|
|
.collect();
|
|
|
|
|
|
RefinedMesh {
|
|
|
|
|
|
vertices,
|
|
|
|
|
|
faces,
|
|
|
|
|
|
creases,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn subdivide_catmull_clark(mesh: &RefinedMesh, blend_crease: bool) -> RefinedMesh {
|
|
|
|
|
|
use std::collections::{HashMap, HashSet};
|
|
|
|
|
|
|
|
|
|
|
|
if mesh.faces.is_empty() {
|
|
|
|
|
|
return mesh.clone();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let face_points: Vec<[f64; 3]> = mesh
|
|
|
|
|
|
.faces
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.map(|face| mean_points(face.iter().filter_map(|&index| mesh.vertices.get(index))))
|
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
|
|
let mut edge_faces: HashMap<(usize, usize), Vec<usize>> = HashMap::new();
|
|
|
|
|
|
let mut vertex_faces: Vec<Vec<usize>> = vec![Vec::new(); mesh.vertices.len()];
|
|
|
|
|
|
let mut vertex_edges: Vec<HashSet<(usize, usize)>> = vec![HashSet::new(); mesh.vertices.len()];
|
|
|
|
|
|
for (face_index, face) in mesh.faces.iter().enumerate() {
|
|
|
|
|
|
for &vertex in face {
|
|
|
|
|
|
if let Some(faces) = vertex_faces.get_mut(vertex) {
|
|
|
|
|
|
faces.push(face_index);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
for corner in 0..face.len() {
|
|
|
|
|
|
let a = face[corner];
|
|
|
|
|
|
let b = face[(corner + 1) % face.len()];
|
|
|
|
|
|
if a == b {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
let key = edge_key(a, b);
|
|
|
|
|
|
edge_faces.entry(key).or_default().push(face_index);
|
|
|
|
|
|
if let Some(edges) = vertex_edges.get_mut(a) {
|
|
|
|
|
|
edges.insert(key);
|
|
|
|
|
|
}
|
|
|
|
|
|
if let Some(edges) = vertex_edges.get_mut(b) {
|
|
|
|
|
|
edges.insert(key);
|
|
|
|
|
|
}
|
feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
}
|
2026-07-26 22:30:14 +03:00
|
|
|
|
}
|
feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
|
2026-07-26 22:30:14 +03:00
|
|
|
|
let mut vertex_points = Vec::with_capacity(mesh.vertices.len());
|
|
|
|
|
|
for (vertex_index, &point) in mesh.vertices.iter().enumerate() {
|
|
|
|
|
|
let incident_edges = &vertex_edges[vertex_index];
|
|
|
|
|
|
let incident_faces = &vertex_faces[vertex_index];
|
|
|
|
|
|
if incident_edges.is_empty() || incident_faces.is_empty() {
|
|
|
|
|
|
vertex_points.push(point);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let face_average =
|
|
|
|
|
|
mean_points(incident_faces.iter().filter_map(|&index| face_points.get(index)));
|
|
|
|
|
|
let edge_midpoints: Vec<[f64; 3]> = incident_edges
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.filter_map(|&(a, b)| {
|
|
|
|
|
|
let other = if a == vertex_index { b } else { a };
|
|
|
|
|
|
mesh.vertices
|
|
|
|
|
|
.get(other)
|
|
|
|
|
|
.map(|&p| mul3(add3(point, p), 0.5))
|
|
|
|
|
|
})
|
|
|
|
|
|
.collect();
|
|
|
|
|
|
let edge_average = mean_points(edge_midpoints.iter());
|
|
|
|
|
|
let n = incident_faces.len() as f64;
|
|
|
|
|
|
let smooth = mul3(
|
|
|
|
|
|
add3(add3(face_average, mul3(edge_average, 2.0)), mul3(point, n - 3.0)),
|
|
|
|
|
|
1.0 / n,
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
let mut sharp_neighbours: Vec<(f64, [f64; 3])> = incident_edges
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.filter_map(|&key| {
|
|
|
|
|
|
let boundary = edge_faces.get(&key).is_some_and(|faces| faces.len() == 1);
|
|
|
|
|
|
let sharpness = if boundary {
|
|
|
|
|
|
f64::INFINITY
|
|
|
|
|
|
} else {
|
|
|
|
|
|
mesh.creases.get(&key).copied().unwrap_or(0.0)
|
|
|
|
|
|
};
|
|
|
|
|
|
if sharpness <= 0.0 {
|
|
|
|
|
|
return None;
|
feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
}
|
2026-07-26 22:30:14 +03:00
|
|
|
|
let other = if key.0 == vertex_index { key.1 } else { key.0 };
|
|
|
|
|
|
mesh.vertices.get(other).copied().map(|p| (sharpness, p))
|
|
|
|
|
|
})
|
|
|
|
|
|
.collect();
|
|
|
|
|
|
sharp_neighbours.sort_by(|a, b| b.0.total_cmp(&a.0));
|
|
|
|
|
|
|
|
|
|
|
|
let crease_point = match sharp_neighbours.as_slice() {
|
|
|
|
|
|
[(_, first), (_, second), ..] => mul3(
|
|
|
|
|
|
add3(add3(mul3(point, 6.0), *first), *second),
|
|
|
|
|
|
1.0 / 8.0,
|
|
|
|
|
|
),
|
|
|
|
|
|
_ => smooth,
|
|
|
|
|
|
};
|
|
|
|
|
|
let corner_point = if sharp_neighbours.len() >= 3 {
|
|
|
|
|
|
point
|
feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
} else {
|
2026-07-26 22:30:14 +03:00
|
|
|
|
crease_point
|
|
|
|
|
|
};
|
|
|
|
|
|
let sharpness = sharp_neighbours
|
|
|
|
|
|
.get(1)
|
|
|
|
|
|
.map(|item| item.0)
|
|
|
|
|
|
.unwrap_or(0.0);
|
|
|
|
|
|
let amount = if blend_crease {
|
|
|
|
|
|
sharpness.clamp(0.0, 1.0)
|
|
|
|
|
|
} else if sharpness > 0.0 {
|
|
|
|
|
|
1.0
|
|
|
|
|
|
} else {
|
|
|
|
|
|
0.0
|
|
|
|
|
|
};
|
|
|
|
|
|
vertex_points.push(mix3(smooth, corner_point, amount));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let mut edge_point_indices = HashMap::with_capacity(edge_faces.len());
|
|
|
|
|
|
let mut vertices = vertex_points;
|
|
|
|
|
|
for (&key, adjacent_faces) in &edge_faces {
|
|
|
|
|
|
let Some((&a, &b)) = mesh.vertices.get(key.0).zip(mesh.vertices.get(key.1)) else {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
};
|
|
|
|
|
|
let midpoint = mul3(add3(a, b), 0.5);
|
|
|
|
|
|
let smooth = if adjacent_faces.len() == 2 {
|
|
|
|
|
|
let f0 = face_points[adjacent_faces[0]];
|
|
|
|
|
|
let f1 = face_points[adjacent_faces[1]];
|
|
|
|
|
|
mul3(add3(add3(a, b), add3(f0, f1)), 0.25)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
midpoint
|
|
|
|
|
|
};
|
|
|
|
|
|
let boundary = adjacent_faces.len() != 2;
|
|
|
|
|
|
let sharpness = mesh.creases.get(&key).copied().unwrap_or(0.0);
|
|
|
|
|
|
let amount = if boundary {
|
|
|
|
|
|
1.0
|
|
|
|
|
|
} else if blend_crease {
|
|
|
|
|
|
sharpness.clamp(0.0, 1.0)
|
|
|
|
|
|
} else if sharpness > 0.0 {
|
|
|
|
|
|
1.0
|
|
|
|
|
|
} else {
|
|
|
|
|
|
0.0
|
|
|
|
|
|
};
|
|
|
|
|
|
edge_point_indices.insert(key, vertices.len());
|
|
|
|
|
|
vertices.push(mix3(smooth, midpoint, amount));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let face_point_start = vertices.len();
|
|
|
|
|
|
vertices.extend(face_points.iter().copied());
|
|
|
|
|
|
let mut faces = Vec::new();
|
|
|
|
|
|
let mut creases = HashMap::new();
|
|
|
|
|
|
for (face_index, face) in mesh.faces.iter().enumerate() {
|
|
|
|
|
|
for corner in 0..face.len() {
|
|
|
|
|
|
let vertex = face[corner];
|
|
|
|
|
|
let next_key = edge_key(vertex, face[(corner + 1) % face.len()]);
|
|
|
|
|
|
let prev_key = edge_key(face[(corner + face.len() - 1) % face.len()], vertex);
|
|
|
|
|
|
let (Some(&next_edge), Some(&prev_edge)) = (
|
|
|
|
|
|
edge_point_indices.get(&next_key),
|
|
|
|
|
|
edge_point_indices.get(&prev_key),
|
|
|
|
|
|
) else {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
};
|
|
|
|
|
|
faces.push(vec![
|
|
|
|
|
|
vertex,
|
|
|
|
|
|
next_edge,
|
|
|
|
|
|
face_point_start + face_index,
|
|
|
|
|
|
prev_edge,
|
|
|
|
|
|
]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (&key, &sharpness) in &mesh.creases {
|
|
|
|
|
|
let Some(&edge_point) = edge_point_indices.get(&key) else {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
};
|
|
|
|
|
|
let child_sharpness = (sharpness - 1.0).max(0.0);
|
|
|
|
|
|
if child_sharpness > 0.0 {
|
|
|
|
|
|
creases.insert(edge_key(key.0, edge_point), child_sharpness);
|
|
|
|
|
|
creases.insert(edge_key(edge_point, key.1), child_sharpness);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
RefinedMesh {
|
|
|
|
|
|
vertices,
|
|
|
|
|
|
faces,
|
|
|
|
|
|
creases,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn display_mesh(mesh: &Mesh) -> RefinedMesh {
|
|
|
|
|
|
let mut refined = base_refined_mesh(mesh);
|
|
|
|
|
|
for _ in 0..mesh.subdivision_level.clamp(0, 4) {
|
|
|
|
|
|
refined = subdivide_catmull_clark(&refined, mesh.blend_crease);
|
|
|
|
|
|
}
|
|
|
|
|
|
refined
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn face_triangle_indices(
|
|
|
|
|
|
vertices: &[[f64; 3]],
|
|
|
|
|
|
faces: &[Vec<usize>],
|
|
|
|
|
|
) -> (Vec<u32>, Vec<usize>) {
|
|
|
|
|
|
let mut indices = Vec::new();
|
|
|
|
|
|
let mut triangle_faces = Vec::new();
|
|
|
|
|
|
for (face_index, face) in faces.iter().enumerate() {
|
|
|
|
|
|
let polygon: Vec<[f64; 3]> = face
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.filter_map(|&index| vertices.get(index).copied())
|
|
|
|
|
|
.collect();
|
|
|
|
|
|
if polygon.len() < 3 {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
for triangle in triangulate_planar(&polygon).chunks_exact(3) {
|
|
|
|
|
|
let mut mapped = [0u32; 3];
|
|
|
|
|
|
let mut valid = true;
|
|
|
|
|
|
for corner in 0..3 {
|
|
|
|
|
|
let Some(local) = polygon.iter().position(|point| *point == triangle[corner]) else {
|
|
|
|
|
|
valid = false;
|
|
|
|
|
|
break;
|
|
|
|
|
|
};
|
|
|
|
|
|
mapped[corner] = face[local] as u32;
|
|
|
|
|
|
}
|
|
|
|
|
|
if valid && mapped[0] != mapped[1] && mapped[1] != mapped[2] && mapped[2] != mapped[0] {
|
|
|
|
|
|
indices.extend(mapped);
|
|
|
|
|
|
triangle_faces.push(face_index);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
(indices, triangle_faces)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn vertex_normals(vertices: &[[f64; 3]], indices: &[u32]) -> Vec<[f32; 3]> {
|
|
|
|
|
|
let mut normals = vec![[0.0f64; 3]; vertices.len()];
|
|
|
|
|
|
for triangle in indices.chunks_exact(3) {
|
|
|
|
|
|
let (Some(&a), Some(&b), Some(&c)) = (
|
|
|
|
|
|
vertices.get(triangle[0] as usize),
|
|
|
|
|
|
vertices.get(triangle[1] as usize),
|
|
|
|
|
|
vertices.get(triangle[2] as usize),
|
|
|
|
|
|
) else {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
};
|
|
|
|
|
|
let ab = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
|
|
|
|
|
|
let ac = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
|
|
|
|
|
|
let normal = [
|
|
|
|
|
|
ab[1] * ac[2] - ab[2] * ac[1],
|
|
|
|
|
|
ab[2] * ac[0] - ab[0] * ac[2],
|
|
|
|
|
|
ab[0] * ac[1] - ab[1] * ac[0],
|
|
|
|
|
|
];
|
|
|
|
|
|
for &index in triangle {
|
|
|
|
|
|
if let Some(sum) = normals.get_mut(index as usize) {
|
|
|
|
|
|
*sum = add3(*sum, normal);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
normals
|
|
|
|
|
|
.into_iter()
|
|
|
|
|
|
.map(|normal| {
|
|
|
|
|
|
let length =
|
|
|
|
|
|
(normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]).sqrt();
|
|
|
|
|
|
if length > 1e-15 {
|
|
|
|
|
|
[
|
|
|
|
|
|
(normal[0] / length) as f32,
|
|
|
|
|
|
(normal[1] / length) as f32,
|
|
|
|
|
|
(normal[2] / length) as f32,
|
|
|
|
|
|
]
|
|
|
|
|
|
} else {
|
|
|
|
|
|
[0.0, 0.0, 1.0]
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.collect()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn append_feature_edge(
|
|
|
|
|
|
edge_verts: &mut Vec<[f32; 3]>,
|
|
|
|
|
|
edge_verts_low: &mut Vec<[f32; 3]>,
|
|
|
|
|
|
vertices: &[[f64; 3]],
|
|
|
|
|
|
a: usize,
|
|
|
|
|
|
b: usize,
|
|
|
|
|
|
) {
|
|
|
|
|
|
for index in [a, b] {
|
|
|
|
|
|
let Some(&[x, y, z]) = vertices.get(index) else {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
};
|
|
|
|
|
|
let high = [x as f32, y as f32, z as f32];
|
|
|
|
|
|
edge_verts.push(high);
|
|
|
|
|
|
edge_verts_low.push([
|
|
|
|
|
|
(x - high[0] as f64) as f32,
|
|
|
|
|
|
(y - high[1] as f64) as f32,
|
|
|
|
|
|
(z - high[2] as f64) as f32,
|
|
|
|
|
|
]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn make_mesh_lod_set(
|
|
|
|
|
|
name: String,
|
|
|
|
|
|
color: [f32; 4],
|
|
|
|
|
|
vertices: Vec<[f64; 3]>,
|
|
|
|
|
|
faces: Vec<Vec<usize>>,
|
|
|
|
|
|
visible_edges: std::collections::HashSet<(usize, usize)>,
|
|
|
|
|
|
face_colors: &[Option<[f32; 4]>],
|
|
|
|
|
|
) -> Option<crate::scene::model::mesh_model::MeshLodSet> {
|
|
|
|
|
|
let (indices, triangle_faces) = face_triangle_indices(&vertices, &faces);
|
|
|
|
|
|
if indices.is_empty() {
|
|
|
|
|
|
return None;
|
|
|
|
|
|
}
|
|
|
|
|
|
let normals = vertex_normals(&vertices, &indices);
|
|
|
|
|
|
let triangle_colors = triangle_faces
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.map(|&face| face_colors.get(face).copied().flatten())
|
|
|
|
|
|
.collect();
|
|
|
|
|
|
let model = crate::scene::convert::solid3d_tess::finalize_mesh(
|
|
|
|
|
|
name,
|
|
|
|
|
|
vertices.clone(),
|
|
|
|
|
|
normals,
|
|
|
|
|
|
indices,
|
|
|
|
|
|
Vec::new(),
|
|
|
|
|
|
triangle_colors,
|
|
|
|
|
|
color,
|
|
|
|
|
|
None,
|
|
|
|
|
|
);
|
|
|
|
|
|
let mut set = crate::scene::model::mesh_model::MeshLodSet::from_lods(vec![model]);
|
|
|
|
|
|
for (a, b) in visible_edges {
|
|
|
|
|
|
append_feature_edge(
|
|
|
|
|
|
&mut set.edge_verts,
|
|
|
|
|
|
&mut set.edge_verts_low,
|
|
|
|
|
|
&vertices,
|
|
|
|
|
|
a,
|
|
|
|
|
|
b,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
Some(set)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Convert every standard DWG mesh family to the material-aware shaded mesh
|
|
|
|
|
|
/// pipeline. The retained wire entity carries snaps/grips only; fill, normals,
|
|
|
|
|
|
/// depth, materials and face colours live in `MeshLodSet`.
|
|
|
|
|
|
pub(crate) fn tessellate_shaded_mesh(
|
|
|
|
|
|
entity: &acadrust::EntityType,
|
|
|
|
|
|
color: [f32; 4],
|
|
|
|
|
|
) -> Option<crate::scene::model::mesh_model::MeshLodSet> {
|
|
|
|
|
|
use std::collections::HashSet;
|
|
|
|
|
|
|
|
|
|
|
|
match entity {
|
|
|
|
|
|
acadrust::EntityType::Mesh(mesh) => {
|
|
|
|
|
|
let display = display_mesh(mesh);
|
|
|
|
|
|
let mut edges = HashSet::new();
|
|
|
|
|
|
for face in &display.faces {
|
|
|
|
|
|
for corner in 0..face.len() {
|
|
|
|
|
|
edges.insert(edge_key(face[corner], face[(corner + 1) % face.len()]));
|
feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
}
|
2026-07-26 22:30:14 +03:00
|
|
|
|
}
|
|
|
|
|
|
make_mesh_lod_set(
|
|
|
|
|
|
mesh.common.handle.value().to_string(),
|
|
|
|
|
|
color,
|
|
|
|
|
|
display.vertices,
|
|
|
|
|
|
display.faces,
|
|
|
|
|
|
edges,
|
|
|
|
|
|
&[],
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
acadrust::EntityType::PolygonMesh(mesh) => {
|
|
|
|
|
|
let m = mesh.m_vertex_count.max(0) as usize;
|
|
|
|
|
|
let n = mesh.n_vertex_count.max(0) as usize;
|
|
|
|
|
|
if m == 0 || n == 0 || mesh.vertices.len() < m.saturating_mul(n) {
|
|
|
|
|
|
return None;
|
|
|
|
|
|
}
|
|
|
|
|
|
let vertices: Vec<[f64; 3]> = mesh
|
|
|
|
|
|
.vertices
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.take(m * n)
|
|
|
|
|
|
.map(|vertex| {
|
|
|
|
|
|
[
|
|
|
|
|
|
vertex.location.x,
|
|
|
|
|
|
vertex.location.y,
|
|
|
|
|
|
vertex.location.z,
|
|
|
|
|
|
]
|
|
|
|
|
|
})
|
|
|
|
|
|
.collect();
|
|
|
|
|
|
let closed_m = mesh.is_closed_m();
|
|
|
|
|
|
let closed_n = mesh.is_closed_n();
|
|
|
|
|
|
let m_cells = if closed_m { m } else { m.saturating_sub(1) };
|
|
|
|
|
|
let n_cells = if closed_n { n } else { n.saturating_sub(1) };
|
|
|
|
|
|
let mut faces = Vec::with_capacity(m_cells.saturating_mul(n_cells));
|
|
|
|
|
|
let mut edges = HashSet::new();
|
|
|
|
|
|
for i in 0..m_cells {
|
|
|
|
|
|
for j in 0..n_cells {
|
|
|
|
|
|
let face = vec![
|
|
|
|
|
|
i * n + j,
|
|
|
|
|
|
((i + 1) % m) * n + j,
|
|
|
|
|
|
((i + 1) % m) * n + (j + 1) % n,
|
|
|
|
|
|
i * n + (j + 1) % n,
|
|
|
|
|
|
];
|
|
|
|
|
|
for corner in 0..4 {
|
|
|
|
|
|
edges.insert(edge_key(face[corner], face[(corner + 1) % 4]));
|
feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
}
|
2026-07-26 22:30:14 +03:00
|
|
|
|
faces.push(face);
|
feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
}
|
2026-07-26 22:30:14 +03:00
|
|
|
|
}
|
|
|
|
|
|
make_mesh_lod_set(
|
|
|
|
|
|
mesh.common.handle.value().to_string(),
|
|
|
|
|
|
color,
|
|
|
|
|
|
vertices,
|
|
|
|
|
|
faces,
|
|
|
|
|
|
edges,
|
|
|
|
|
|
&[],
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
acadrust::EntityType::PolyfaceMesh(mesh) => {
|
|
|
|
|
|
let vertices: Vec<[f64; 3]> = mesh
|
|
|
|
|
|
.vertices
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.map(|vertex| {
|
|
|
|
|
|
[
|
|
|
|
|
|
vertex.location.x,
|
|
|
|
|
|
vertex.location.y,
|
|
|
|
|
|
vertex.location.z,
|
|
|
|
|
|
]
|
|
|
|
|
|
})
|
|
|
|
|
|
.collect();
|
|
|
|
|
|
let mut faces = Vec::new();
|
|
|
|
|
|
let mut face_colors = Vec::new();
|
|
|
|
|
|
let mut edges = HashSet::new();
|
|
|
|
|
|
for face in &mesh.faces {
|
|
|
|
|
|
let raw = [face.index1, face.index2, face.index3, face.index4];
|
|
|
|
|
|
let indices: Vec<usize> = raw
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.filter(|&&index| index != 0)
|
|
|
|
|
|
.filter_map(|&index| (index.unsigned_abs() as usize).checked_sub(1))
|
|
|
|
|
|
.filter(|&index| index < vertices.len())
|
|
|
|
|
|
.collect();
|
|
|
|
|
|
if indices.len() < 3 {
|
|
|
|
|
|
continue;
|
feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
}
|
2026-07-26 22:30:14 +03:00
|
|
|
|
for corner in 0..indices.len() {
|
|
|
|
|
|
if raw[corner] > 0 {
|
|
|
|
|
|
edges.insert(edge_key(indices[corner], indices[(corner + 1) % indices.len()]));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
faces.push(indices);
|
|
|
|
|
|
face_colors.push(face.color.as_ref().map(crate::scene::convert::tess_util::aci_to_rgba));
|
feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
}
|
2026-07-26 22:30:14 +03:00
|
|
|
|
make_mesh_lod_set(
|
|
|
|
|
|
mesh.common.handle.value().to_string(),
|
|
|
|
|
|
color,
|
|
|
|
|
|
vertices,
|
|
|
|
|
|
faces,
|
|
|
|
|
|
edges,
|
|
|
|
|
|
&face_colors,
|
|
|
|
|
|
)
|
feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
}
|
2026-07-26 22:30:14 +03:00
|
|
|
|
_ => None,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
|
refactor: drop truck, and evaluate every curve through the kernel
The last four truck crates are gone from the manifest. What they were doing
falls into three parts, and each now has one answer instead of two.
NURBS evaluation. truck was carrying B-spline curves and surfaces for the
SPLINE entity, hatch boundaries, BLEND's endpoint frames, the spline preview
and ACIS spline-surface faces. The kernel now has both, over one de Boor
written across however many coordinates a control point holds — so a plane
curve and a space curve cannot drift apart, and a surface is the same
algorithm applied twice. The rational and the polynomial cases stop being
separate types: weights absent means polynomial.
The entity conversion. Every LINE, ARC, ELLIPSE, SPLINE and polyline was
built into truck topology purely so it could be sampled back into points.
They are sampled through `entities::curve` now, which is where each one's
geometry is already defined once and what EXTRUDE and REVOLVE read — so a
circle drawn on screen and a circle handed to the Model tab come from the
same definition. That retires four of TruckObject's variants and the module
is renamed for what it does.
SWEEP and LOFT. Both only ever produced a mesh, so both are built from point
lists: a band of quads per span, and a lid where a profile closes. Lofting
profiles of different densities resamples them by distance rather than by
index, so a circle lofted to a square no longer twists.
Two things worth noting for anyone reading the old comments. The tolerance
globals that lived in the tessellation module were never about truck at all —
they are the per-frame chord height, and they move to `curve_tol`. And the
rule that a profile handed over as `Lines` silently broke EXTRUDE and REVOLVE
no longer holds: those read the curve directly and never look at this channel.
The two `automation` test failures are unchanged from before this and are not
caused by it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 01:08:50 +03:00
|
|
|
|
impl RenderConvertible for Mesh {
|
|
|
|
|
|
fn to_render(&self, _document: &acadrust::CadDocument) -> Option<RenderEntity> {
|
2026-07-26 22:30:14 +03:00
|
|
|
|
if self.vertices.is_empty() {
|
|
|
|
|
|
return None;
|
feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-22 12:59:07 -07:00
|
|
|
|
// Per-vertex snap tables cost ~56 B/vertex and are retained in every
|
|
|
|
|
|
// copy of the wire set. On NWD-scale imports (#358: tens of millions
|
|
|
|
|
|
// of mesh vertices) that is GBs for snap targets far too dense to
|
|
|
|
|
|
// pick apart on screen — so past this size ship none, which is what
|
|
|
|
|
|
// PolyfaceMesh / PolygonMesh already do at any size.
|
|
|
|
|
|
const SNAP_TABLE_MAX_VERTICES: usize = 50_000;
|
|
|
|
|
|
let (snap_pts, key_vertices) = if self.vertices.len() > SNAP_TABLE_MAX_VERTICES {
|
|
|
|
|
|
(Vec::new(), Vec::new())
|
|
|
|
|
|
} else {
|
|
|
|
|
|
(
|
|
|
|
|
|
self.vertices
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.map(|v| (glam::DVec3::new(v.x, v.y, v.z), SnapHint::Node))
|
|
|
|
|
|
.collect(),
|
|
|
|
|
|
self.vertices.iter().map(|v| [v.x, v.y, v.z]).collect(),
|
|
|
|
|
|
)
|
|
|
|
|
|
};
|
feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
|
refactor: drop truck, and evaluate every curve through the kernel
The last four truck crates are gone from the manifest. What they were doing
falls into three parts, and each now has one answer instead of two.
NURBS evaluation. truck was carrying B-spline curves and surfaces for the
SPLINE entity, hatch boundaries, BLEND's endpoint frames, the spline preview
and ACIS spline-surface faces. The kernel now has both, over one de Boor
written across however many coordinates a control point holds — so a plane
curve and a space curve cannot drift apart, and a surface is the same
algorithm applied twice. The rational and the polynomial cases stop being
separate types: weights absent means polynomial.
The entity conversion. Every LINE, ARC, ELLIPSE, SPLINE and polyline was
built into truck topology purely so it could be sampled back into points.
They are sampled through `entities::curve` now, which is where each one's
geometry is already defined once and what EXTRUDE and REVOLVE read — so a
circle drawn on screen and a circle handed to the Model tab come from the
same definition. That retires four of TruckObject's variants and the module
is renamed for what it does.
SWEEP and LOFT. Both only ever produced a mesh, so both are built from point
lists: a band of quads per span, and a lid where a profile closes. Lofting
profiles of different densities resamples them by distance rather than by
index, so a circle lofted to a square no longer twists.
Two things worth noting for anyone reading the old comments. The tolerance
globals that lived in the tessellation module were never about truck at all —
they are the per-frame chord height, and they move to `curve_tol`. And the
rule that a profile handed over as `Lines` silently broke EXTRUDE and REVOLVE
no longer holds: those read the curve directly and never look at this channel.
The two `automation` test failures are unchanged from before this and are not
caused by it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 01:08:50 +03:00
|
|
|
|
Some(RenderEntity {
|
fix(pick): hit-test lineweight width and thickness walls
Selection tested a flat 8 px radius around each wire's centreline and
nothing else, so two things the user can see were not pickable.
A wire renders as a band `line_weight_px` wide, so pick now widens to that
rendered half-width via `pick_tolerance_px`, mirroring the wire shader's
`select(0.5, half_width, lwdisplay_enable)`. Behaviour is unchanged for
every standard weight (the widest, 2.11 mm, renders 7.97 px half-width —
just under the 8 px threshold); this keeps the two sides from drifting
apart if the display boost in `lineweight_to_px` ever changes.
Entities extruded by a DXF thickness draw their wall as four edges with
nothing in between, so the interior was a hole the cursor fell through.
WireModel gains `pick_tris`: triangles hit-testing treats as solid and the
renderer never uploads. Kept apart from `fill_tris` because that channel
reaches the GPU, and a wall that rendered shaded would change how every
thickness drawing looks. Ranked below `fill_tris` — those are drawn, these
are not, so where they overlap the drawn face is what the user means.
Walls are dropped when a wire is projected into a floating viewport:
`points` are reprojected and clipped into paper coords there, so cloned
model-space triangles would hit-test at a model-scale offset. Extruded
entities stay selectable by their edges, which do get projected.
The wall loop runs on every hover that misses everything else, so it
rejects on the wire's AABB first — an extruded circle is ~128 triangles
and a drawing full of thickness would otherwise project every wall on
each mouse move.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 14:58:55 +03:00
|
|
|
|
pick_tris: Vec::new(),
|
refactor: drop truck, and evaluate every curve through the kernel
The last four truck crates are gone from the manifest. What they were doing
falls into three parts, and each now has one answer instead of two.
NURBS evaluation. truck was carrying B-spline curves and surfaces for the
SPLINE entity, hatch boundaries, BLEND's endpoint frames, the spline preview
and ACIS spline-surface faces. The kernel now has both, over one de Boor
written across however many coordinates a control point holds — so a plane
curve and a space curve cannot drift apart, and a surface is the same
algorithm applied twice. The rational and the polynomial cases stop being
separate types: weights absent means polynomial.
The entity conversion. Every LINE, ARC, ELLIPSE, SPLINE and polyline was
built into truck topology purely so it could be sampled back into points.
They are sampled through `entities::curve` now, which is where each one's
geometry is already defined once and what EXTRUDE and REVOLVE read — so a
circle drawn on screen and a circle handed to the Model tab come from the
same definition. That retires four of TruckObject's variants and the module
is renamed for what it does.
SWEEP and LOFT. Both only ever produced a mesh, so both are built from point
lists: a band of quads per span, and a lid where a profile closes. Lofting
profiles of different densities resamples them by distance rather than by
index, so a circle lofted to a square no longer twists.
Two things worth noting for anyone reading the old comments. The tolerance
globals that lived in the tessellation module were never about truck at all —
they are the per-frame chord height, and they move to `curve_tol`. And the
rule that a profile handed over as `Lines` silently broke EXTRUDE and REVOLVE
no longer holds: those read the curve directly and never look at this channel.
The two `automation` test failures are unchanged from before this and are not
caused by it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 01:08:50 +03:00
|
|
|
|
object: RenderObject::Lines(Vec::new()),
|
feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
snap_pts,
|
|
|
|
|
|
tangent_geoms: vec![],
|
|
|
|
|
|
key_vertices,
|
2026-07-26 22:30:14 +03:00
|
|
|
|
fill_tris: vec![],
|
feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl Grippable for Mesh {
|
|
|
|
|
|
fn grips(&self) -> Vec<GripDef> {
|
2026-08-12 19:49:52 +03:00
|
|
|
|
Vec::new()
|
feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
|
|
|
|
|
if let Some(v) = self.vertices.get_mut(grip_id) {
|
|
|
|
|
|
match apply {
|
|
|
|
|
|
GripApply::Translate(d) => {
|
|
|
|
|
|
v.x += d.x as f64;
|
|
|
|
|
|
v.y += d.y as f64;
|
|
|
|
|
|
v.z += d.z as f64;
|
|
|
|
|
|
}
|
|
|
|
|
|
GripApply::Absolute(p) => {
|
|
|
|
|
|
v.x = p.x as f64;
|
|
|
|
|
|
v.y = p.y as f64;
|
|
|
|
|
|
v.z = p.z as f64;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl PropertyEditable for Mesh {
|
2026-07-02 22:46:19 +03:00
|
|
|
|
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
|
// Watertight when every face edge is shared by exactly two faces
|
|
|
|
|
|
// (closed manifold). Empty meshes are not watertight.
|
|
|
|
|
|
let mut edge_use: std::collections::HashMap<(usize, usize), u32> =
|
|
|
|
|
|
std::collections::HashMap::new();
|
|
|
|
|
|
for face in &self.faces {
|
|
|
|
|
|
let vs = &face.vertices;
|
|
|
|
|
|
for i in 0..vs.len() {
|
|
|
|
|
|
let a = vs[i];
|
|
|
|
|
|
let b = vs[(i + 1) % vs.len()];
|
|
|
|
|
|
let key = if a < b { (a, b) } else { (b, a) };
|
|
|
|
|
|
*edge_use.entry(key).or_insert(0) += 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
let watertight =
|
|
|
|
|
|
!self.faces.is_empty() && edge_use.values().all(|&c| c == 2);
|
2026-07-02 22:46:19 +03:00
|
|
|
|
vec![PropSection {
|
2026-08-02 22:09:25 +03:00
|
|
|
|
title: t!("Geometry").into_owned(),
|
feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
props: vec![
|
2026-07-26 22:30:14 +03:00
|
|
|
|
Property {
|
2026-08-02 22:09:25 +03:00
|
|
|
|
label: t!("Level of Smoothness").into_owned(),
|
2026-07-26 22:30:14 +03:00
|
|
|
|
field: "msh_subdiv_edit",
|
|
|
|
|
|
value: PropValue::EditText(self.subdivision_level.to_string()),
|
|
|
|
|
|
},
|
|
|
|
|
|
Property {
|
2026-08-02 22:09:25 +03:00
|
|
|
|
label: t!("Blend Creases").into_owned(),
|
2026-07-26 22:30:14 +03:00
|
|
|
|
field: "msh_blend_crease",
|
|
|
|
|
|
value: PropValue::BoolToggle {
|
|
|
|
|
|
field: "msh_blend_crease",
|
|
|
|
|
|
value: self.blend_crease,
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
2026-08-02 22:09:25 +03:00
|
|
|
|
ro(t!("Number of Faces").as_ref(), "msh_f", self.faces.len().to_string()),
|
|
|
|
|
|
ro(t!("Number of Vertices").as_ref(), "msh_v", self.vertices.len().to_string()),
|
|
|
|
|
|
ro(t!("Number of Edges").as_ref(), "msh_e", self.edges.len().to_string()),
|
|
|
|
|
|
ro(t!("Creased Edges").as_ref(),
|
2026-07-26 22:30:14 +03:00
|
|
|
|
"msh_creased",
|
|
|
|
|
|
self.edges
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.filter(|edge| edge.crease.is_some_and(|value| value > 0.0))
|
|
|
|
|
|
.count()
|
|
|
|
|
|
.to_string(),
|
|
|
|
|
|
),
|
2026-08-02 22:09:25 +03:00
|
|
|
|
ro(t!("Override Option").as_ref(),
|
2026-07-26 22:30:14 +03:00
|
|
|
|
"msh_override",
|
|
|
|
|
|
self.override_option.to_string(),
|
feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
),
|
2026-08-02 22:09:25 +03:00
|
|
|
|
ro(t!("Number of Grips").as_ref(), "msh_grips", self.vertices.len().to_string()),
|
|
|
|
|
|
ro(t!("Watertight").as_ref(),
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
|
"msh_watertight",
|
|
|
|
|
|
if watertight { "Yes" } else { "No" },
|
feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
),
|
|
|
|
|
|
],
|
2026-07-02 22:46:19 +03:00
|
|
|
|
}]
|
feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-26 22:30:14 +03:00
|
|
|
|
fn apply_geom_prop(&mut self, field: &str, value: &str) {
|
|
|
|
|
|
match field {
|
|
|
|
|
|
"msh_subdiv_edit" => {
|
|
|
|
|
|
if let Some(value) = parse_f64(value) {
|
|
|
|
|
|
self.subdivision_level = (value.round() as i32).clamp(0, 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
"msh_blend_crease" => {
|
|
|
|
|
|
self.blend_crease = match value {
|
|
|
|
|
|
"toggle" => !self.blend_crease,
|
|
|
|
|
|
"On" | "Yes" | "true" | "1" => true,
|
|
|
|
|
|
"Off" | "No" | "false" | "0" => false,
|
|
|
|
|
|
_ => self.blend_crease,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
_ => {}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl Transformable for Mesh {
|
|
|
|
|
|
fn apply_transform(&mut self, t: &EntityTransform) {
|
2026-06-16 11:33:46 +03:00
|
|
|
|
crate::scene::view::transform::apply_standard_entity_transform(self, t, |entity, p1, p2| {
|
feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
for v in &mut entity.vertices {
|
2026-06-16 11:33:46 +03:00
|
|
|
|
crate::scene::view::transform::reflect_xy_point(&mut v.x, &mut v.y, p1, p2);
|
feat(entities): surface remaining 3D fields + add Mesh render path
Closes out the 3D-entity audit. Mesh (modern SubD mesh) gained a full
render impl since it had no TruckConvertible / Grippable / PropertyEditable
hookup at all — it was silently dropped on open. Remaining mesh-style
entities pick up the few fields the parser populated:
- Mesh (SubD): wireframe + face-fan fill_tris rendering, per-vertex grips,
Properties panel surfaces version, subdivision_level, blend_crease,
creased-edge count, vertex / face / edge counts; wired into the
Transformable / Grippable / PropertyEditable / TruckConvertible
dispatch in entities/traits.rs
- Polyline3D: smooth_type, default_start_width, default_end_width,
mesh_m_count, mesh_n_count, smooth_m_density, smooth_n_density
(widths editable)
- PolygonMesh: smooth_type, m/n_smooth_density, elevation, normal
- PolyfaceMesh: smooth_surface, seqend_handle, elevation, normal,
thickness
Solid3D / Region / Body's uid + silhouettes + history_handle were
included in the prior 2D commit since they share the same file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:35:14 +03:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|