mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-08 13:01:52 +00:00
Fix lazy classifier target-cache tolerance and preserve valid closed Hermite inputs
Co-Authored-By: OpenAI GPT-6.1 <noreply@openai.com>
This commit is contained in:
@@ -110,11 +110,7 @@ pub fn validate(input: &[f64]) -> Result<(), FfiError> {
|
||||
if e[0] != 0. || header[8] != 1. {
|
||||
return Err(invalid());
|
||||
}
|
||||
} else if e[0] == 1. {
|
||||
if e[11..14].iter().zip(&e[17..20]).all(|(a, b)| a == b) {
|
||||
return Err(invalid());
|
||||
}
|
||||
} else if e[1..4].iter().zip(&e[4..7]).all(|(a, b)| a == b) {
|
||||
} else if e[0] == 0. && e[1..4].iter().zip(&e[4..7]).all(|(a, b)| a == b) {
|
||||
return Err(invalid());
|
||||
}
|
||||
}
|
||||
@@ -192,10 +188,10 @@ mod tests {
|
||||
let mut same = i.clone();
|
||||
same[22 + 9] = 1.;
|
||||
assert!(validate(&same).is_err());
|
||||
// The cubic needs two distinct endpoints.
|
||||
let mut collapsed = i.clone();
|
||||
collapsed[22 + 17..22 + 20].copy_from_slice(&[0.3, 0.6, -0.4]);
|
||||
assert!(validate(&collapsed).is_err());
|
||||
// A nonconstant cubic may close on itself; the wire constructor glues its ends.
|
||||
let mut closed = i.clone();
|
||||
closed[22 + 17..22 + 20].copy_from_slice(&[0.3, 0.6, -0.4]);
|
||||
assert!(validate(&closed).is_ok());
|
||||
// A line record still needs distinct endpoints, and unknown kinds are rejected.
|
||||
let mut repeated = i.clone();
|
||||
repeated[22] = 0.;
|
||||
|
||||
@@ -25,8 +25,9 @@
|
||||
//! itself then names stand-ins for the four curves (`operand_handles`) reporting
|
||||
//! their natural intervals, so `IdenticalSolutions` merges within 1/10000th of
|
||||
//! them and `sm_RefineCCSolutions` resolves each curve, as upstream.
|
||||
//! - **Caches.** The caches are built for the call with [`make_curve_cache`] (see
|
||||
//! unit `curve.curve_p5`), or kept across calls in slots the caller lends
|
||||
//! - **Caches.** The caches are built for the call with
|
||||
//! [`make_curve_cache`](crate::curve::curve::p5::make_curve_cache) (see unit
|
||||
//! `curve.curve_p5`), or kept across calls in slots the caller lends
|
||||
//! (`global_curve_intersect_cached`, as upstream keeps them on the curves; the
|
||||
//! composite curve offsetter uses it for its offset pairs, and
|
||||
//! `global_curve_intersect_unnamed_cached` serves callers that name no handles,
|
||||
@@ -34,6 +35,8 @@
|
||||
//! identity, [`core::ptr::eq`]) shares one cache and so one tree, which makes the
|
||||
//! intersection a self intersection, as upstream's shared object cache. A cache that cannot be built is
|
||||
//! `SM_ERR_NULL_POINTER` (upstream `NER`).
|
||||
//! Callers holding topology may supply each curve owner's zone tolerance to the
|
||||
//! lazy build without changing intersection tolerance or copying geometry.
|
||||
//! - **Leaf pair solve.** Upstream calls `LocalSolve` on the intersection test
|
||||
//! solver directly, which appends to the solution array the solver still points
|
||||
//! at; the port lends the array to the solver for that call.
|
||||
@@ -57,7 +60,7 @@
|
||||
use crate::curve::curve::p1::refine_cc_solutions;
|
||||
use crate::curve::curve::p2::{at, CciGlobalSolver, Curve};
|
||||
use crate::curve::curve::p4::{analytic_curve_intersect, point_or_undef, AnalyticOperand};
|
||||
use crate::curve::curve::p5::make_curve_cache;
|
||||
use crate::curve::curve::p5::make_curve_cache_with_zone_tol;
|
||||
use crate::curve::curve_cache::CurveCache;
|
||||
use crate::error::{ignored, Result, SolidError};
|
||||
use crate::geom::extent1d::Extent1d;
|
||||
@@ -285,13 +288,24 @@ fn cache_tree<O: SolutionObject>(cache: &CurveCache<O>) -> Result<&Tree<O>> {
|
||||
cache.tree().ok_or(SolidError::null_pointer(OP_GCI))
|
||||
}
|
||||
|
||||
/// `GetOrCreateObjectCache` for a curve (`NER` on failure).
|
||||
/// `GetOrCreateObjectCache` for a curve (`NER` on failure). `zone_tol_3d` is the zone
|
||||
/// tolerance the caller resolved for the curve's owner: an edge curve's cache
|
||||
/// tessellates at half that tolerance ([`make_curve_cache_with_zone_tol`]), as
|
||||
/// upstream's `SmTol::GetApproxTol3d(&m_crCurve)` reads it from the curve's edge
|
||||
/// (`SmCurveCache.cpp:1496`). `None` preserves the curve's recorded owner tolerance,
|
||||
/// falling back to the system tolerance when the curve has none.
|
||||
fn build_cache<O: SolutionObject>(
|
||||
k: &Kernel,
|
||||
curve: &Curve,
|
||||
handle: Option<O>,
|
||||
zone_tol_3d: Option<f64>,
|
||||
) -> Result<CurveCache<O>> {
|
||||
match make_curve_cache::<O>(k, curve, handle) {
|
||||
match make_curve_cache_with_zone_tol::<O>(
|
||||
k,
|
||||
curve,
|
||||
handle,
|
||||
zone_tol_3d.or_else(|| curve.owner_zone_tol_3d()),
|
||||
) {
|
||||
Ok(c) => Ok(c),
|
||||
Err(e) if e.is_unported() => Err(e),
|
||||
Err(_) => Err(SolidError::null_pointer(OP_GCI)),
|
||||
@@ -375,6 +389,7 @@ impl Curve {
|
||||
opts,
|
||||
&mut None,
|
||||
&mut None,
|
||||
[None, None],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -402,9 +417,63 @@ impl Curve {
|
||||
opts: &CurveIntersectOptions<O>,
|
||||
cache1: &mut Option<CurveCache<O>>,
|
||||
cache2: &mut Option<CurveCache<O>>,
|
||||
) -> Result<SolutionArray<O>> {
|
||||
self.global_curve_intersect_cached_with_zone_tols(
|
||||
k,
|
||||
interval,
|
||||
other,
|
||||
other_interval,
|
||||
dist_tol,
|
||||
opts,
|
||||
cache1,
|
||||
cache2,
|
||||
[None, None],
|
||||
)
|
||||
}
|
||||
|
||||
/// [`Curve::global_curve_intersect_cached`] with the zone tolerances the caller
|
||||
/// resolved for the curves' owners. Upstream builds each curve's cache on the curve
|
||||
/// object itself, so an edge curve tessellates at half the edge's tolerance
|
||||
/// (`SmTol::GetApproxTol3d(&m_crCurve)`, `SmCurveCache.cpp:1496`); a caller whose
|
||||
/// handle geometry is not that owned object supplies the tolerance here. A slot is
|
||||
/// used exactly as in [`Curve::global_curve_intersect_cached`]: nothing is built
|
||||
/// before the body reaches it, and a `None` tolerance builds as that method does.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// As [`Curve::global_curve_intersect`].
|
||||
pub(crate) fn global_curve_intersect_cached_with_zone_tols<O: SolutionObject>(
|
||||
&self,
|
||||
k: &Kernel,
|
||||
interval: &Extent1d,
|
||||
other: &Self,
|
||||
other_interval: &Extent1d,
|
||||
dist_tol: f64,
|
||||
opts: &CurveIntersectOptions<O>,
|
||||
cache1: &mut Option<CurveCache<O>>,
|
||||
cache2: &mut Option<CurveCache<O>>,
|
||||
zone_tols: [Option<f64>; 2],
|
||||
) -> Result<SolutionArray<O>> {
|
||||
if opts.handles == [None, None] && opts.analytic_handles == [None, None] {
|
||||
return self.global_curve_intersect(k, interval, other, other_interval, dist_tol, opts);
|
||||
let own = operand_handles(self, other);
|
||||
let inner = CurveIntersectOptions {
|
||||
skip_global_coincidence_check: opts.skip_global_coincidence_check,
|
||||
handles: [Some(own[0]), Some(own[1])],
|
||||
analytic_handles: [Some(own[2]), Some(own[3])],
|
||||
};
|
||||
return Ok(self
|
||||
.global_curve_intersect_cached_with_zone_tols(
|
||||
k,
|
||||
interval,
|
||||
other,
|
||||
other_interval,
|
||||
dist_tol,
|
||||
&inner,
|
||||
&mut None,
|
||||
&mut None,
|
||||
zone_tols,
|
||||
)?
|
||||
.without_objects());
|
||||
}
|
||||
self.global_curve_intersect_body(
|
||||
k,
|
||||
@@ -415,6 +484,7 @@ impl Curve {
|
||||
opts,
|
||||
cache1,
|
||||
cache2,
|
||||
zone_tols,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -456,12 +526,16 @@ impl Curve {
|
||||
&inner,
|
||||
cache1,
|
||||
cache2,
|
||||
[None, None],
|
||||
)?
|
||||
.without_objects())
|
||||
}
|
||||
|
||||
/// The body of [`Curve::global_curve_intersect`] for a caller giving handles, with
|
||||
/// the cache slots of [`Curve::global_curve_intersect_cached`].
|
||||
/// the cache slots of [`Curve::global_curve_intersect_cached`]. `zone_tols` are the
|
||||
/// zone tolerances the caller resolved for the curves' owners, used only where a
|
||||
/// slot's cache is built ([`build_cache`]); `None` preserves the corresponding
|
||||
/// curve's recorded owner tolerance.
|
||||
fn global_curve_intersect_body<O: SolutionObject>(
|
||||
&self,
|
||||
k: &Kernel,
|
||||
@@ -472,6 +546,7 @@ impl Curve {
|
||||
opts: &CurveIntersectOptions<O>,
|
||||
cache1_slot: &mut Option<CurveCache<O>>,
|
||||
cache2_slot: &mut Option<CurveCache<O>>,
|
||||
zone_tols: [Option<f64>; 2],
|
||||
) -> Result<SolutionArray<O>> {
|
||||
let [h_this, h_other] = opts.handles;
|
||||
let same_curve = core::ptr::eq(self, other);
|
||||
@@ -518,12 +593,13 @@ impl Curve {
|
||||
}
|
||||
|
||||
// get target curves' caches (kept in the caller's slots, see
|
||||
// `global_curve_intersect_cached`)
|
||||
// `global_curve_intersect_cached`), each at its owner's zone tolerance when the
|
||||
// caller resolved one
|
||||
if cache1_slot.is_none() {
|
||||
*cache1_slot = Some(build_cache(k, self, h_this)?);
|
||||
*cache1_slot = Some(build_cache(k, self, h_this, zone_tols[0])?);
|
||||
}
|
||||
if !same_curve && cache2_slot.is_none() {
|
||||
*cache2_slot = Some(build_cache(k, other, h_other)?);
|
||||
*cache2_slot = Some(build_cache(k, other, h_other, zone_tols[1])?);
|
||||
}
|
||||
let cache1 = cache1_slot
|
||||
.as_mut()
|
||||
|
||||
@@ -17,7 +17,11 @@
|
||||
//! clearing is represented by a private copy for a lifted 2D source. Concrete
|
||||
//! curve-on-surface dispatch is preserved; [`CurveClassification::intersect_edges`]
|
||||
//! inserts through a read-only adapter (its mutable edits defer) and
|
||||
//! [`CurveClassification::intersect_edges_mut`] through a mutable one. Ignored native evaluation
|
||||
//! [`CurveClassification::intersect_edges_mut`] through a mutable one. Each target
|
||||
//! edge's curve cache is built at that edge's live zone tolerance, as upstream's
|
||||
//! `SmTol::GetApproxTol3d(&m_crCurve)` reads it from the curve's owning edge; the
|
||||
//! classification curve's cache has no owner in reach through the caller's geometry and
|
||||
//! still builds on the system tolerance. Ignored native evaluation
|
||||
//! and drop statuses preserve initialized outputs; unported errors propagate. The
|
||||
//! native checked-drop guess aliases its output and is reset to zero before solving;
|
||||
//! the Rust call explicitly uses that zero guess. A 3D trim-boundary source returns
|
||||
@@ -371,7 +375,12 @@ impl<O: ClassObject> CurveClassification<O> {
|
||||
solutions.push(sol);
|
||||
}
|
||||
} else {
|
||||
let found = working.global_curve_intersect(
|
||||
// `GlobalCurveIntersect` caches the edge's own curve object, whose cache is
|
||||
// built at `SmTol::GetApproxTol3d(&m_crCurve)`: half the edge's tolerance
|
||||
// (`SmCurveCache.cpp:1496`, through the curve's owner). The caller's handle
|
||||
// geometry is not that owned object, so the live edge tolerance is supplied
|
||||
// to the lazy build; a cache is still built only where the solve needs it.
|
||||
let found = working.global_curve_intersect_cached_with_zone_tols(
|
||||
k,
|
||||
&self.interval(),
|
||||
target,
|
||||
@@ -384,6 +393,9 @@ impl<O: ClassObject> CurveClassification<O> {
|
||||
],
|
||||
..CurveIntersectOptions::default()
|
||||
},
|
||||
&mut None,
|
||||
&mut None,
|
||||
[None, Some(edge_zone)],
|
||||
)?;
|
||||
solutions.extend(
|
||||
found
|
||||
|
||||
@@ -321,6 +321,23 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "hermite_target_closed_disjoint",
|
||||
"upstream_ref": "source/SMLib/src/SmBrep.cpp:2264-2293: CreateWireEdgeFromCurve glues coincident endpoints; a nonconstant closed Hermite is a valid disjoint target, not an invalid line",
|
||||
"inputs": {
|
||||
"op": 0,
|
||||
"dim3": true,
|
||||
"zone": 1e-06,
|
||||
"edges": [
|
||||
{
|
||||
"a": [0.5, 2.0, 0.0],
|
||||
"b": [0.5, 2.0, 0.0],
|
||||
"zone": 1e-06,
|
||||
"hermite": [[0.5, 2.0, 0.0], [1.0, 0.0, 1.0], [0.5, 2.0, 0.0], [1.0, 0.0, -1.0]]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -144,8 +144,7 @@ SC_EXPORT int32_t sc_intersect_curve_class_p9_run(const double* in,int64_t n,dou
|
||||
for(int k=11;k<23;k++) if(!std::isfinite(p[k]*in[10])) return SC_ERR_ARG;
|
||||
// A Hermite edge carries its own curve, never the source curve.
|
||||
if(p[9]) { ++shared; if(p[0]!=0 || in[8]!=1) return SC_ERR_ARG; }
|
||||
else if(p[0]==1) { if(p[11]==p[17] && p[12]==p[18] && p[13]==p[19]) return SC_ERR_ARG; }
|
||||
else if(p[1]==p[4] && p[2]==p[5] && p[3]==p[6]) return SC_ERR_ARG;
|
||||
else if(p[0]==0 && p[1]==p[4] && p[2]==p[5] && p[3]==p[6]) return SC_ERR_ARG;
|
||||
}
|
||||
if(shared>1) return SC_ERR_ARG;
|
||||
SC_TRY
|
||||
|
||||
Reference in New Issue
Block a user