2026-04-02 18:51:38 +03:00
|
|
|
use acadrust::entities::{Polyline, Polyline2D, Polyline3D};
|
2026-08-02 22:09:25 +03:00
|
|
|
use crate::t;
|
2026-04-02 18:51:38 +03:00
|
|
|
|
|
|
|
|
use crate::command::EntityTransform;
|
2026-07-08 17:53:05 +03:00
|
|
|
use crate::entities::common::{
|
|
|
|
|
edit_prop as edit, parse_f64, ro_prop as ro, square_grip, stepper_prop as stepper,
|
|
|
|
|
};
|
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::{extrusion_wall_tris, RenderEntity, RenderObject};
|
2026-06-16 11:33:46 +03:00
|
|
|
use crate::scene::model::object::{GripApply, GripDef, PropSection, PropValue, Property};
|
|
|
|
|
use crate::scene::model::wire_model::TangentGeom;
|
2026-04-02 18:51:38 +03:00
|
|
|
|
|
|
|
|
// ── Polyline (old-style 3D heavy polyline) ────────────────────────────────────
|
|
|
|
|
|
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
|
|
|
fn tessellate_polyline(pl: &Polyline) -> RenderEntity {
|
2026-05-12 23:47:40 +03:00
|
|
|
let pts: Vec<[f64; 3]> = pl
|
2026-04-02 18:51:38 +03:00
|
|
|
.vertices
|
|
|
|
|
.iter()
|
2026-05-12 23:47:40 +03:00
|
|
|
.map(|v| [v.location.x, v.location.y, v.location.z])
|
2026-04-02 18:51:38 +03:00
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
let mut points = pts.clone();
|
|
|
|
|
if pl.flags.is_closed() && pts.len() >= 2 {
|
|
|
|
|
points.push(pts[0]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let key_verts = pts.clone();
|
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
|
|
|
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(points),
|
2026-04-02 18:51:38 +03:00
|
|
|
snap_pts: vec![],
|
|
|
|
|
tangent_geoms: vec![],
|
|
|
|
|
key_vertices: key_verts,
|
2026-05-06 23:24:26 +03:00
|
|
|
fill_tris: vec![],
|
2026-04-02 18:51:38 +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 Polyline {
|
|
|
|
|
fn to_render(&self, _document: &acadrust::CadDocument) -> Option<RenderEntity> {
|
2026-04-02 18:51:38 +03:00
|
|
|
Some(tessellate_polyline(self))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Grippable for Polyline {
|
|
|
|
|
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 18:51:38 +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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
|
2026-06-16 11:33:46 +03:00
|
|
|
fn grip_menu(&self, _grip_id: usize) -> Vec<crate::scene::model::object::GripMenuItem> {
|
|
|
|
|
use crate::scene::model::object::{GripMenuAction, GripMenuItem};
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
vec![
|
2026-06-11 00:04:37 +03:00
|
|
|
GripMenuItem {
|
|
|
|
|
label: "Stretch",
|
|
|
|
|
action: GripMenuAction::Stretch,
|
|
|
|
|
},
|
|
|
|
|
GripMenuItem {
|
|
|
|
|
label: "Add Vertex",
|
|
|
|
|
action: GripMenuAction::AddVertex,
|
|
|
|
|
},
|
|
|
|
|
GripMenuItem {
|
|
|
|
|
label: "Remove Vertex",
|
|
|
|
|
action: GripMenuAction::RemoveVertex,
|
|
|
|
|
},
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-16 11:33:46 +03:00
|
|
|
fn apply_grip_menu(&mut self, grip_id: usize, action: crate::scene::model::object::GripMenuAction) {
|
|
|
|
|
use crate::scene::model::object::GripMenuAction as A;
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
let n = self.vertices.len();
|
|
|
|
|
match action {
|
|
|
|
|
A::AddVertex if grip_id < n => {
|
2026-07-31 23:36:07 +03:00
|
|
|
if grip_id == n - 1 && !self.is_closed() {
|
|
|
|
|
self.vertices.push(self.vertices[grip_id].clone());
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
return;
|
|
|
|
|
}
|
2026-07-31 23:36:07 +03:00
|
|
|
let i1 = (grip_id + 1) % n;
|
|
|
|
|
let v0 = self.vertices[grip_id].clone();
|
|
|
|
|
let v1 = self.vertices[i1].clone();
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
let mx = (v0.location.x + v1.location.x) * 0.5;
|
|
|
|
|
let my = (v0.location.y + v1.location.y) * 0.5;
|
|
|
|
|
let mz = (v0.location.z + v1.location.z) * 0.5;
|
|
|
|
|
let mut new_v = v0.clone();
|
|
|
|
|
new_v.location.x = mx;
|
|
|
|
|
new_v.location.y = my;
|
|
|
|
|
new_v.location.z = mz;
|
|
|
|
|
let insert_at = (grip_id + 1).min(self.vertices.len());
|
|
|
|
|
self.vertices.insert(insert_at, new_v);
|
|
|
|
|
}
|
|
|
|
|
A::RemoveVertex if grip_id < n && n > 2 => {
|
|
|
|
|
self.vertices.remove(grip_id);
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-02 18:51:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PropertyEditable for Polyline {
|
2026-07-02 22:46:19 +03:00
|
|
|
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
|
|
|
|
|
vec![PropSection {
|
2026-08-02 22:09:25 +03:00
|
|
|
title: t!("Geometry").into_owned(),
|
2026-04-02 18:51:38 +03:00
|
|
|
props: vec![
|
2026-08-02 22:09:25 +03:00
|
|
|
ro(t!("Vertices").as_ref(), "vertices", self.vertices.len().to_string()),
|
2026-04-02 18:51:38 +03:00
|
|
|
Property {
|
2026-08-02 22:09:25 +03:00
|
|
|
label: t!("Closed").into_owned(),
|
2026-04-02 18:51:38 +03:00
|
|
|
field: "pl_closed",
|
|
|
|
|
value: PropValue::BoolToggle {
|
|
|
|
|
field: "pl_closed",
|
|
|
|
|
value: self.flags.is_closed(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
2026-07-02 22:46:19 +03:00
|
|
|
}]
|
2026-04-02 18:51:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apply_geom_prop(&mut self, field: &str, value: &str) {
|
|
|
|
|
if field == "pl_closed" {
|
|
|
|
|
let closed = if value == "toggle" {
|
|
|
|
|
!self.flags.is_closed()
|
|
|
|
|
} else {
|
|
|
|
|
value == "true"
|
|
|
|
|
};
|
|
|
|
|
self.flags.set_closed(closed);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Transformable for Polyline {
|
|
|
|
|
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 18:51:38 +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 18:51:38 +03:00
|
|
|
&mut v.location.x,
|
|
|
|
|
&mut v.location.y,
|
|
|
|
|
p1,
|
|
|
|
|
p2,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Polyline2D (heavy 2D polyline with bulge) ─────────────────────────────────
|
|
|
|
|
|
2026-07-21 15:12:52 +03:00
|
|
|
/// The vertices a curve-/spline-fit 2D polyline actually DRAWS: the
|
|
|
|
|
/// fit-generated points (VertexFlags 8 / 1) plus any unflagged originals,
|
|
|
|
|
/// with the spline-frame CONTROL points (flag 16) dropped — those are editing
|
|
|
|
|
/// scaffolding shown only under SPLFRAME, and chaining through them draws
|
|
|
|
|
/// spokes across the fitted curve (#408). `None` = no fit data, draw the
|
|
|
|
|
/// stored vertices as-is. A fit-flagged polyline whose fit points are missing
|
|
|
|
|
/// falls back to the stored vertices rather than drawing nothing.
|
|
|
|
|
pub fn drawn_vertices2d(
|
|
|
|
|
pl: &Polyline2D,
|
|
|
|
|
) -> Option<Vec<acadrust::entities::Vertex2D>> {
|
|
|
|
|
use acadrust::entities::polyline::VertexFlags;
|
|
|
|
|
let has_fit = pl.vertices.iter().any(|v| {
|
|
|
|
|
v.flags.bits()
|
|
|
|
|
& (VertexFlags::SPLINE_VERTEX.bits() | VertexFlags::EXTRA_VERTEX.bits())
|
|
|
|
|
!= 0
|
|
|
|
|
});
|
|
|
|
|
if !has_fit {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
let kept: Vec<_> = pl
|
|
|
|
|
.vertices
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|v| v.flags.bits() & VertexFlags::SPLINE_CONTROL.bits() == 0)
|
|
|
|
|
.cloned()
|
|
|
|
|
.collect();
|
|
|
|
|
(kept.len() >= 2).then_some(kept)
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
fn tessellate_polyline2d(pl: &Polyline2D) -> RenderEntity {
|
2026-07-21 15:12:52 +03:00
|
|
|
let filtered = drawn_vertices2d(pl);
|
|
|
|
|
let verts: &[acadrust::entities::Vertex2D] = filtered.as_deref().unwrap_or(&pl.vertices);
|
2026-04-02 18:51:38 +03:00
|
|
|
if verts.is_empty() {
|
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
|
|
|
return 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![]),
|
2026-04-02 18:51:38 +03:00
|
|
|
snap_pts: vec![],
|
|
|
|
|
tangent_geoms: vec![],
|
|
|
|
|
key_vertices: vec![],
|
2026-05-06 23:24:26 +03:00
|
|
|
fill_tris: vec![],
|
2026-04-02 18:51:38 +03:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let elev = pl.elevation;
|
2026-05-02 20:04:30 +03:00
|
|
|
let normal = (pl.normal.x, pl.normal.y, pl.normal.z);
|
2026-04-02 18:51:38 +03:00
|
|
|
let count = verts.len();
|
|
|
|
|
let seg_count = if pl.is_closed() { count } else { count - 1 };
|
|
|
|
|
let mut tangents: Vec<TangentGeom> = Vec::new();
|
2026-05-12 23:47:40 +03:00
|
|
|
let mut key_verts: Vec<[f64; 3]> = Vec::new();
|
2026-04-02 18:51:38 +03:00
|
|
|
|
2026-05-02 20:04:30 +03:00
|
|
|
let to_wcs = |x: f64, y: f64| -> (f64, f64, f64) {
|
2026-06-16 11:33:46 +03:00
|
|
|
crate::scene::view::transform::ocs_point_to_wcs((x, y, elev), normal)
|
2026-05-02 20:04:30 +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
|
|
|
let to_pt = |v: &acadrust::entities::Vertex2D| -> [f64; 3] {
|
2026-05-02 20:04:30 +03:00
|
|
|
let (wx, wy, wz) = to_wcs(v.location.x, v.location.y);
|
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
|
|
|
[wx, wy, wz]
|
2026-04-02 18:51:38 +03:00
|
|
|
};
|
|
|
|
|
|
2026-05-05 20:03:57 +03:00
|
|
|
if pl.thickness.abs() > 1e-10 {
|
|
|
|
|
let (nx, ny, nz) = normal;
|
|
|
|
|
let t = pl.thickness;
|
2026-06-11 00:04:37 +03:00
|
|
|
let off = |p: [f64; 3]| -> [f64; 3] { [p[0] + t * nx, p[1] + t * ny, p[2] + t * nz] };
|
|
|
|
|
let to_f32 = |p: [f64; 3]| -> [f32; 3] { [p[0] as f32, p[1] as f32, p[2] as f32] };
|
2026-05-12 23:47:40 +03:00
|
|
|
let mut path: Vec<[f64; 3]> = Vec::new();
|
|
|
|
|
let mut kv: Vec<[f64; 3]> = Vec::new();
|
2026-05-05 20:03:57 +03:00
|
|
|
let mut tgs: Vec<TangentGeom> = Vec::new();
|
|
|
|
|
let (w0x, w0y, w0z) = to_wcs(verts[0].location.x, verts[0].location.y);
|
2026-05-12 23:47:40 +03:00
|
|
|
path.push([w0x, w0y, w0z]);
|
|
|
|
|
kv.push([w0x, w0y, w0z]);
|
2026-05-05 20:03:57 +03:00
|
|
|
for i in 0..seg_count {
|
|
|
|
|
let va = &verts[i];
|
|
|
|
|
let vb = &verts[(i + 1) % count];
|
|
|
|
|
let (ox0, oy0) = (va.location.x, va.location.y);
|
|
|
|
|
let (ox1, oy1) = (vb.location.x, vb.location.y);
|
|
|
|
|
let bulge = va.bulge;
|
|
|
|
|
if bulge.abs() < 1e-9 {
|
|
|
|
|
let (wx, wy, wz) = to_wcs(ox1, oy1);
|
2026-05-12 23:47:40 +03:00
|
|
|
path.push([wx, wy, wz]);
|
|
|
|
|
let p1_pt = path[path.len() - 2];
|
|
|
|
|
let p2_pt = *path.last().unwrap();
|
2026-05-12 10:36:15 +03:00
|
|
|
tgs.push(TangentGeom::Line {
|
2026-05-12 23:47:40 +03:00
|
|
|
p1: to_f32(p1_pt),
|
|
|
|
|
p2: to_f32(p2_pt),
|
2026-05-12 10:36:15 +03:00
|
|
|
});
|
refactor(common): unify bulge → arc math into BulgeArc
The same DXF/DWG bulge → (center, radius, sweep) formula was inlined
in six places (hatch outline, polyline tess + truck path, lwpolyline
×4, scene hatch fill, explode bulge_to_arc) — each with subtly
different sweep-direction handling for the bulge sign.
Add `entities::common::BulgeArc { center, radius, start_angle,
end_angle, sweep }` with one `from_bulge(p0, p1, bulge)` constructor
and a `sample(t)` helper. Callsites collapse to a few lines.
Net: -121 lines, one canonical sweep-direction convention shared by
all consumers.
array.rs (the path-along-curve f32 helper) keeps its inline math —
its sign convention is inverted relative to the others and changing
it without exercising the array-path command risks regressing the
output.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 23:42:26 +03:00
|
|
|
} else if let Some(arc) =
|
|
|
|
|
crate::entities::common::BulgeArc::from_bulge([ox0, oy0], [ox1, oy1], bulge)
|
|
|
|
|
{
|
|
|
|
|
let (wcx, wcy, wcz) = to_wcs(arc.center[0], arc.center[1]);
|
2026-05-12 10:36:15 +03:00
|
|
|
tgs.push(TangentGeom::Circle {
|
|
|
|
|
center: [wcx as f32, wcy as f32, wcz as f32],
|
refactor(common): unify bulge → arc math into BulgeArc
The same DXF/DWG bulge → (center, radius, sweep) formula was inlined
in six places (hatch outline, polyline tess + truck path, lwpolyline
×4, scene hatch fill, explode bulge_to_arc) — each with subtly
different sweep-direction handling for the bulge sign.
Add `entities::common::BulgeArc { center, radius, start_angle,
end_angle, sweep }` with one `from_bulge(p0, p1, bulge)` constructor
and a `sample(t)` helper. Callsites collapse to a few lines.
Net: -121 lines, one canonical sweep-direction convention shared by
all consumers.
array.rs (the path-along-curve f32 helper) keeps its inline math —
its sign convention is inverted relative to the others and changing
it without exercising the array-path command risks regressing the
output.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 23:42:26 +03:00
|
|
|
radius: arc.radius as f32,
|
2026-05-12 10:36:15 +03:00
|
|
|
});
|
2026-08-10 12:12:00 +03:00
|
|
|
for s in arc
|
2026-08-10 15:03:31 +03:00
|
|
|
.tessellate_angle(cadkernel::tessellation::DEFAULT_ANGLE)
|
2026-08-10 12:12:00 +03:00
|
|
|
.into_iter()
|
|
|
|
|
.skip(1)
|
|
|
|
|
{
|
refactor(common): unify bulge → arc math into BulgeArc
The same DXF/DWG bulge → (center, radius, sweep) formula was inlined
in six places (hatch outline, polyline tess + truck path, lwpolyline
×4, scene hatch fill, explode bulge_to_arc) — each with subtly
different sweep-direction handling for the bulge sign.
Add `entities::common::BulgeArc { center, radius, start_angle,
end_angle, sweep }` with one `from_bulge(p0, p1, bulge)` constructor
and a `sample(t)` helper. Callsites collapse to a few lines.
Net: -121 lines, one canonical sweep-direction convention shared by
all consumers.
array.rs (the path-along-curve f32 helper) keeps its inline math —
its sign convention is inverted relative to the others and changing
it without exercising the array-path command risks regressing the
output.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 23:42:26 +03:00
|
|
|
let (wx, wy, wz) = to_wcs(s[0], s[1]);
|
2026-05-12 23:47:40 +03:00
|
|
|
path.push([wx, wy, wz]);
|
2026-05-05 20:03:57 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let (wbx, wby, wbz) = to_wcs(ox1, oy1);
|
2026-05-12 23:47:40 +03:00
|
|
|
kv.push([wbx, wby, wbz]);
|
2026-05-05 20:03:57 +03:00
|
|
|
}
|
2026-07-20 16:04:03 +03:00
|
|
|
// A wide Polyline2D extrudes its whole band into a solid tube (walls +
|
|
|
|
|
// caps), same as a thickened LwPolyline; a zero-width one falls through
|
|
|
|
|
// to the centre-line extrusion below.
|
|
|
|
|
let is_wide = pl.start_width > 1e-9
|
|
|
|
|
|| pl.end_width > 1e-9
|
|
|
|
|
|| pl
|
|
|
|
|
.vertices
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|v| v.start_width > 1e-9 || v.end_width > 1e-9);
|
|
|
|
|
if is_wide {
|
|
|
|
|
let (origin, fills) = wide_fills(pl);
|
|
|
|
|
let (fill_tris, lines) =
|
|
|
|
|
crate::entities::common::thick_band_tube(origin, &fills, t, normal, &to_wcs);
|
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
|
|
|
return RenderEntity {
|
2026-07-20 16:04:03 +03:00
|
|
|
pick_tris: fill_tris.clone(),
|
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(lines),
|
2026-07-20 16:04:03 +03:00
|
|
|
snap_pts: vec![],
|
|
|
|
|
tangent_geoms: tgs,
|
|
|
|
|
key_vertices: kv,
|
|
|
|
|
fill_tris,
|
|
|
|
|
};
|
|
|
|
|
}
|
2026-05-12 23:47:40 +03:00
|
|
|
let mut pts: Vec<[f64; 3]> = Vec::with_capacity(path.len() * 2 + kv.len() * 3 + 4);
|
2026-05-05 20:03:57 +03:00
|
|
|
pts.extend_from_slice(&path);
|
2026-05-12 23:47:40 +03:00
|
|
|
pts.push([f64::NAN; 3]);
|
2026-05-12 10:36:15 +03:00
|
|
|
for &p in &path {
|
|
|
|
|
pts.push(off(p));
|
|
|
|
|
}
|
2026-05-05 20:03:57 +03:00
|
|
|
if !kv.is_empty() {
|
2026-05-12 23:47:40 +03:00
|
|
|
pts.push([f64::NAN; 3]);
|
2026-05-05 20:03:57 +03:00
|
|
|
for (i, &pb) in kv.iter().enumerate() {
|
2026-05-12 10:36:15 +03:00
|
|
|
pts.push(pb);
|
|
|
|
|
pts.push(off(pb));
|
|
|
|
|
if i + 1 < kv.len() {
|
2026-05-12 23:47:40 +03:00
|
|
|
pts.push([f64::NAN; 3]);
|
2026-05-12 10:36:15 +03:00
|
|
|
}
|
2026-05-05 20:03:57 +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
|
|
|
return 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: extrusion_wall_tris(&path, [t * nx, t * ny, t * nz]),
|
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-12 10:36:15 +03:00
|
|
|
snap_pts: vec![],
|
|
|
|
|
tangent_geoms: tgs,
|
|
|
|
|
key_vertices: kv,
|
|
|
|
|
fill_tris: vec![],
|
|
|
|
|
};
|
2026-05-05 20:03:57 +03:00
|
|
|
}
|
|
|
|
|
|
2026-04-02 18:51:38 +03:00
|
|
|
for i in 0..seg_count {
|
|
|
|
|
let v0 = &verts[i];
|
|
|
|
|
let v1 = &verts[(i + 1) % count];
|
|
|
|
|
let p0 = to_pt(v0);
|
|
|
|
|
let p1 = to_pt(v1);
|
|
|
|
|
let bulge = v0.bulge;
|
|
|
|
|
|
|
|
|
|
if bulge.abs() < 1e-9 {
|
|
|
|
|
tangents.push(TangentGeom::Line {
|
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
|
|
|
p1: [p0[0] as f32, p0[1] as f32, p0[2] as f32],
|
|
|
|
|
p2: [p1[0] as f32, p1[1] as f32, p1[2] as f32],
|
2026-04-02 18:51:38 +03:00
|
|
|
});
|
refactor(common): unify bulge → arc math into BulgeArc
The same DXF/DWG bulge → (center, radius, sweep) formula was inlined
in six places (hatch outline, polyline tess + truck path, lwpolyline
×4, scene hatch fill, explode bulge_to_arc) — each with subtly
different sweep-direction handling for the bulge sign.
Add `entities::common::BulgeArc { center, radius, start_angle,
end_angle, sweep }` with one `from_bulge(p0, p1, bulge)` constructor
and a `sample(t)` helper. Callsites collapse to a few lines.
Net: -121 lines, one canonical sweep-direction convention shared by
all consumers.
array.rs (the path-along-curve f32 helper) keeps its inline math —
its sign convention is inverted relative to the others and changing
it without exercising the array-path command risks regressing the
output.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 23:42:26 +03:00
|
|
|
} else if let Some(arc) = crate::entities::common::BulgeArc::from_bulge(
|
|
|
|
|
[v0.location.x, v0.location.y],
|
|
|
|
|
[v1.location.x, v1.location.y],
|
|
|
|
|
bulge,
|
|
|
|
|
) {
|
|
|
|
|
let (wcx, wcy, wcz) = to_wcs(arc.center[0], arc.center[1]);
|
2026-04-02 18:51:38 +03:00
|
|
|
tangents.push(TangentGeom::Circle {
|
2026-05-02 20:04:30 +03:00
|
|
|
center: [wcx as f32, wcy as f32, wcz as f32],
|
refactor(common): unify bulge → arc math into BulgeArc
The same DXF/DWG bulge → (center, radius, sweep) formula was inlined
in six places (hatch outline, polyline tess + truck path, lwpolyline
×4, scene hatch fill, explode bulge_to_arc) — each with subtly
different sweep-direction handling for the bulge sign.
Add `entities::common::BulgeArc { center, radius, start_angle,
end_angle, sweep }` with one `from_bulge(p0, p1, bulge)` constructor
and a `sample(t)` helper. Callsites collapse to a few lines.
Net: -121 lines, one canonical sweep-direction convention shared by
all consumers.
array.rs (the path-along-curve f32 helper) keeps its inline math —
its sign convention is inverted relative to the others and changing
it without exercising the array-path command risks regressing the
output.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 23:42:26 +03:00
|
|
|
radius: arc.radius as f32,
|
2026-04-02 18:51:38 +03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if i == 0 {
|
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
|
|
|
key_verts.push([p0[0], p0[1], p0[2]]);
|
2026-04-02 18:51:38 +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
|
|
|
key_verts.push([p1[0], p1[1], p1[2]]);
|
2026-04-02 18:51:38 +03:00
|
|
|
}
|
|
|
|
|
|
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
|
|
|
let (fill_origin, fills) = wide_fills(pl);
|
2026-07-20 16:36:06 +03:00
|
|
|
// A wide Polyline2D whose per-vertex widths VARY renders a smooth taper; a
|
|
|
|
|
// uniform-width one keeps the constant-band Contour.
|
|
|
|
|
let object = match tapered_band_verts_2d(pl) {
|
|
|
|
|
Some(band_verts) => {
|
|
|
|
|
let (pts, widths) = crate::entities::common::tapered_band_points(
|
|
|
|
|
&band_verts,
|
|
|
|
|
pl.is_closed(),
|
|
|
|
|
&to_wcs,
|
|
|
|
|
);
|
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
|
|
|
RenderObject::TaperedLines(pts, widths)
|
2026-07-20 16:36:06 +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
|
|
|
None => RenderObject::Lines(
|
|
|
|
|
crate::entities::curve::polyline2d_curve(pl)
|
|
|
|
|
.map(|planar| crate::entities::curve::curve_points(&planar))
|
|
|
|
|
.unwrap_or_default(),
|
|
|
|
|
),
|
2026-07-20 16:36:06 +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
|
|
|
RenderEntity {
|
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
|
|
|
pick_tris: crate::entities::common::wide_band_tris(fill_origin, &fills),
|
2026-07-20 16:36:06 +03:00
|
|
|
object,
|
2026-04-02 18:51:38 +03:00
|
|
|
snap_pts: vec![],
|
|
|
|
|
tangent_geoms: tangents,
|
|
|
|
|
key_vertices: key_verts,
|
2026-05-06 23:24:26 +03:00
|
|
|
fill_tris: vec![],
|
2026-04-02 18:51:38 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-20 16:36:06 +03:00
|
|
|
/// Per-vertex `(location, bulge, start_width, end_width)` band description for a
|
|
|
|
|
/// wide Polyline2D whose width VARIES — `None` when the width is uniform.
|
|
|
|
|
fn tapered_band_verts_2d(
|
|
|
|
|
pl: &acadrust::entities::Polyline2D,
|
|
|
|
|
) -> Option<Vec<([f64; 2], f64, f64, f64)>> {
|
2026-07-31 23:36:07 +03:00
|
|
|
let default_start = pl.start_width;
|
|
|
|
|
let default_end = pl.end_width;
|
2026-07-21 15:12:52 +03:00
|
|
|
let filtered = drawn_vertices2d(pl);
|
|
|
|
|
let verts: &[acadrust::entities::Vertex2D] = filtered.as_deref().unwrap_or(&pl.vertices);
|
|
|
|
|
let band: Vec<([f64; 2], f64, f64, f64)> = verts
|
2026-07-20 16:36:06 +03:00
|
|
|
.iter()
|
|
|
|
|
.map(|v| {
|
2026-07-31 23:36:07 +03:00
|
|
|
let sw = if v.start_width > 1e-9 {
|
|
|
|
|
v.start_width
|
|
|
|
|
} else {
|
|
|
|
|
default_start
|
|
|
|
|
};
|
|
|
|
|
let ew = if v.end_width > 1e-9 {
|
|
|
|
|
v.end_width
|
|
|
|
|
} else {
|
|
|
|
|
default_end
|
|
|
|
|
};
|
2026-07-20 16:36:06 +03:00
|
|
|
([v.location.x, v.location.y], v.bulge, sw, ew)
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
let w0 = band.first().map_or(0.0, |v| v.2);
|
|
|
|
|
let varies = band
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|&(_, _, sw, ew)| (sw - w0).abs() > 1e-9 || (ew - w0).abs() > 1e-9);
|
|
|
|
|
if varies && w0.max(band.iter().map(|v| v.3).fold(0.0, f64::max)) > 1e-9 {
|
|
|
|
|
Some(band)
|
|
|
|
|
} else {
|
|
|
|
|
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
|
|
|
impl RenderConvertible for Polyline2D {
|
|
|
|
|
fn to_render(&self, _document: &acadrust::CadDocument) -> Option<RenderEntity> {
|
2026-04-02 18:51:38 +03:00
|
|
|
Some(tessellate_polyline2d(self))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Grippable for Polyline2D {
|
|
|
|
|
fn grips(&self) -> Vec<GripDef> {
|
2026-06-11 00:04:37 +03:00
|
|
|
let elev = self.elevation;
|
2026-04-02 18:51:38 +03:00
|
|
|
self.vertices
|
|
|
|
|
.iter()
|
|
|
|
|
.enumerate()
|
2026-06-11 00:04:37 +03:00
|
|
|
.map(|(i, v)| square_grip(i, glam::DVec3::new(v.location.x, v.location.y, elev)))
|
2026-04-02 18:51:38 +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;
|
|
|
|
|
}
|
|
|
|
|
GripApply::Absolute(p) => {
|
|
|
|
|
v.location.x = p.x as f64;
|
|
|
|
|
v.location.y = p.y as f64;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
|
2026-06-16 11:33:46 +03:00
|
|
|
fn grip_menu(&self, _grip_id: usize) -> Vec<crate::scene::model::object::GripMenuItem> {
|
|
|
|
|
use crate::scene::model::object::{GripMenuAction, GripMenuItem};
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
vec![
|
2026-06-11 00:04:37 +03:00
|
|
|
GripMenuItem {
|
|
|
|
|
label: "Stretch",
|
|
|
|
|
action: GripMenuAction::Stretch,
|
|
|
|
|
},
|
|
|
|
|
GripMenuItem {
|
|
|
|
|
label: "Add Vertex",
|
|
|
|
|
action: GripMenuAction::AddVertex,
|
|
|
|
|
},
|
|
|
|
|
GripMenuItem {
|
|
|
|
|
label: "Remove Vertex",
|
|
|
|
|
action: GripMenuAction::RemoveVertex,
|
|
|
|
|
},
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-16 11:33:46 +03:00
|
|
|
fn apply_grip_menu(&mut self, grip_id: usize, action: crate::scene::model::object::GripMenuAction) {
|
|
|
|
|
use crate::scene::model::object::GripMenuAction as A;
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
let n = self.vertices.len();
|
|
|
|
|
let elev = self.elevation;
|
|
|
|
|
match action {
|
|
|
|
|
A::AddVertex if grip_id < n => {
|
2026-07-31 23:36:07 +03:00
|
|
|
if grip_id == n - 1 && !self.is_closed() {
|
|
|
|
|
self.vertices[grip_id].bulge = 0.0;
|
|
|
|
|
let mut new_v = self.vertices[grip_id].clone();
|
|
|
|
|
new_v.bulge = 0.0;
|
|
|
|
|
new_v.id = 0;
|
|
|
|
|
self.vertices.push(new_v);
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
return;
|
|
|
|
|
}
|
2026-07-31 23:36:07 +03:00
|
|
|
let i1 = (grip_id + 1) % n;
|
|
|
|
|
let v0 = self.vertices[grip_id].clone();
|
|
|
|
|
let v1 = self.vertices[i1].clone();
|
|
|
|
|
let midpoint = if v0.bulge.abs() >= 1e-9 {
|
|
|
|
|
crate::entities::common::BulgeArc::from_bulge(
|
|
|
|
|
[v0.location.x, v0.location.y],
|
|
|
|
|
[v1.location.x, v1.location.y],
|
|
|
|
|
v0.bulge,
|
|
|
|
|
)
|
|
|
|
|
.map(|arc| arc.sample(0.5))
|
|
|
|
|
.unwrap_or([
|
|
|
|
|
(v0.location.x + v1.location.x) * 0.5,
|
|
|
|
|
(v0.location.y + v1.location.y) * 0.5,
|
|
|
|
|
])
|
|
|
|
|
} else {
|
|
|
|
|
[
|
|
|
|
|
(v0.location.x + v1.location.x) * 0.5,
|
|
|
|
|
(v0.location.y + v1.location.y) * 0.5,
|
|
|
|
|
]
|
|
|
|
|
};
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
let mut new_v = v0.clone();
|
2026-07-31 23:36:07 +03:00
|
|
|
new_v.location.x = midpoint[0];
|
|
|
|
|
new_v.location.y = midpoint[1];
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
new_v.location.z = elev;
|
2026-07-31 23:36:07 +03:00
|
|
|
new_v.id = 0;
|
|
|
|
|
let effective_start = if v0.start_width > 1e-9 {
|
|
|
|
|
v0.start_width
|
|
|
|
|
} else {
|
|
|
|
|
self.start_width
|
|
|
|
|
};
|
|
|
|
|
let effective_end = if v0.end_width > 1e-9 {
|
|
|
|
|
v0.end_width
|
|
|
|
|
} else {
|
|
|
|
|
self.end_width
|
|
|
|
|
};
|
|
|
|
|
let middle_width = (effective_start + effective_end) * 0.5;
|
|
|
|
|
self.vertices[grip_id].end_width = middle_width;
|
|
|
|
|
new_v.start_width = middle_width;
|
|
|
|
|
if v0.bulge.abs() >= 1e-9 {
|
|
|
|
|
new_v.bulge = (v0.bulge.atan() * 0.5).tan();
|
|
|
|
|
}
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
let insert_at = (grip_id + 1).min(self.vertices.len());
|
2026-07-31 23:36:07 +03:00
|
|
|
if v0.bulge.abs() >= 1e-9 {
|
|
|
|
|
self.vertices[grip_id].bulge = new_v.bulge;
|
|
|
|
|
}
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
self.vertices.insert(insert_at, new_v);
|
|
|
|
|
}
|
|
|
|
|
A::RemoveVertex if grip_id < n && n > 2 => {
|
|
|
|
|
self.vertices.remove(grip_id);
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-02 18:51:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PropertyEditable for Polyline2D {
|
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
|
|
|
let n = self.vertices.len();
|
|
|
|
|
let mut area = 0.0;
|
|
|
|
|
let mut length = 0.0;
|
|
|
|
|
let seg_count = if self.is_closed() { n } else { n.saturating_sub(1) };
|
|
|
|
|
for i in 0..seg_count {
|
|
|
|
|
let a = &self.vertices[i].location;
|
|
|
|
|
let b = &self.vertices[(i + 1) % n].location;
|
|
|
|
|
area += a.x * b.y - b.x * a.y;
|
|
|
|
|
length += ((b.x - a.x).powi(2) + (b.y - a.y).powi(2)).sqrt();
|
|
|
|
|
}
|
|
|
|
|
area = (area * 0.5).abs();
|
|
|
|
|
|
2026-07-08 17:53:05 +03:00
|
|
|
let vi = if n == 0 {
|
|
|
|
|
0
|
|
|
|
|
} else {
|
|
|
|
|
crate::scene::view::dispatch::prop_current_vertex().min(n - 1)
|
|
|
|
|
};
|
|
|
|
|
let v = self.vertices.get(vi);
|
|
|
|
|
let vertex_x = v.map(|v| v.location.x).unwrap_or_default();
|
|
|
|
|
let vertex_y = v.map(|v| v.location.y).unwrap_or_default();
|
|
|
|
|
let seg_start_w = v.map(|v| v.start_width).unwrap_or_default();
|
|
|
|
|
let seg_end_w = v.map(|v| v.end_width).unwrap_or_default();
|
|
|
|
|
let vertex_label = if n == 0 {
|
|
|
|
|
"—".to_string()
|
|
|
|
|
} else {
|
|
|
|
|
format!("{} / {}", vi + 1, n)
|
|
|
|
|
};
|
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
|
|
|
|
|
|
|
|
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
|
|
|
stepper(t!("Current Vertex").as_ref(), "pl2_current_vertex", vertex_label),
|
|
|
|
|
edit(t!("Vertex X").as_ref(), "pl2_vertex_x", vertex_x),
|
|
|
|
|
edit(t!("Vertex Y").as_ref(), "pl2_vertex_y", vertex_y),
|
|
|
|
|
edit(t!("Start segment width").as_ref(), "pl2_seg_start_w", seg_start_w),
|
|
|
|
|
edit(t!("End segment width").as_ref(), "pl2_seg_end_w", seg_end_w),
|
|
|
|
|
edit(t!("Global width").as_ref(), "pl2_start_w", self.start_width),
|
|
|
|
|
edit(t!("Elevation").as_ref(), "pl2_elevation", self.elevation),
|
|
|
|
|
ro(t!("Area").as_ref(), "pl2_area", format!("{area:.4}")),
|
|
|
|
|
ro(t!("Length").as_ref(), "pl2_length", format!("{length:.4}")),
|
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(),
|
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![
|
|
|
|
|
Property {
|
2026-08-02 22:09:25 +03:00
|
|
|
label: t!("Closed").into_owned(),
|
2026-04-02 18:51:38 +03:00
|
|
|
field: "pl2_closed",
|
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
|
|
|
value: PropValue::BoolToggle {
|
|
|
|
|
field: "pl2_closed",
|
|
|
|
|
value: self.is_closed(),
|
|
|
|
|
},
|
2026-04-02 18:51:38 +03:00
|
|
|
},
|
2026-07-03 08:06:00 +03:00
|
|
|
Property {
|
2026-08-02 22:09:25 +03:00
|
|
|
label: t!("Linetype generation").into_owned(),
|
2026-07-03 08:06:00 +03:00
|
|
|
field: "pl2_ltype_gen",
|
|
|
|
|
value: PropValue::BoolToggle {
|
|
|
|
|
field: "pl2_ltype_gen",
|
|
|
|
|
value: self.flags.bits() & 128 != 0,
|
|
|
|
|
},
|
|
|
|
|
},
|
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 18:51:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apply_geom_prop(&mut self, field: &str, value: &str) {
|
2026-07-08 17:53:05 +03:00
|
|
|
// Per-vertex edits target the vertex the panel is focused on.
|
|
|
|
|
let n = self.vertices.len();
|
|
|
|
|
let vi = if n == 0 {
|
|
|
|
|
0
|
|
|
|
|
} else {
|
|
|
|
|
crate::scene::view::dispatch::prop_current_vertex().min(n - 1)
|
|
|
|
|
};
|
2026-04-02 18:51:38 +03:00
|
|
|
match field {
|
|
|
|
|
"pl2_closed" => {
|
|
|
|
|
let closed = if value == "toggle" {
|
|
|
|
|
!self.is_closed()
|
|
|
|
|
} else {
|
|
|
|
|
value == "true"
|
|
|
|
|
};
|
2026-05-12 10:36:15 +03:00
|
|
|
if closed {
|
|
|
|
|
self.close();
|
|
|
|
|
} else {
|
|
|
|
|
self.flags.set_closed(false);
|
|
|
|
|
}
|
2026-04-02 18:51:38 +03:00
|
|
|
}
|
|
|
|
|
"pl2_elevation" => {
|
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
|
|
|
if let Some(v) = parse_f64(value) {
|
2026-04-02 18:51:38 +03:00
|
|
|
self.elevation = v;
|
|
|
|
|
}
|
|
|
|
|
}
|
feat(entities): surface remaining 2D + Region fields in properties
Closes out the 2D-entity audit (and the Region partner that ships next
to Solid3D / Body). Round-trip-only metadata fields plus a few editable
ones gain Properties-panel rows so the values the parser populates are
visible and don't get silently dropped on save:
- Hatch: seed_points count, pixel_size, normal
- Polyline2D: smooth_surface, default_start_width, default_end_width,
thickness, normal (with editors for the widths/thickness)
- Region: uid, silhouettes (shared file with Solid3D / Body)
- Underlay: clip_inverted
- RasterImage: class_version, definition_handle, definition_reactor_handle
- Wipeout: class_version, clip_mode, definition_handle,
definition_reactor_handle
- Leader: annotation_handle, override_color
- Tolerance: dimension_style_name, dimension_style_handle, dimension_gap,
text_height (editable), direction, normal
- MLine: style_handle, style_element_count, justification picker
- Ole2Frame: version, source_application, binary_data size, dwg_mode,
dwg_trailing_byte, is_paper_space
- Viewport: snap_base/spacing/angle, twist_angle, front/back clip Z,
circle_sides, grid_flags, grid_major, base_ucs_handle, ucs_ortho_type,
background_handle, shade_plot_handle, visual_style_handle,
default_lighting, default_lighting_type, ambient_color (with editors
for the snap / clip / circle_sides values)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:28:16 +03:00
|
|
|
"pl2_start_w" => {
|
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
|
|
|
if let Some(v) = parse_f64(value) {
|
feat(entities): surface remaining 2D + Region fields in properties
Closes out the 2D-entity audit (and the Region partner that ships next
to Solid3D / Body). Round-trip-only metadata fields plus a few editable
ones gain Properties-panel rows so the values the parser populates are
visible and don't get silently dropped on save:
- Hatch: seed_points count, pixel_size, normal
- Polyline2D: smooth_surface, default_start_width, default_end_width,
thickness, normal (with editors for the widths/thickness)
- Region: uid, silhouettes (shared file with Solid3D / Body)
- Underlay: clip_inverted
- RasterImage: class_version, definition_handle, definition_reactor_handle
- Wipeout: class_version, clip_mode, definition_handle,
definition_reactor_handle
- Leader: annotation_handle, override_color
- Tolerance: dimension_style_name, dimension_style_handle, dimension_gap,
text_height (editable), direction, normal
- MLine: style_handle, style_element_count, justification picker
- Ole2Frame: version, source_application, binary_data size, dwg_mode,
dwg_trailing_byte, is_paper_space
- Viewport: snap_base/spacing/angle, twist_angle, front/back clip Z,
circle_sides, grid_flags, grid_major, base_ucs_handle, ucs_ortho_type,
background_handle, shade_plot_handle, visual_style_handle,
default_lighting, default_lighting_type, ambient_color (with editors
for the snap / clip / circle_sides values)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:28:16 +03:00
|
|
|
if v >= 0.0 {
|
|
|
|
|
self.start_width = v;
|
|
|
|
|
self.end_width = v;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-03 08:06:00 +03:00
|
|
|
"pl2_vertex_x" => {
|
2026-07-08 17:53:05 +03:00
|
|
|
if let (Some(v), Some(vert)) = (parse_f64(value), self.vertices.get_mut(vi)) {
|
2026-07-03 08:06:00 +03:00
|
|
|
vert.location.x = v;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"pl2_vertex_y" => {
|
2026-07-08 17:53:05 +03:00
|
|
|
if let (Some(v), Some(vert)) = (parse_f64(value), self.vertices.get_mut(vi)) {
|
2026-07-03 08:06:00 +03:00
|
|
|
vert.location.y = v;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"pl2_seg_start_w" => {
|
2026-07-08 17:53:05 +03:00
|
|
|
if let (Some(v), Some(vert)) = (parse_f64(value), self.vertices.get_mut(vi)) {
|
2026-07-03 08:06:00 +03:00
|
|
|
if v >= 0.0 {
|
|
|
|
|
vert.start_width = v;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"pl2_seg_end_w" => {
|
2026-07-08 17:53:05 +03:00
|
|
|
if let (Some(v), Some(vert)) = (parse_f64(value), self.vertices.get_mut(vi)) {
|
2026-07-03 08:06:00 +03:00
|
|
|
if v >= 0.0 {
|
|
|
|
|
vert.end_width = v;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"pl2_ltype_gen" => {
|
|
|
|
|
let on = if value == "toggle" {
|
|
|
|
|
self.flags.bits() & 128 == 0
|
|
|
|
|
} else {
|
|
|
|
|
value == "true"
|
|
|
|
|
};
|
|
|
|
|
let bits = if on {
|
|
|
|
|
self.flags.bits() | 128
|
|
|
|
|
} else {
|
|
|
|
|
self.flags.bits() & !128
|
|
|
|
|
};
|
|
|
|
|
self.flags = acadrust::entities::polyline::PolylineFlags::from_bits(bits);
|
|
|
|
|
}
|
2026-04-02 18:51:38 +03:00
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Transformable for Polyline2D {
|
|
|
|
|
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 18:51:38 +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 18:51:38 +03:00
|
|
|
&mut v.location.x,
|
|
|
|
|
&mut v.location.y,
|
|
|
|
|
p1,
|
|
|
|
|
p2,
|
|
|
|
|
);
|
2026-06-11 01:17:47 +03:00
|
|
|
// Bulge encodes which side the arc bows to; a reflection
|
|
|
|
|
// reverses it or every curved segment flips to the wrong side.
|
|
|
|
|
v.bulge = -v.bulge;
|
2026-04-02 18:51:38 +03:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Polyline3D ────────────────────────────────────────────────────────────────
|
|
|
|
|
|
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
|
|
|
fn tessellate_polyline3d(pl: &Polyline3D) -> RenderEntity {
|
2026-05-12 23:47:40 +03:00
|
|
|
let to_pt = |v: &acadrust::entities::Vertex3DPolyline| -> [f64; 3] {
|
|
|
|
|
[v.position.x, v.position.y, v.position.z]
|
2026-04-26 22:12:56 +03:00
|
|
|
};
|
2026-04-02 18:51:38 +03:00
|
|
|
|
2026-04-26 22:12:56 +03:00
|
|
|
// DXF vertex flags: 8 = spline-fit curve point, 16 = spline frame control point.
|
|
|
|
|
// When spline-fit vertices are present use them for the wire and control points for snap;
|
|
|
|
|
// otherwise treat all vertices uniformly.
|
|
|
|
|
let spline_curve: Vec<_> = pl.vertices.iter().filter(|v| v.flags & 8 != 0).collect();
|
2026-05-12 10:36:15 +03:00
|
|
|
let ctrl_pts: Vec<_> = pl.vertices.iter().filter(|v| v.flags & 16 != 0).collect();
|
2026-04-26 22:12:56 +03:00
|
|
|
|
|
|
|
|
let (wire_pts, key_verts) = if !spline_curve.is_empty() {
|
2026-05-12 23:47:40 +03:00
|
|
|
let wire: Vec<[f64; 3]> = spline_curve.iter().map(|v| to_pt(v)).collect();
|
|
|
|
|
let ctrl: Vec<[f64; 3]> = ctrl_pts.iter().map(|v| to_pt(v)).collect();
|
2026-04-26 22:12:56 +03:00
|
|
|
(wire, ctrl)
|
|
|
|
|
} else {
|
2026-05-12 23:47:40 +03:00
|
|
|
let pts: Vec<[f64; 3]> = pl.vertices.iter().map(to_pt).collect();
|
2026-04-26 22:12:56 +03:00
|
|
|
(pts.clone(), pts)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mut points = wire_pts.clone();
|
|
|
|
|
if pl.is_closed() && wire_pts.len() >= 2 {
|
|
|
|
|
points.push(wire_pts[0]);
|
2026-04-02 18:51:38 +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
|
|
|
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(points),
|
2026-04-02 18:51:38 +03:00
|
|
|
snap_pts: vec![],
|
|
|
|
|
tangent_geoms: vec![],
|
|
|
|
|
key_vertices: key_verts,
|
2026-05-06 23:24:26 +03:00
|
|
|
fill_tris: vec![],
|
2026-04-02 18:51:38 +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 Polyline3D {
|
|
|
|
|
fn to_render(&self, _document: &acadrust::CadDocument) -> Option<RenderEntity> {
|
2026-04-02 18:51:38 +03:00
|
|
|
Some(tessellate_polyline3d(self))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Grippable for Polyline3D {
|
|
|
|
|
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.position.x, v.position.y, v.position.z),
|
2026-04-02 18:51:38 +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.position.x += d.x as f64;
|
|
|
|
|
v.position.y += d.y as f64;
|
|
|
|
|
v.position.z += d.z as f64;
|
|
|
|
|
}
|
|
|
|
|
GripApply::Absolute(p) => {
|
|
|
|
|
v.position.x = p.x as f64;
|
|
|
|
|
v.position.y = p.y as f64;
|
|
|
|
|
v.position.z = p.z as f64;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
|
2026-06-16 11:33:46 +03:00
|
|
|
fn grip_menu(&self, _grip_id: usize) -> Vec<crate::scene::model::object::GripMenuItem> {
|
|
|
|
|
use crate::scene::model::object::{GripMenuAction, GripMenuItem};
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
vec![
|
2026-06-11 00:04:37 +03:00
|
|
|
GripMenuItem {
|
|
|
|
|
label: "Stretch",
|
|
|
|
|
action: GripMenuAction::Stretch,
|
|
|
|
|
},
|
|
|
|
|
GripMenuItem {
|
|
|
|
|
label: "Add Vertex",
|
|
|
|
|
action: GripMenuAction::AddVertex,
|
|
|
|
|
},
|
|
|
|
|
GripMenuItem {
|
|
|
|
|
label: "Remove Vertex",
|
|
|
|
|
action: GripMenuAction::RemoveVertex,
|
|
|
|
|
},
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-16 11:33:46 +03:00
|
|
|
fn apply_grip_menu(&mut self, grip_id: usize, action: crate::scene::model::object::GripMenuAction) {
|
|
|
|
|
use crate::scene::model::object::GripMenuAction as A;
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
let n = self.vertices.len();
|
|
|
|
|
match action {
|
|
|
|
|
A::AddVertex if grip_id < n => {
|
2026-07-31 23:36:07 +03:00
|
|
|
if grip_id == n - 1 && !self.is_closed() {
|
|
|
|
|
let mut new_v = self.vertices[grip_id].clone();
|
|
|
|
|
new_v.handle = acadrust::Handle::NULL;
|
|
|
|
|
self.vertices.push(new_v);
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
return;
|
|
|
|
|
}
|
2026-07-31 23:36:07 +03:00
|
|
|
let i1 = (grip_id + 1) % n;
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
let v0 = &self.vertices[grip_id];
|
|
|
|
|
let v1 = &self.vertices[i1];
|
|
|
|
|
let mx = (v0.position.x + v1.position.x) * 0.5;
|
|
|
|
|
let my = (v0.position.y + v1.position.y) * 0.5;
|
|
|
|
|
let mz = (v0.position.z + v1.position.z) * 0.5;
|
|
|
|
|
let mut new_v = v0.clone();
|
|
|
|
|
new_v.position.x = mx;
|
|
|
|
|
new_v.position.y = my;
|
|
|
|
|
new_v.position.z = mz;
|
2026-07-31 23:36:07 +03:00
|
|
|
new_v.handle = acadrust::Handle::NULL;
|
feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text
Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.
Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.
Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:43 +03:00
|
|
|
let insert_at = (grip_id + 1).min(self.vertices.len());
|
|
|
|
|
self.vertices.insert(insert_at, new_v);
|
|
|
|
|
}
|
|
|
|
|
A::RemoveVertex if grip_id < n && n > 2 => {
|
|
|
|
|
self.vertices.remove(grip_id);
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-02 18:51:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PropertyEditable for Polyline3D {
|
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
|
|
|
use acadrust::entities::polyline3d::SmoothSurfaceType as SST;
|
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 n = self.vertices.len();
|
|
|
|
|
let v0 = self.vertices.first();
|
2026-07-03 08:06:00 +03:00
|
|
|
let vertex_x = v0.map(|v| v.position.x).unwrap_or_default();
|
|
|
|
|
let vertex_y = v0.map(|v| v.position.y).unwrap_or_default();
|
|
|
|
|
let vertex_z = v0.map(|v| v.position.z).unwrap_or_default();
|
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 fit_smooth = match self.smooth_type {
|
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
|
|
|
SST::None => "None",
|
|
|
|
|
SST::QuadraticBSpline => "Quadratic",
|
|
|
|
|
SST::CubicBSpline => "Cubic",
|
|
|
|
|
SST::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
|
|
|
|
|
|
|
|
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(), "pl3_vertex", if n > 0 { "1" } else { "" }),
|
|
|
|
|
edit(t!("Vertex X").as_ref(), "pl3_vertex_x", vertex_x),
|
|
|
|
|
edit(t!("Vertex Y").as_ref(), "pl3_vertex_y", vertex_y),
|
|
|
|
|
edit(t!("Vertex Z").as_ref(), "pl3_vertex_z", vertex_z),
|
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(),
|
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![
|
|
|
|
|
Property {
|
2026-08-02 22:09:25 +03:00
|
|
|
label: t!("Closed").into_owned(),
|
2026-04-02 18:51:38 +03:00
|
|
|
field: "pl3_closed",
|
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
|
|
|
value: PropValue::BoolToggle {
|
|
|
|
|
field: "pl3_closed",
|
|
|
|
|
value: self.is_closed(),
|
|
|
|
|
},
|
2026-04-02 18:51:38 +03:00
|
|
|
},
|
2026-08-02 22:09:25 +03:00
|
|
|
ro(t!("Fit/Smooth").as_ref(), "pl3_smooth", fit_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 18:51:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apply_geom_prop(&mut self, field: &str, value: &str) {
|
2026-07-03 08:06:00 +03:00
|
|
|
match field {
|
|
|
|
|
"pl3_closed" => {
|
|
|
|
|
let closed = if value == "toggle" {
|
|
|
|
|
!self.is_closed()
|
|
|
|
|
} else {
|
|
|
|
|
value == "true"
|
|
|
|
|
};
|
|
|
|
|
if closed {
|
|
|
|
|
self.close();
|
|
|
|
|
} else {
|
|
|
|
|
self.open();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"pl3_vertex_x" => {
|
|
|
|
|
if let (Some(v), Some(vert)) = (parse_f64(value), self.vertices.first_mut()) {
|
|
|
|
|
vert.position.x = v;
|
|
|
|
|
}
|
2026-05-12 10:36:15 +03:00
|
|
|
}
|
2026-07-03 08:06:00 +03:00
|
|
|
"pl3_vertex_y" => {
|
|
|
|
|
if let (Some(v), Some(vert)) = (parse_f64(value), self.vertices.first_mut()) {
|
|
|
|
|
vert.position.y = v;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"pl3_vertex_z" => {
|
|
|
|
|
if let (Some(v), Some(vert)) = (parse_f64(value), self.vertices.first_mut()) {
|
|
|
|
|
vert.position.z = v;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
2026-04-02 18:51:38 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Transformable for Polyline3D {
|
|
|
|
|
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 18:51:38 +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 18:51:38 +03:00
|
|
|
&mut v.position.x,
|
|
|
|
|
&mut v.position.y,
|
|
|
|
|
p1,
|
|
|
|
|
p2,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-23 23:26:14 +03:00
|
|
|
/// Generate solid-fill boundary polygons for each wide segment of a Polyline2D.
|
2026-06-26 10:55:28 +03:00
|
|
|
/// Solid-fill bands for a wide Polyline2D, plus the `world_origin` they are
|
|
|
|
|
/// relative to (the first vertex). See `lwpolyline::wide_fills` — offsets are
|
|
|
|
|
/// f32 from `origin` so the band stays precise at UTM-scale coordinates.
|
|
|
|
|
pub(crate) fn wide_fills(pl: &acadrust::entities::Polyline2D) -> ([f64; 2], Vec<Vec<[f32; 2]>>) {
|
2026-07-17 15:00:06 +03:00
|
|
|
// The stored widths are the band's FULL width and `polyline_segment_fill`
|
|
|
|
|
// offsets ±hw about the centreline — halve them. See `lwpolyline::wide_fills`.
|
2026-07-31 23:36:07 +03:00
|
|
|
let hw_default_start = pl.start_width as f32 * 0.5;
|
|
|
|
|
let hw_default_end = pl.end_width as f32 * 0.5;
|
2026-07-21 15:12:52 +03:00
|
|
|
let filtered = drawn_vertices2d(pl);
|
|
|
|
|
let verts: &[acadrust::entities::Vertex2D] = filtered.as_deref().unwrap_or(&pl.vertices);
|
2026-05-23 23:26:14 +03:00
|
|
|
let n = verts.len();
|
|
|
|
|
if n < 2 {
|
2026-06-26 10:55:28 +03:00
|
|
|
return ([0.0; 2], vec![]);
|
2026-05-23 23:26:14 +03:00
|
|
|
}
|
2026-06-26 10:55:28 +03:00
|
|
|
let origin = [verts[0].location.x, verts[0].location.y];
|
2026-05-23 23:26:14 +03:00
|
|
|
let seg_count = if pl.is_closed() { n } else { n - 1 };
|
|
|
|
|
let mut out = Vec::new();
|
|
|
|
|
for i in 0..seg_count {
|
|
|
|
|
let v0 = &verts[i];
|
|
|
|
|
let v1 = &verts[(i + 1) % n];
|
|
|
|
|
let hw0 = if v0.start_width > 1e-9 {
|
2026-07-17 15:00:06 +03:00
|
|
|
v0.start_width as f32 * 0.5
|
2026-05-23 23:26:14 +03:00
|
|
|
} else {
|
2026-07-31 23:36:07 +03:00
|
|
|
hw_default_start
|
2026-05-23 23:26:14 +03:00
|
|
|
};
|
|
|
|
|
let hw1 = if v0.end_width > 1e-9 {
|
2026-07-17 15:00:06 +03:00
|
|
|
v0.end_width as f32 * 0.5
|
2026-05-23 23:26:14 +03:00
|
|
|
} else {
|
2026-07-31 23:36:07 +03:00
|
|
|
hw_default_end
|
2026-05-23 23:26:14 +03:00
|
|
|
};
|
|
|
|
|
if hw0 < 1e-6 && hw1 < 1e-6 {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2026-06-26 10:55:28 +03:00
|
|
|
let p0 = [
|
|
|
|
|
(v0.location.x - origin[0]) as f32,
|
|
|
|
|
(v0.location.y - origin[1]) as f32,
|
|
|
|
|
];
|
|
|
|
|
let p1 = [
|
|
|
|
|
(v1.location.x - origin[0]) as f32,
|
|
|
|
|
(v1.location.y - origin[1]) as f32,
|
|
|
|
|
];
|
2026-06-11 00:04:37 +03:00
|
|
|
if let Some(poly) =
|
|
|
|
|
crate::entities::common::polyline_segment_fill(p0, p1, hw0, hw1, v0.bulge as f32)
|
|
|
|
|
{
|
2026-05-23 23:26:14 +03:00
|
|
|
out.push(poly);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-06-26 10:55:28 +03:00
|
|
|
(origin, out)
|
2026-05-23 23:26:14 +03:00
|
|
|
}
|