forked from 1b-agent/opencadstudio-upstream
feat(properties): show polyline length and area
Line, Circle and Arc reported their lengths/quantities in the Geometry section, but polylines (and rectangles, which are closed polylines) only listed vertex count, closed flag and elevation. Add read-only Length and Area rows: length is the true path length with arc segments measured by their arc length, area treats the polyline as closed with a circular segment correction per bulge. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
af850421b4
commit
490c0f6cf9
1 file changed
+41
@@ -323,6 +323,7 @@ fn grips(pline: &LwPolyline) -> Vec<GripDef> {
|
||||
}
|
||||
|
||||
fn properties(pline: &LwPolyline) -> PropSection {
|
||||
let (length, area) = length_and_area(pline);
|
||||
PropSection {
|
||||
title: "Geometry".into(),
|
||||
props: vec![
|
||||
@@ -333,10 +334,50 @@ fn properties(pline: &LwPolyline) -> PropSection {
|
||||
if pline.is_closed { "Yes" } else { "No" },
|
||||
),
|
||||
edit("Elevation", "elevation", pline.elevation),
|
||||
ro("Length", "length", format!("{length:.4}")),
|
||||
ro("Area", "area", format!("{area:.4}")),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
/// Path length and enclosed area of a polyline, accounting for arc
|
||||
/// (bulge) segments. Length is the actual path — it excludes the implicit
|
||||
/// closing segment when the polyline is open. Area always treats the
|
||||
/// polyline as closed (last vertex joined back to the first), matching how
|
||||
/// CAD property panels report a polyline's area.
|
||||
fn length_and_area(pline: &LwPolyline) -> (f64, f64) {
|
||||
let n = pline.vertices.len();
|
||||
if n < 2 {
|
||||
return (0.0, 0.0);
|
||||
}
|
||||
let mut length = 0.0;
|
||||
let mut chord_area = 0.0; // shoelace over chords, signed
|
||||
let mut arc_area = 0.0; // bulge corrections, signed
|
||||
for i in 0..n {
|
||||
let v0 = &pline.vertices[i];
|
||||
let v1 = &pline.vertices[(i + 1) % n];
|
||||
let p0 = [v0.location.x, v0.location.y];
|
||||
let p1 = [v1.location.x, v1.location.y];
|
||||
// The wrap edge (last → first) is part of the closed-area shoelace
|
||||
// regardless, but only contributes to length when the polyline is
|
||||
// actually closed.
|
||||
chord_area += p0[0] * p1[1] - p1[0] * p0[1];
|
||||
let is_wrap = i + 1 == n;
|
||||
if is_wrap && !pline.is_closed {
|
||||
continue;
|
||||
}
|
||||
let chord = ((p1[0] - p0[0]).powi(2) + (p1[1] - p0[1]).powi(2)).sqrt();
|
||||
match crate::entities::common::BulgeArc::from_bulge(p0, p1, v0.bulge) {
|
||||
Some(arc) => {
|
||||
length += arc.radius * arc.sweep.abs();
|
||||
arc_area += 0.5 * arc.radius * arc.radius * (arc.sweep - arc.sweep.sin());
|
||||
}
|
||||
None => length += chord,
|
||||
}
|
||||
}
|
||||
(length, (chord_area / 2.0 + arc_area).abs())
|
||||
}
|
||||
|
||||
fn apply_geom_prop(pline: &mut LwPolyline, field: &str, value: &str) {
|
||||
let Some(v) = parse_f64(value) else {
|
||||
return;
|
||||
|
||||
Reference in new issue
Block a user