Merge pull request #1206 from Ramox81/codex/fix-region-measurement-persistence-20260911

This commit is contained in:
Hakan Seven committed 2026-09-11 16:08:10 +03:00
commit c4506bf0d9
4 files changed
+73 -28

No files matched your search

+54 -12
View File
@@ -260,17 +260,26 @@ fn position_section(prefix: &str, p: &acadrust::types::Vector3) -> PropSection {
}
}
/// Approximate a region's enclosed area and boundary perimeter from its
/// wireframe loops. Perimeter is the total edge length across every wire.
/// Area accumulates the Newell area vector of each loop (opposite-wound
/// holes subtract) and halves its magnitude — exact for a single planar
/// loop, approximate for multi-loop or curved regions. Returns zeros when
/// there is nothing to measure.
/// The area and perimeter of a region's boundary wires.
///
/// Measured in space rather than in projection: a region need not lie in a
/// coordinate plane, and flattening it to XY first would report its shadow.
fn region_area_perimeter(wires: &[acadrust::entities::Wire]) -> (f64, f64) {
/// Measure decoded planar curves in a unit frame, retaining legacy wire-only input.
fn region_area_perimeter(region: &Region) -> (f64, f64) {
let exact = (|| {
let (plane, loops, true) = crate::scene::model::presspull_model::profile_geometry(
&EntityType::Region(region.clone()),
)? else { return None; };
let unit = cadkernel::space::Plane::orthonormal(plane.origin, plane.x_axis, plane.normal()?)?;
let transform = cadkernel::geom2d::Transform {
origin: unit.project(plane.origin)?.into(),
x_axis: unit.project_vector(plane.x_axis)?.into(),
y_axis: unit.project_vector(plane.y_axis)?.into(),
};
let curves = loops.iter().flatten().map(|curve| curve.transformed(&transform))
.collect::<Option<Vec<_>>>()?;
let area = curves.iter().map(|curve| curve.enclosed_area()).sum::<f64>().abs();
let perimeter = curves.iter().map(|curve| curve.length()).sum::<f64>();
(area.is_finite() && perimeter.is_finite()).then_some((area, perimeter))
})();
if let Some(measurements) = exact { return measurements; }
let wires = &region.wires;
let mut area_vector = [0.0f64; 3];
let mut perimeter = 0.0;
for wire in wires {
@@ -351,7 +360,7 @@ impl Grippable for Region {
impl PropertyEditable for Region {
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
let (area, perimeter) = region_area_perimeter(&self.wires);
let (area, perimeter) = region_area_perimeter(self);
let mut sections =
acis_sections(&self.acis_data, &self.wires, &self.silhouettes, self.history_handle);
sections[0]
@@ -912,3 +921,36 @@ pub fn tessellate_volume(
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use cadkernel::geom2d::{Curve, Line};
fn ring(points: &[[f64; 2]]) -> Vec<Curve> {
points.iter().copied().zip(points.iter().copied().cycle().skip(1))
.take(points.len()).map(|(start, end)| Curve::Line(Line { start, end })).collect()
}
#[test]
fn decoded_region_reports_exact_hole_area_perimeter_and_bounds() {
let loops = vec![
ring(&[[0.0, 0.0], [10.0, 0.0], [10.0, 10.0], [0.0, 10.0]]),
ring(&[[2.0, 2.0], [2.0, 4.0], [4.0, 4.0], [4.0, 2.0]]),
];
let EntityType::Region(mut region) =
crate::scene::model::presspull_model::region_from_loops(
&loops,
crate::command::WorkingPlane::default(),
).unwrap()
else { unreachable!() };
region.wires.clear();
let (area, perimeter) = region_area_perimeter(&region);
assert!((area - 96.0).abs() < 1e-8, "{area}");
assert!((perimeter - 48.0).abs() < 1e-8, "{perimeter}");
let (min, max) = crate::scene::convert::tess::entity_bounds(&EntityType::Region(region));
assert_eq!(min, [0.0, 0.0, 0.0]);
assert_eq!(max, [10.0, 10.0, 0.0]);
}
}