mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-08 13:01:52 +00:00
query.face_p4: call GetLoopCrossings for the loop bisector in ClassifyLoops
SmFace::ClassifyLoops still returned Unported for a target loop that crosses no seam, although SmCurveClassification::GetLoopCrossings is ported. Call it with the target loops, the face props and the bisector's classification, as upstream does; its status stays ignored. The query.face_p4 runner now also exports the face props the bisector and GetLoopCrossings read (zone tolerance, no-vertex poles, pole points, missing seam direction). Faces whose surface or loop edge curves the importer cannot reproduce exactly report inexact_geometry on both sides when ClassifyLoops may evaluate them, decided from the export as unit query.face_p3 does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,16 +17,22 @@
|
||||
//! does (`SetProps_Stage2`, `ReSet(SM_PROPSTAGE_3)`, the loop props) and exports the
|
||||
//! values `ClassifyLoops` reads (stage, closed flags, poles, the loops' seam crossing
|
||||
//! and winding counts, the seam classifications' end point classes and vertices, the
|
||||
//! seam done-touch lists); both sides then run `ClassifyLoops` on those same props (the
|
||||
//! seam done-touch lists, and for the loop bisector and `GetLoopCrossings` the zone
|
||||
//! tolerance, no-vertex poles, pole points and missing seam direction); both sides then run `ClassifyLoops` on those same props (the
|
||||
//! Rust side imports them; the seam classifications' non-vertex point classifications
|
||||
//! carry no object, which `ClassifyLoops` never reads). The target loops are exported
|
||||
//! too. With `props` `0` the props are fresh and `ClassifyLoops` computes them itself
|
||||
//! (`SetProps_Stage2`; the Rust side runs the port of unit `heal.face_props`).
|
||||
//!
|
||||
//! **Deferred.** A target loop that crosses no seam the face props classified needs
|
||||
//! `sms_FindAndClassifyLoopBisector` (`query.face_p3`) and `GetLoopCrossings`
|
||||
//! (`intersect.curve_class_p4`); fresh props of a closed surface need
|
||||
//! `SmFace::HasSeamProblem` (`topology.face_p4`).
|
||||
//! **Inexact geometry.** Faces whose surface, and edges whose curve, the importer cannot
|
||||
//! reproduce exactly (unit `topology.face_p2`'s `Exact`) are not compared when
|
||||
//! `ClassifyLoops` may evaluate them: with fresh props (`SetProps_Stage2`), or when a
|
||||
//! target loop's imported loop props show no crossing of a closed seam (the loop
|
||||
//! bisector and `GetLoopCrossings` evaluate the edge curves). Both sides then report
|
||||
//! `inexact_geometry`, decided from the export alone.
|
||||
//!
|
||||
//! **Deferred.** Fresh props of a closed surface need `SmFace::HasSeamProblem`
|
||||
//! (`topology.face_p4`), which reaches its unported callees.
|
||||
//!
|
||||
//! **Rejected** (both sides report `rejected`): the inputs the FFI wrapper documents,
|
||||
//! where `ClassifyLoops` would read outside its arrays.
|
||||
@@ -50,13 +56,16 @@ use solidcore::intersect::point_class::{ClassObject, ClassObjectKind};
|
||||
use solidcore::intersect::curve_class::p1::{CrvDirOnFaceType, CurveClassification, TouchData};
|
||||
use solidcore::intersect::point_class::PointClassType;
|
||||
use solidcore::kernel::Kernel;
|
||||
use solidcore::math::Point3;
|
||||
use solidcore::nurbs::b_spline_surface_n_lib::p2::OrientType;
|
||||
use solidcore::nurbs::b_spline_surface::SurfParamType;
|
||||
use solidcore::query::face::p4 as qf4;
|
||||
use solidcore::topology::{EdgeId, EdgeuseId, FaceId, LoopId, VertexId};
|
||||
use solidcore::topology::face::p2 as face_p2;
|
||||
use solidcore::topology::{edgeuse::p1 as eu1, Brep, EdgeId, EdgeuseId, FaceId, LoopId, VertexId};
|
||||
use solidcore_ffi::units::query_face_p4::{self as ffi, Op};
|
||||
|
||||
use super::topology_edgeuse_p1::{domain, flag, gen_surface, push_surface, SrfIn, FILES};
|
||||
use super::topology_face_p2::import;
|
||||
use super::topology_face_p2::{import, Exact};
|
||||
use crate::case::CaseContext;
|
||||
use crate::runner::default_cases;
|
||||
use crate::{Case, HarnessError, Output, Rng, UnitDiff};
|
||||
@@ -483,6 +492,10 @@ fn result_output(
|
||||
}
|
||||
|
||||
fn cpp(i: &Inputs) -> Result<Output, HarnessError> {
|
||||
// the same rejections as the Rust side (failed build, rejected, inexact geometry)
|
||||
if let Err(out) = export(&Kernel::default(), i)? {
|
||||
return Ok(out);
|
||||
}
|
||||
let o = run_ffi(Op::Classify, i)?;
|
||||
let mut rd = Rd { v: &o, at: 0 };
|
||||
if rd.num()? != 0.0 {
|
||||
@@ -585,21 +598,81 @@ fn tri(code: i64) -> Result<Tri, HarnessError> {
|
||||
})
|
||||
}
|
||||
|
||||
fn rust(i: &Inputs) -> Result<Output, HarnessError> {
|
||||
let k = Kernel::default();
|
||||
/// What the export of a case gives the Rust side.
|
||||
struct Exported {
|
||||
b: Brep,
|
||||
face: FaceId,
|
||||
props: FaceProps<H>,
|
||||
loops: Vec<LoopId>,
|
||||
}
|
||||
|
||||
/// Whether the surface of `face` or an edge curve of its loops or of `loops` was not
|
||||
/// imported exactly ([`Exact`]).
|
||||
fn face_or_edges_inexact(b: &Brep, exact: &Exact, face: FaceId, loops: &[LoopId]) -> bool {
|
||||
if !exact.faces.get(face.index() as usize).copied().unwrap_or(true) {
|
||||
return true;
|
||||
}
|
||||
let mut all = face_p2::loops(b, face, false, None).unwrap_or_default();
|
||||
all.extend_from_slice(loops);
|
||||
all.into_iter().any(|l| {
|
||||
let Some(lu) = b.loop_(l).ok().and_then(|l| l.loopuse) else {
|
||||
return false;
|
||||
};
|
||||
eu1::loopuse_edgeuses(b, lu).is_ok_and(|eus| {
|
||||
eus.into_iter().any(|eu| {
|
||||
eu1::edge(b, eu).ok().flatten().is_some_and(|e| {
|
||||
!exact.edges.get(e.index() as usize).copied().unwrap_or(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether `ClassifyLoops` may evaluate geometry on this input: fresh props (`props` `0`,
|
||||
/// computed by `SetProps_Stage2`), or a pass over a target loop whose imported loop props
|
||||
/// show no crossing of a closed seam (the loop bisector and `GetLoopCrossings`). With
|
||||
/// imported props a seam-crossing pass reads only the exported touch lists.
|
||||
fn evaluates_geometry(
|
||||
i: &Inputs,
|
||||
b: &Brep,
|
||||
face: FaceId,
|
||||
props: &FaceProps<H>,
|
||||
loops: &[LoopId],
|
||||
) -> bool {
|
||||
if i.props != 1 {
|
||||
return true;
|
||||
}
|
||||
let n = if loops.is_empty() {
|
||||
face_p2::loops(b, face, false, None).map_or(0, |l| l.len())
|
||||
} else {
|
||||
loops.len()
|
||||
};
|
||||
let closed = |t: Tri| !matches!(t, Tri::False);
|
||||
(0..n).any(|ii| {
|
||||
props.loop_props.get(ii).is_some_and(|lp| {
|
||||
!(closed(props.closed_u) && lp.seam_crossing_cnt_u > 0)
|
||||
&& !(closed(props.closed_v) && lp.seam_crossing_cnt_v > 0)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// The export of `i`: the imported B-rep, face, props and target loops; `Err(output)` for
|
||||
/// a failed build, a rejected case or inexact geometry the call may evaluate (the same
|
||||
/// output on both sides).
|
||||
fn export(k: &Kernel, i: &Inputs) -> Result<Result<Exported, Output>, HarnessError> {
|
||||
let o = run_ffi(Op::Export, i)?;
|
||||
if o.first() != Some(&0.0) {
|
||||
return Ok(Output::err(format!(
|
||||
return Ok(Err(Output::err(format!(
|
||||
"build_failed: {}",
|
||||
o.get(1).copied().unwrap_or(-9.0)
|
||||
)));
|
||||
))));
|
||||
}
|
||||
let (graph, block) = ffi::split_export(&o).ok_or_else(|| harness("bad export"))?;
|
||||
let mut rd = Rd { v: block, at: 0 };
|
||||
if rd.num()? != 0.0 {
|
||||
return Ok(Output::err("rejected"));
|
||||
return Ok(Err(Output::err("rejected")));
|
||||
}
|
||||
let (b, _exact) = import(graph)?;
|
||||
let (b, exact) = import(graph)?;
|
||||
let face_index = rd.int()?;
|
||||
let face = FaceId::from_index(u32::try_from(face_index).map_err(|_| harness("bad face"))?);
|
||||
|
||||
@@ -620,12 +693,20 @@ fn rust(i: &Inputs) -> Result<Output, HarnessError> {
|
||||
lp.seam_winding_cnt_v = rd.int()? as u64;
|
||||
loop_props.push(lp);
|
||||
}
|
||||
let crv_class_u = classification(&k, &mut rd)?;
|
||||
let crv_class_v = classification(&k, &mut rd)?;
|
||||
let crv_class_u = classification(k, &mut rd)?;
|
||||
let crv_class_v = classification(k, &mut rd)?;
|
||||
let done_u = touches(&mut rd)?;
|
||||
let done_v = touches(&mut rd)?;
|
||||
let zone_tol = rd.num()?;
|
||||
let no_vertex_poles = rd.int()?;
|
||||
let bad_missing_seam = rd.int()?;
|
||||
let n_pp = rd.count()?;
|
||||
let mut pole_points = Vec::new();
|
||||
for _ in 0..n_pp {
|
||||
pole_points.push(Point3::new(rd.num()?, rd.num()?, rd.num()?));
|
||||
}
|
||||
let n_loops = rd.count()?;
|
||||
let mut loops = rd
|
||||
let loops = rd
|
||||
.ints(n_loops)?
|
||||
.into_iter()
|
||||
.map(loop_id)
|
||||
@@ -644,8 +725,40 @@ fn rust(i: &Inputs) -> Result<Output, HarnessError> {
|
||||
props.crv_class_v = crv_class_v;
|
||||
props.done_touch_list_u = done_u;
|
||||
props.done_touch_list_v = done_v;
|
||||
props.zone_tol_3d = zone_tol;
|
||||
props.no_vertex_poles =
|
||||
u64::try_from(no_vertex_poles).map_err(|_| harness("bad no-vertex poles"))?;
|
||||
props.bad_missing_seam_dir = u64::try_from(bad_missing_seam)
|
||||
.ok()
|
||||
.and_then(SurfParamType::from_code)
|
||||
.ok_or_else(|| harness("bad SmSurfParamType"))?;
|
||||
props.pole_points = pole_points;
|
||||
}
|
||||
|
||||
if face_or_edges_inexact(&b, &exact, face, &loops)
|
||||
&& evaluates_geometry(i, &b, face, &props, &loops)
|
||||
{
|
||||
return Ok(Err(Output::err("inexact_geometry")));
|
||||
}
|
||||
Ok(Ok(Exported {
|
||||
b,
|
||||
face,
|
||||
props,
|
||||
loops,
|
||||
}))
|
||||
}
|
||||
|
||||
fn rust(i: &Inputs) -> Result<Output, HarnessError> {
|
||||
let k = Kernel::default();
|
||||
let Exported {
|
||||
b,
|
||||
face,
|
||||
mut props,
|
||||
mut loops,
|
||||
} = match export(&k, i)? {
|
||||
Ok(e) => e,
|
||||
Err(out) => return Ok(out),
|
||||
};
|
||||
let mut out = initial_out();
|
||||
let mut scratch: CurveClassification<H> = CurveClassification::new(&k);
|
||||
let r = qf4::classify_loops(&k, &b, face, &mut loops, &mut out, &mut scratch, &mut props);
|
||||
|
||||
@@ -37,7 +37,8 @@
|
||||
//! | `Classify` | `rejected [status loops classifiable not_yet n_io (loop type orient degen area containment)* start stop n_no_area idx* tree scratch_size]` |
|
||||
//!
|
||||
//! `props` is `stage face_index closed_u closed_v poles not_yet n_lp (cross_u wind_u
|
||||
//! cross_v wind_v)* class_u class_v done_u done_v`; a classification is `0` (none) or `1
|
||||
//! cross_v wind_v)* class_u class_v done_u done_v zone_tol no_vertex_poles
|
||||
//! bad_missing_seam n_pp (x y z)*` (`n_pp` pole points); a classification is `0` (none) or `1
|
||||
//! n (start_class start_vertex end_class end_vertex)*` (the vertex's export index for a
|
||||
//! vertex classification, else `-1`); a touch list `n (loop dir loop_index)*` (`loop`: the
|
||||
//! loop's export index, `-1` none, `-2` not a loop); `loops` `n idx*`; `tree` `n (pole
|
||||
|
||||
@@ -49,10 +49,10 @@
|
||||
//! [`LoopObject::from_classify_curve`]; `EntityRef` cannot name curves) cannot run
|
||||
//! the bisector: `ClassifyLoops` then returns [`SolidError::InvalidInput`]
|
||||
//! ([`qf3::NO_CURVE_HANDLES`]) instead of ignoring it as a status.
|
||||
//! - **Unported callees** (`UNPORTED` stubs): `SmCurveClassification::GetLoopCrossings`
|
||||
//! (`intersect.curve_class_p4`), reached only for a target loop that crosses no seam
|
||||
//! the face props already classified. Faces whose loops all cross a periodic seam
|
||||
//! (no-area loops of a missing seam) are classified entirely by the port.
|
||||
//! - **Ignored `GetLoopCrossings` status.** A failing call leaves the touch lists it
|
||||
//! reset at entry; an `SM_ERR` return keeps what it filled. Errors for upstream's
|
||||
//! undefined behaviour ([`SolidError::InvalidInput`], [`SolidError::NotFound`]) and
|
||||
//! unported callees propagate.
|
||||
//! - **Undefined behaviour upstream** is [`SolidError::InvalidInput`]: an index read
|
||||
//! outside `rClassifyLoopIOs`, the face props' loop props or a touch list (odd length),
|
||||
//! an empty seam classification read at its first or last interval, a loop without
|
||||
@@ -263,19 +263,24 @@ fn find_and_classify_loop_bisector<O: LoopObject>(
|
||||
}
|
||||
|
||||
/// `rCrvClassification.GetLoopCrossings(rLoops, &rFaceProps, &sCrossingCounts,
|
||||
/// &sRawTouchDirList, &sDoneTouchDirList)`: the done touch list.
|
||||
/// &sRawTouchDirList, &sDoneTouchDirList)`: the done touch list (the counts and the raw
|
||||
/// list are not read by `ClassifyLoops`). `curves` are the bisector curves
|
||||
/// `classification` names. The status is not checked upstream: a failing call leaves
|
||||
/// the lists it reset at entry empty, except where upstream's behaviour is undefined or
|
||||
/// a callee is unported (propagated).
|
||||
fn get_loop_crossings<O: LoopObject>(
|
||||
_k: &Kernel,
|
||||
_b: &Brep,
|
||||
_classification: &CurveClassification<O>,
|
||||
_loops: &[LoopId],
|
||||
_props: &FaceProps<O>,
|
||||
k: &Kernel,
|
||||
curves: &qf3::LoopObjectCurves<'_, O>,
|
||||
classification: &CurveClassification<O>,
|
||||
loops: &[LoopId],
|
||||
props: &mut FaceProps<O>,
|
||||
) -> Result<Vec<TouchData<O>>> {
|
||||
// UNPORTED(intersect.curve_class_p4): SmCurveClassification::GetLoopCrossings
|
||||
Err(SolidError::unported(
|
||||
"SmCurveClassification::GetLoopCrossings",
|
||||
"intersect.curve_class_p4",
|
||||
))
|
||||
let handles: Vec<O> = loops.iter().map(|&l| O::from_loop(l)).collect();
|
||||
match classification.get_loop_crossings(k, curves, &handles, Some(props)) {
|
||||
Ok(c) => Ok(c.done_touches),
|
||||
Err(e @ (SolidError::InvalidInput { .. } | SolidError::NotFound { .. })) => Err(e),
|
||||
Err(e) => ignored(Err(e)).map(|_: Option<()>| Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// The orientation of an entering or pairing touch: `SM_OT_SAME` for an arrive-out /
|
||||
@@ -310,10 +315,10 @@ fn touch_orient<O: Copy>(done: &[TouchData<O>], j1: usize) -> Result<OrientType>
|
||||
/// `SM_ERR` ([`SolidError::sm_err`]) for a face without loops or a classification curve
|
||||
/// without touches to walk; [`SolidError::InvalidInput`] ([`NOT_THIS_FACE`],
|
||||
/// [`NO_SURFACE`]) when the first loop is not on `face` or the face has no surface, and
|
||||
/// where upstream's behaviour is undefined (module docs); [`SolidError::Unported`] for a
|
||||
/// target loop that crosses no seam (the bisector's callees, `intersect.curve_class_p4`);
|
||||
/// [`SolidError::InvalidInput`] ([`qf3::NO_CURVE_HANDLES`]) when such a loop meets a
|
||||
/// handle type that cannot name the bisector's curves; the
|
||||
/// where upstream's behaviour is undefined (module docs); [`SolidError::InvalidInput`]
|
||||
/// ([`qf3::NO_CURVE_HANDLES`]) when a target loop that crosses no seam meets a handle
|
||||
/// type that cannot name the bisector's curves; the errors of the bisector and of
|
||||
/// [`CurveClassification::get_loop_crossings`] other than its ignored statuses; the
|
||||
/// errors of the topology queries and of [`FaceProps::set_props_stage2`]'s
|
||||
/// [`SolidError::Unported`].
|
||||
#[allow(clippy::too_many_lines)]
|
||||
@@ -463,8 +468,7 @@ pub fn classify_loops<O: LoopObject>(
|
||||
|
||||
// UVTrimLine Vertex and Edge TgtLoop_Crossings (the status is not checked)
|
||||
crv = ClassifyCrv::Bisector;
|
||||
bisector_done =
|
||||
ignored(get_loop_crossings(k, b, scratch, loops, props))?.unwrap_or_default();
|
||||
bisector_done = get_loop_crossings(k, &bisector_curves, scratch, loops, props)?;
|
||||
}
|
||||
let done: &[TouchData<O>] = match crv {
|
||||
ClassifyCrv::SeamU => &props.done_touch_list_u,
|
||||
|
||||
@@ -1083,6 +1083,17 @@ void put_props(std::vector<double>& o, Graph& g, const SmFaceProps& p)
|
||||
put_classification(o, g, p.m_pCrvClassV);
|
||||
put_touches(o, g, p.m_sDoneTouchListU);
|
||||
put_touches(o, g, p.m_sDoneTouchListV);
|
||||
// what the loop bisector and GetLoopCrossings read
|
||||
o.push_back((double)p.m_sZoneTol3d);
|
||||
o.push_back((double)p.m_lNoVertexPoles);
|
||||
o.push_back((double)p.m_eBadMissingSeam);
|
||||
o.push_back((double)p.m_sPolePoints.GetSize());
|
||||
for (ULONG i = 0; i < p.m_sPolePoints.GetSize(); i++)
|
||||
{
|
||||
o.push_back(p.m_sPolePoints[i].x);
|
||||
o.push_back(p.m_sPolePoints[i].y);
|
||||
o.push_back(p.m_sPolePoints[i].z);
|
||||
}
|
||||
}
|
||||
|
||||
void put_loops(std::vector<double>& o, Graph& g, const SmTArray<SmLoop*>& l)
|
||||
|
||||
Reference in New Issue
Block a user