diff --git a/src/app/doc_api.rs b/src/app/doc_api.rs index 4c27a8fa1..78ae5dcb6 100644 --- a/src/app/doc_api.rs +++ b/src/app/doc_api.rs @@ -1244,11 +1244,14 @@ impl DocApiBackend for HostSession<'_> { fn bounds(&mut self, id: ObjectId) -> ApiResult { let handle = obj_to_handle(id); - // Lift-on-miss for solids (consistent with volume/centroid); borrow the cache + // Lift-on-miss for modeler geometry (consistent with volume/centroid); borrow the cache // read-only via with_body (O(1), no B-rep deep clone). if matches!( self.document().get_entity(handle), Some(EntityType::Solid3D(_)) + ) || matches!( + self.document().get_entity(handle), + Some(EntityType::Region(region)) if region.acis_data.has_data() ) { let mut f = |body: &KernelBody| -> ApiResult { let bb = cadkernel::brep::body_bounds(body) diff --git a/src/entities/solid3d.rs b/src/entities/solid3d.rs index 8cf4f1924..57f97f580 100644 --- a/src/entities/solid3d.rs +++ b/src/entities/solid3d.rs @@ -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::>>()?; + let area = curves.iter().map(|curve| curve.enclosed_area()).sum::().abs(); + let perimeter = curves.iter().map(|curve| curve.length()).sum::(); + (area.is_finite() && perimeter.is_finite()).then_some((area, perimeter)) + })(); + if let Some(measurements) = exact { return measurements; } + let wires = ®ion.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 { - 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] diff --git a/src/scene/convert/tess.rs b/src/scene/convert/tess.rs index a84d3d60d..18e3d3123 100644 --- a/src/scene/convert/tess.rs +++ b/src/scene/convert/tess.rs @@ -1719,6 +1719,15 @@ pub(crate) fn set_wire_aabb(w: &mut WireModel, entity_box: [f32; 4]) { } fn entity_bounds(e: &acadrust::EntityType) -> ([f64; 3], [f64; 3]) { + if let acadrust::EntityType::Region(region) = e { + if region.wires.is_empty() { + if let Some(bounds) = super::solid3d_tess::kernel_region_body(region) + .and_then(|body| cadkernel::brep::body_bounds(&body)) + { + return (bounds.min, bounds.max); + } + } + } if let acadrust::EntityType::Line(line) = e { if let Some(association) = acadrust::entities::CenterMarkAssociation::read( &line.common.extended_data,