forked from 1b-agent/opencadstudio-upstream
Merge pull request #1206 from Ramox81/codex/fix-region-measurement-persistence-20260911
This commit is contained in:
commit
c4506bf0d9
4 files changed
+73
-28
No files matched your search
+7
-13
@@ -213,14 +213,8 @@ fn entity_json(e: &acadrust::EntityType, detail: &str) -> Value {
|
||||
_ => {}
|
||||
}
|
||||
if detail == "full" {
|
||||
let bounds = e.as_entity().bounding_box();
|
||||
map.insert(
|
||||
"bounds".into(),
|
||||
json!({
|
||||
"min":[bounds.min.x,bounds.min.y,bounds.min.z],
|
||||
"max":[bounds.max.x,bounds.max.y,bounds.max.z]
|
||||
}),
|
||||
);
|
||||
let (min, max) = crate::scene::convert::tess::entity_bounds(e);
|
||||
map.insert("bounds".into(), json!({ "min": min, "max": max }));
|
||||
if let Ok(Value::Object(wrapper)) = serde_json::to_value(e) {
|
||||
if let Some((_, properties)) = wrapper.into_iter().next() {
|
||||
map.insert("properties".into(), properties);
|
||||
@@ -654,11 +648,11 @@ impl OpenCADStudio {
|
||||
continue;
|
||||
}
|
||||
if let Some(bounds) = bounds {
|
||||
let entity_bounds = e.as_entity().bounding_box();
|
||||
if entity_bounds.max.x < bounds[0]
|
||||
|| entity_bounds.max.y < bounds[1]
|
||||
|| entity_bounds.min.x > bounds[2]
|
||||
|| entity_bounds.min.y > bounds[3]
|
||||
let (min, max) = crate::scene::convert::tess::entity_bounds(e);
|
||||
if max[0] < bounds[0]
|
||||
|| max[1] < bounds[1]
|
||||
|| min[0] > bounds[2]
|
||||
|| min[1] > bounds[3]
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -968,7 +968,7 @@ impl OpenCADStudio {
|
||||
let Some(entity) = tab.scene.document.get_entity(handle) else {
|
||||
continue;
|
||||
};
|
||||
let bb = entity.as_entity().bounding_box();
|
||||
let (min, max) = crate::scene::convert::tess::entity_bounds(entity);
|
||||
let metrics = tab
|
||||
.scene
|
||||
.meshes
|
||||
@@ -994,7 +994,7 @@ impl OpenCADStudio {
|
||||
"area":planar.curve.is_closed().then(|| planar.curve.enclosed_area().abs())
|
||||
})
|
||||
});
|
||||
out.push(json!({"handle":format!("{:X}",handle.value()),"type":crate::entities::names::ui_name(entity),"bounds":{"min":[bb.min.x,bb.min.y,bb.min.z],"max":[bb.max.x,bb.max.y,bb.max.z]},"curve":curve,"mesh":metrics.map(|m|json!({"vertices":m.metrics.vertices,"triangles":m.metrics.triangles,"surface_area":m.metrics.surface_area,"volume":m.metrics.volume,"centroid":m.metrics.centroid,"moment_of_inertia":m.metrics.moment_of_inertia,"principal_directions":m.metrics.principal_directions,"principal_moments":m.metrics.principal_moments,"product_of_inertia":m.metrics.product_of_inertia,"radii_of_gyration":m.metrics.radii_of_gyration}))}));
|
||||
out.push(json!({"handle":format!("{:X}",handle.value()),"type":crate::entities::names::ui_name(entity),"bounds":{"min":min,"max":max},"curve":curve,"mesh":metrics.map(|m|json!({"vertices":m.metrics.vertices,"triangles":m.metrics.triangles,"surface_area":m.metrics.surface_area,"volume":m.metrics.volume,"centroid":m.metrics.centroid,"moment_of_inertia":m.metrics.moment_of_inertia,"principal_directions":m.metrics.principal_directions,"principal_moments":m.metrics.principal_moments,"product_of_inertia":m.metrics.product_of_inertia,"radii_of_gyration":m.metrics.radii_of_gyration}))}));
|
||||
}
|
||||
json!({"ok":true,"document_id":tab.id,"geometry_revision":tab.scene.geometry_epoch,"measurements":out})
|
||||
}
|
||||
|
||||
+54
-12
@@ -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 = ®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<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(®ion);
|
||||
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]);
|
||||
}
|
||||
}
|
||||
@@ -1718,7 +1718,16 @@ pub(crate) fn set_wire_aabb(w: &mut WireModel, entity_box: [f32; 4]) {
|
||||
w.aabb = out;
|
||||
}
|
||||
|
||||
fn entity_bounds(e: &acadrust::EntityType) -> ([f64; 3], [f64; 3]) {
|
||||
pub(crate) 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,
|
||||
|
||||
Reference in new issue
Block a user