Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/difftest/src/solidcraft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,7 @@ fn step_rust(k: &mut Kernel, bodies: &mut BTreeMap<String, Brep>, step: &Step) -
Ok(())
}

fn measure(k: &Kernel, b: &Brep) -> Result<Measured, SolidError> {
fn measure(k: &Kernel, b: &mut Brep) -> Result<Measured, SolidError> {
let store = AttributeStore::default();
let attrs = PropertyAttributes {
store: &store,
Expand All @@ -650,7 +650,7 @@ pub fn run_rust(case: &Case) -> Run {
return stop;
}
}
match bodies.get(RESULT) {
match bodies.get_mut(RESULT) {
Some(b) => measure(&k, b).map_or_else(|e| solid(&e), Run::Done),
None => Run::Error(format!("no body `{RESULT}`")),
}
Expand Down
15 changes: 9 additions & 6 deletions crates/difftest/src/units/mass_integrator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ fn geometry_rust(g: &Geometry) -> Output {
)?;
let p = props::brep_properties(
&wire_kernel,
&brep,
&mut brep,
accuracy,
Vec3::new(ox, oy, oz),
props::MassPropertyBehavior {
Expand Down Expand Up @@ -497,17 +497,20 @@ fn geometry_rust(g: &Geometry) -> Output {
};
let accuracy = p[12];
return match *query {
2 => Ok(vec![0.0, props::face_area(&fk, &brep, face, accuracy)?]),
2 => Ok(vec![0.0, props::face_area(&fk, &mut brep, face, accuracy)?]),
3 => {
let (area, c) = props::face_centroid(&fk, &brep, face, accuracy)?;
let (area, c) = props::face_centroid(&fk, &mut brep, face, accuracy)?;
Ok(vec![0.0, area, c.x, c.y, c.z])
}
4 => Ok(vec![0.0, props::brep_area(&fk, &brep, accuracy)?]),
5 => Ok(vec![0.0, props::brep_volume(&fk, &brep, accuracy, &attrs)?]),
4 => Ok(vec![0.0, props::brep_area(&fk, &mut brep, accuracy)?]),
5 => Ok(vec![
0.0,
props::brep_volume(&fk, &mut brep, accuracy, &attrs)?,
]),
6 => {
let m = props::brep_mass_properties(
&fk,
&brep,
&mut brep,
accuracy,
1.0,
Vec3::new(p[9], p[10], p[11]),
Expand Down
29 changes: 15 additions & 14 deletions crates/solidcore/examples/primitives/checks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ impl Evaluation {
}
}

fn measure(k: &Kernel, b: &Brep) -> Measured {
fn measure(k: &Kernel, b: &mut Brep) -> Measured {
let counts = (|| {
let faces = bl::faces(b, None)?;
let edges = bl::edges(b, None)?;
Expand All @@ -311,22 +311,23 @@ fn measure(k: &Kernel, b: &Brep) -> Measured {
tight: true,
expand_by_zone_tol_3d: false,
};
let bbox = calculate_bounding_box(k, b, opts).map(|ext| {
let bbox = calculate_bounding_box(k, &mut *b, opts).map(|ext| {
(
Point3::new(ext.min_at(0), ext.min_at(1), ext.min_at(2)),
Point3::new(ext.max_at(0), ext.max_at(1), ext.max_at(2)),
)
});
let properties = calculate_bounding_box(k, b, BoundingBoxOptions::default()).and_then(|ext| {
brep_properties(
k,
b,
ACCURACY,
ext.mid(),
MassPropertyBehavior::default(),
&attributes,
)
});
let properties =
calculate_bounding_box(k, &mut *b, BoundingBoxOptions::default()).and_then(|ext| {
brep_properties(
k,
b,
ACCURACY,
ext.mid(),
MassPropertyBehavior::default(),
&attributes,
)
});
Measured {
counts,
manifold_solid: is_manifold_solid(b),
Expand Down Expand Up @@ -378,7 +379,7 @@ fn unmeasured(name: &'static str, expected: Value, e: &solidcore::SolidError) ->
/// Builds `spec`, measures it and runs every check (none stops the others).
pub fn evaluate(spec: &Spec) -> Evaluation {
let mut k = Kernel::default();
let b = match (spec.build)(&mut k) {
let mut b = match (spec.build)(&mut k) {
Ok(b) => b,
Err(e) => {
return Evaluation {
Expand All @@ -388,7 +389,7 @@ pub fn evaluate(spec: &Spec) -> Evaluation {
}
}
};
let m = measure(&k, &b);
let m = measure(&k, &mut b);
let mut checks = Vec::new();
checks.push(match &m.manifold_solid {
Ok(ok) => Check {
Expand Down
88 changes: 55 additions & 33 deletions crates/solidcore/src/mass/integrator/properties.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use crate::surface::surface::Surface;
use crate::topology::face::p2::{
OdeArea, PrecisePropertyLayout, PPF_ALL, PPF_AREA, PPF_AREA_FIRST_MOMENTS,
};
use crate::topology::{edge, vertex, Brep, EdgeId, FaceId, RegionId, VertexId};
use crate::topology::{edge, vertex, Brep, EdgeId, FaceId, FaceuseId, RegionId, VertexId};
use crate::{EntityRef, Result, SolidError};

const OP: &str = "mass::integrator::properties";
Expand Down Expand Up @@ -185,27 +185,24 @@ pub fn curve_properties(
}

/// `SmFace::CreateOrientedTrimBoundary(ctx, TRUE, TRUE, ..)` (unit `topology.face_p1`) on
/// a copy of the B-rep (the caller holds it shared; the UV trim curves the call makes are
/// not kept; the copy is a whole-B-rep clone per face). The copied curves' edge-use
/// owners are not read here.
fn boundary(k: &Kernel, b: &Brep, f: FaceId) -> Result<Vec<Curve>> {
let mut copy = b.clone();
/// the caller's B-rep: the UV trim curves it makes stay there, as upstream. The returned
/// curves' edge-use owners are not read here.
fn boundary(k: &Kernel, b: &mut Brep, f: FaceId) -> Result<Vec<Curve>> {
let key_of = |e: EntityRef| crate::geom::object::ObjectRef {
type_code: 0,
key: ObjectKey(match e {
EntityRef::Edgeuse(eu) => u64::from(eu.index()),
_ => 0,
}),
};
crate::topology::face::p1::create_oriented_trim_boundary(
k, &mut copy, f, true, true, None, &key_of,
)
.map(|t| t.curves)
crate::topology::face::p1::create_oriented_trim_boundary(k, b, f, true, true, None, &key_of)
.map(|t| t.curves)
}
/// `SmFace::ComputePreciseProperties`; the default property mask is `PPF_ALL`.
/// `SmFace::ComputePreciseProperties`; the default property mask is `PPF_ALL`. Missing UV
/// trim curves of the face are made on `b`.
pub fn face_properties(
k: &Kernel,
b: &Brep,
b: &mut Brep,
f: FaceId,
orientation: OrientType,
accuracy: f64,
Expand Down Expand Up @@ -363,6 +360,16 @@ fn edges(b: &Brep) -> Result<Vec<EdgeId>> {
fn vertices(b: &Brep) -> Result<Vec<VertexId>> {
crate::topology::brep::p1::vertices(b, None)
}
/// The region on the side of face use `fu`.
fn faceuse_region(b: &Brep, fu: FaceuseId) -> Result<RegionId> {
let shell = b
.faceuse(fu)?
.shell
.ok_or_else(|| SolidError::null_pointer(OP))?;
b.shell(shell)?
.region
.ok_or_else(|| SolidError::null_pointer(OP))
}
/// `SmRegion::IsVoid` as a C truth value (`UNSURE`, 2, counts as void).
fn region_void(b: &Brep, r: RegionId) -> Result<bool> {
Ok(b.region(r)?.is_void() != 0)
Expand All @@ -384,15 +391,16 @@ fn add(total: &mut PreciseProperties, delta: &PreciseProperties, density: f64) {
}
}
/// `SmBrep::ComputePreciseProperties`, including density attributes and wire/point solids.
/// Missing UV trim curves of the integrated faces are made on `b`.
pub fn brep_properties(
k: &Kernel,
b: &Brep,
b: &mut Brep,
accuracy: f64,
origin: Point3,
behavior: MassPropertyBehavior,
attributes: &PropertyAttributes<'_>,
) -> Result<PreciseProperties> {
let bbox = qbrep::calculate_bounding_box(k, b, BoundingBoxOptions::default())?;
let bbox = qbrep::calculate_bounding_box(k, &mut *b, BoundingBoxOptions::default())?;
let size = bbox.size();
let estimated = size.z.max(size.x.max(size.y));
let estimated = estimated * estimated / 100.0;
Expand All @@ -413,16 +421,7 @@ pub fn brep_properties(
.faceuse(up)?
.mate
.ok_or_else(|| SolidError::null_pointer(OP))?;
let region = |fu| -> Result<RegionId> {
let shell = b
.faceuse(fu)?
.shell
.ok_or_else(|| SolidError::null_pointer(OP))?;
b.shell(shell)?
.region
.ok_or_else(|| SolidError::null_pointer(OP))
};
let mut separating = region(up)? != region(mate)?;
let mut separating = faceuse_region(b, up)? != faceuse_region(b, mate)?;
for (j, fu) in [up, mate].into_iter().enumerate() {
let mut do_area = false;
let mut do_volume = false;
Expand All @@ -438,7 +437,9 @@ pub fn brep_properties(
.faceuse(fu)?
.mate
.ok_or_else(|| SolidError::null_pointer(OP))?;
if !region_void(b, region(fu)?)? && region_void(b, region(other)?)? {
if !region_void(b, faceuse_region(b, fu)?)?
&& region_void(b, faceuse_region(b, other)?)?
{
do_area = true;
}
}
Expand All @@ -450,18 +451,19 @@ pub fn brep_properties(
2 => do_area = true,
_ => {}
}
if behavior.face_volume == 0 && !region_void(b, region(fu)?)? {
if behavior.face_volume == 0 && !region_void(b, faceuse_region(b, fu)?)? {
do_volume = true;
}
if !(do_area || do_volume) {
continue;
}
if separating || mass.y > EFF_ZERO {
let orientation = b.faceuse(fu)?.orientation;
let delta = face_properties(
k,
b,
f,
b.faceuse(fu)?.orientation,
orientation,
accuracy,
origin,
estimated,
Expand Down Expand Up @@ -538,7 +540,7 @@ fn area_estimate(bbox: &Extent3d) -> f64 {
d * d / 100.0
}
/// `SmApiFaceComputeArea`: clamps accuracy and integrates only area.
pub fn face_area(k: &Kernel, b: &Brep, f: FaceId, accuracy: f64) -> Result<f64> {
pub fn face_area(k: &Kernel, b: &mut Brep, f: FaceId, accuracy: f64) -> Result<f64> {
let bbox = face_box(k, b, f)?;
Ok(face_properties(
k,
Expand All @@ -555,7 +557,7 @@ pub fn face_area(k: &Kernel, b: &Brep, f: FaceId, accuracy: f64) -> Result<f64>
.abs())
}
/// `SmApiFaceComputeCentroid`: one area/first-moment integration.
pub fn face_centroid(k: &Kernel, b: &Brep, f: FaceId, accuracy: f64) -> Result<(f64, Point3)> {
pub fn face_centroid(k: &Kernel, b: &mut Brep, f: FaceId, accuracy: f64) -> Result<(f64, Point3)> {
let bbox = face_box(k, b, f)?;
let origin = bbox.mid();
let p = face_properties(
Expand All @@ -578,7 +580,7 @@ pub fn face_centroid(k: &Kernel, b: &Brep, f: FaceId, accuracy: f64) -> Result<(
))
}
/// `SmApiBrepComputeArea`, including open shells and sheets.
pub fn brep_area(k: &Kernel, b: &Brep, accuracy: f64) -> Result<f64> {
pub fn brep_area(k: &Kernel, b: &mut Brep, accuracy: f64) -> Result<f64> {
valid_api_number(accuracy)?;
let mut area = 0.0;
for f in faces(b)? {
Expand All @@ -589,12 +591,12 @@ pub fn brep_area(k: &Kernel, b: &Brep, accuracy: f64) -> Result<f64> {
/// `SmApiBrepComputeVolume`, computed about the bounding-box midpoint.
pub fn brep_volume(
k: &Kernel,
b: &Brep,
b: &mut Brep,
accuracy: f64,
attributes: &PropertyAttributes<'_>,
) -> Result<f64> {
valid_api_number(accuracy)?;
let bbox = qbrep::calculate_bounding_box(k, b, BoundingBoxOptions::default())?;
let bbox = qbrep::calculate_bounding_box(k, &mut *b, BoundingBoxOptions::default())?;
Ok(brep_properties(
k,
b,
Expand Down Expand Up @@ -627,7 +629,7 @@ fn valid_api_number(value: f64) -> Result<()> {
/// `SmApiBrepComputeMassProperties`, with upstream validation and accuracy clamping.
pub fn brep_mass_properties(
k: &Kernel,
b: &Brep,
b: &mut Brep,
accuracy: f64,
density: f64,
origin: Point3,
Expand Down Expand Up @@ -725,4 +727,24 @@ mod tests {
);
}
}
#[test]
fn face_area_makes_missing_uv_trims_on_the_callers_brep() {
// Upstream's `SmFace::ComputePreciseProperties` builds its oriented trim boundary on
// the face itself, so the UV trims it makes stay on the B-rep.
let mut k = Kernel::default();
let mut b =
crate::primitive::api_primitives::create_box(&mut k, Point3::ZERO, 1.0, 1.0, 1.0)
.unwrap();
let uses: Vec<_> = b.edgeuses().ids().collect();
for u in uses {
b.edgeuse_mut(u).unwrap().uv_trim_curve = None;
}
let f = *faces(&b).unwrap().first().unwrap();
let area = face_area(&k, &mut b, f, 1e-6).unwrap();
assert!((area - 1.0).abs() <= 1e-9, "{area}");
assert!(
b.edgeuses().iter().any(|(_, u)| u.uv_trim_curve.is_some()),
"the trims are made on the box itself"
);
}
}
4 changes: 2 additions & 2 deletions crates/solidcore/tests/primitives_solid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ fn precise_sphere_properties_scale_and_translate() {
for (center, radius) in [(Vec3::ZERO, 2.0), (Vec3::new(1.0, -2.0, 0.5), 5.0)] {
for no_pole in [false, true] {
let mut k = Kernel::default();
let b = if no_pole {
let mut b = if no_pole {
create_sphere_no_pole_with_tolerance(&mut k, center, radius, 1e-9)
} else {
create_sphere(&mut k, center, radius)
Expand All @@ -52,7 +52,7 @@ fn precise_sphere_properties_scale_and_translate() {
let key_of = |_| ObjectKey(0);
let properties = brep_properties(
&k,
&b,
&mut b,
1e-10,
bbox.mid(),
MassPropertyBehavior::default(),
Expand Down
Loading