mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-08 13:01:52 +00:00
WIP: port offset executive part 1 and expose parity blockers
Preserve the safe Rust port, native adapter, fixtures, and differential runner. Native isolated validation: 523 pass, 2 order failures, 17 deferred, 41 rejected and 108 C++ timeouts out of 691 cases. This is not a green or partial correctness-gate claim. Co-Authored-By: GPT-6.1-Sol <noreply@openai.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,881 @@
|
||||
//! Native fixtures for `offset.offset_executive_p1` (PR #175).
|
||||
//!
|
||||
//! Upstream: `source/SMLib/inc/SmOffsetExecutive.h`,
|
||||
//! `source/SMLib/src/SmOffsetExecutive.cpp`.
|
||||
//!
|
||||
//! | Rust | Upstream |
|
||||
//! |------|----------|
|
||||
//! | [`config`] | constructor/destructor, `Get/SetInset`, `Get/SetExtendConvexEdges`, `SetMergeResults`, `SetCreateOffsetSolid`, `GetTangencyTolDegrees`, `GetOffsetBrep`, `Get/SetShellFaces`, `GetOffsetEdgeuse` |
|
||||
//! | [`synchronize`] | `SyncronizeOffsetEdgeuses`, indirectly `sm_RemoveSmallStuff`, `sm_InsertBreaks`, `sm_SortEdgeuses`, `sm_ExtractGaps` |
|
||||
//! | [`extend`] | `GetCreateExtendedSurface` |
|
||||
//! | [`probe`] | `ReplaceImplicitOffsets`, `RemoveExcessFaces`, `BuildSideWallOfEdgeuse`, `BuildOffsetOfEdgeuses`, `FillGapEdgeuses`, `BuildOffsetOfVertex`, `BuildOffsetOfFaceuse`, `PiecewiseMerge`, `MapEdgeuses`; `sm_NotifyEdgeAndVertices` indirectly via `MapEdgeuses` |
|
||||
//!
|
||||
//! Deviations: caller-owned flat snapshots instead of pointers/out-parameters;
|
||||
//! bounded fixture sizes, and validated NURBS storage before entering C++.
|
||||
//! A native preflight also rejects two-control-point extension isocurves whose
|
||||
//! tangent cannot be normalized: upstream's fallback would access a third point
|
||||
//! outside the polygon (`NL_CrvAdv.cpp`). This returns a shim argument error,
|
||||
//! never a fixture rejection or a fabricated operation status.
|
||||
//! No private static helper is copied. Split cases execute native topology splitting;
|
||||
//! the runner must defer them if its Rust kernel cannot yet split edges.
|
||||
//!
|
||||
//! # Geometry constructor views
|
||||
//!
|
||||
//! `config` accepts [`Config::tangency_tol_radians`] after `offset_distance` and
|
||||
//! `approx_tol`, before `operation` in its packed input. Pass `2.0 * PI / 180.0`
|
||||
//! for the default geometry-constructor view, or any finite radians value for a
|
||||
//! custom geometry-subclass view. A fixture-only subclass first runs the native
|
||||
//! default geometry constructor, then assigns its protected tangency scalar;
|
||||
//! `GetTangencyTolDegrees` remains the actual inherited native getter.
|
||||
//! Both `default_state` and `final_state` below observe that supplied geometry:
|
||||
//! "default" refers to executive flags before setters, not a reset of tangency.
|
||||
//! Other modes keep the unmodified native default geometry constructor.
|
||||
//!
|
||||
//! # Snapshot protocol (all entries are `f64`)
|
||||
//!
|
||||
//! Every snapshot starts with `0` (fixture built), or consists of `[-1, status]`
|
||||
//! **only** when a fixture constructor rejected it. Operation failures are native
|
||||
//! status values inside a built-fixture snapshot, never fixture rejections.
|
||||
//! All native statuses are **unmodified**: [`NATIVE_SUCCESS`] is 1000, not 0;
|
||||
//! null-pointer errors are [`NATIVE_NULL_POINTER`] (1006). The leading 0 is an
|
||||
//! adapter fixture tag, not a native operation status.
|
||||
//! IDs are zero-based indices in native `GetEdgeuses` / `GetEdges` / `GetFaces`
|
||||
//! traversal, not addresses. `-1` is null, `-2` is a non-null pointer absent from
|
||||
//! that traversal. A counted list is `length, entries...`.
|
||||
//!
|
||||
//! * `config`: `0, default_state[10], final_state[10], number_shell_sets`, then
|
||||
//! one counted face-ID list per shell setter, then `number_original_edgeuses`;
|
||||
//! for each original EU: `GetOffsetEdgeuse_id, counted_related_EU_IDs`;
|
||||
//! finally a counted list of `RelatePair` statuses (duplicates are passed through).
|
||||
//! State order: `offset_distance, approx_tol, operation, inset, extend_convex,
|
||||
//! merge_results, create_solid, tangency_degrees, has_offset_brep, shell_count`.
|
||||
//! * `synchronize`: `0, operation_status, lookup_first, lookup_second`, counted
|
||||
//! fixture-primary-EU IDs (first fixtures then second), counted pre-operation
|
||||
//! relation IDs for first and second, `number_final_edgeuses`, one record per
|
||||
//! final EU, then four counted EU-ID lists: matched first, matched second,
|
||||
//! first gaps, second gaps. Lookups/relation IDs use the **pre-operation**
|
||||
//! traversal; fixture IDs and final lists use the **post-operation** traversal.
|
||||
//! Synchronization fixtures have real two-edge loops so native splitting has
|
||||
//! valid CW/CCW neighbors. Auxiliary closing edges are excluded from measured
|
||||
//! edge/EU traversals; all measured split descendants remain in the snapshot.
|
||||
//! No UV trims are requested, so arbitrary 3D B-splines remain admissible.
|
||||
//! Each final EU record is `eu_id, edge_id, min, max, ApproximateLength(10),
|
||||
//! start_status, start_xyz[3], end_status, end_xyz[3]` (13 entries).
|
||||
//! * `extend`: `0, action_count`; Get records are `0, status, was_cached,
|
||||
//! result_id, has_result`. On success append `surface_kind, domain[4],
|
||||
//! sample_count` and samples `point_status, xyz[3], normal_status, normal[3]`.
|
||||
//! Domain order is `umin, vmin, umax, vmax`; kind is 1 Plane, 2 B-spline,
|
||||
//! 3 other. Result IDs are assigned on first successful encounter, never reused
|
||||
//! after eviction. Forget records: `1, was_cached, remaining_cache_count`;
|
||||
//! Clear records: `2, previous_cache_count, 0`.
|
||||
//! * `probe`: `0, status, curve_count, orientation_count, has_surface,
|
||||
//! has_result_brep, has_offset_brep, counted_original_to_offset_EU_lookups`.
|
||||
//!
|
||||
//! **Relation ordering:** this checkout uses pointer-hashed `unordered_set`
|
||||
//! values in `SmRelation`. We preserve actual `GetSeconds` order; equal-minimum
|
||||
//! sort ties and summation order can therefore be upstream-nondeterministic.
|
||||
//! Do not mistake this for insertion order or canonicalize it in the runner.
|
||||
|
||||
use crate::{call_with_buffer, FfiError};
|
||||
|
||||
/// Fixture resource bound on edges, faces, surfaces, actions and relation pairs.
|
||||
/// These are adapter budgets, not limits asserted about the kernel API.
|
||||
pub const MAX_ITEMS: usize = 4096;
|
||||
/// Maximum number of homogeneous control points in one fixture.
|
||||
pub const MAX_CONTROL_POINTS: usize = 65_536;
|
||||
/// Native `NL_MAXDEG` (`NL_Nurbsdef.h`); larger degrees overrun basis work arrays.
|
||||
pub const MAX_DEGREE: usize = 17;
|
||||
/// Maximum packed input length, bounding aggregate native fixture allocations.
|
||||
pub const MAX_PACKED_VALUES: usize = 1_048_576;
|
||||
/// Aggregate synchronization-edge budget, bounding the quadratic split expansion.
|
||||
pub const MAX_SYNC_EDGES: usize = 128;
|
||||
/// Unmodified upstream `SM_SUCCESS` (distinct from the shim ABI's `SC_OK = 0`).
|
||||
pub const NATIVE_SUCCESS: i32 = 1000;
|
||||
/// Unmodified upstream `SM_ERR_NULL_POINTER` returned by native NER checks.
|
||||
pub const NATIVE_NULL_POINTER: i32 = 1006;
|
||||
|
||||
/// Constructor settings and protected state exercised by [`config`].
|
||||
// Independent native setters are boolean toggles, not states of one machine.
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Config {
|
||||
/// Signed offset distance (stored by the geometry creator).
|
||||
pub offset_distance: f64,
|
||||
/// Finite approximation tolerance, including the native default sentinel.
|
||||
pub approx_tol: f64,
|
||||
/// Geometry-subclass tangency scalar in radians; any finite value is allowed,
|
||||
/// including signed zero, negative, subnormal and large values. Use
|
||||
/// `2.0 * PI / 180.0` for the native default constructor's value.
|
||||
pub tangency_tol_radians: f64,
|
||||
/// Native operation enum, 0 solid, 1 NMT, 2 shell, 3 local, 4 machine.
|
||||
pub operation: u32,
|
||||
/// Value passed to `SetInset`.
|
||||
pub inset: bool,
|
||||
/// Value passed to `SetExtendConvexEdges`.
|
||||
pub extend_convex: bool,
|
||||
/// Value passed to `SetMergeResults`.
|
||||
pub merge_results: bool,
|
||||
/// Value passed to `SetCreateOffsetSolid`.
|
||||
pub create_solid: bool,
|
||||
/// Install the fixture offset B-rep into the executive, transferring ownership.
|
||||
pub install_offset_brep: bool,
|
||||
/// Number of independent original wire edges (two edgeuses each).
|
||||
pub original_edges: usize,
|
||||
/// Number of independent offset wire edges (two edgeuses each).
|
||||
pub offset_edges: usize,
|
||||
/// Number of independent planar selector faces in a separate fixture B-rep.
|
||||
pub face_count: usize,
|
||||
/// Successive calls to `SetShellFaces`, using native face traversal IDs.
|
||||
/// Empty outer list leaves the constructor's null shell list untouched.
|
||||
pub shell_face_sets: Vec<Vec<usize>>,
|
||||
/// Original-EU/offset-EU traversal IDs; duplicates reach real `RelatePair`.
|
||||
pub relations: Vec<[usize; 2]>,
|
||||
}
|
||||
|
||||
/// Geometry owned by an independent wire-edge fixture.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum CurveFixture {
|
||||
/// A real analytic `SmLine`, constructed with `SmLine::CreateLineSegment`.
|
||||
Line {
|
||||
/// Point at the lower domain bound.
|
||||
start: [f64; 3],
|
||||
/// Point at the upper domain bound; coincident endpoints reach native validation.
|
||||
end: [f64; 3],
|
||||
/// Strictly increasing natural parameter interval.
|
||||
domain: [f64; 2],
|
||||
},
|
||||
/// A real `SmBSplineCurve`, not a line stand-in.
|
||||
BSpline {
|
||||
/// Polynomial degree, 1 through [`MAX_DEGREE`].
|
||||
degree: usize,
|
||||
/// Homogeneous `(x*w, y*w, z*w, w)` control points, positive weights.
|
||||
control_points: Vec<[f64; 4]>,
|
||||
/// Nondecreasing knots, length `control_points.len() + degree + 1`.
|
||||
knots: Vec<f64>,
|
||||
},
|
||||
}
|
||||
|
||||
/// One independent offset edge and its actual native trimming interval.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct EdgeFixture {
|
||||
/// Native curve to construct.
|
||||
pub curve: CurveFixture,
|
||||
/// Ordered subinterval of the curve's natural interval; zero width is allowed.
|
||||
pub interval: [f64; 2],
|
||||
}
|
||||
|
||||
/// Arguments for [`synchronize`].
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Synchronize {
|
||||
/// Mapped offsets of the first original EU, in fixture construction order.
|
||||
pub first: Vec<EdgeFixture>,
|
||||
/// Mapped offsets of the other original EU.
|
||||
pub second: Vec<EdgeFixture>,
|
||||
/// Pass null as the first original EU: native NER returns its real error code.
|
||||
pub null_original: bool,
|
||||
/// Pass null as the other original EU; its otherwise supplied fixtures remain
|
||||
/// unmapped (the first list can still be nonempty).
|
||||
pub null_other: bool,
|
||||
}
|
||||
|
||||
/// Surface geometry constructed independently of the Rust kernel's surface types.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum SurfaceFixture {
|
||||
/// Finite analytic plane with a real B-spline backing representation.
|
||||
Plane {
|
||||
/// Parametric origin.
|
||||
origin: [f64; 3],
|
||||
/// Native plane X axis (unitized by the constructor).
|
||||
x_axis: [f64; 3],
|
||||
/// Native plane Y axis (unitized by the constructor).
|
||||
y_axis: [f64; 3],
|
||||
/// Parameter scales; finite, zero and negative scales reach native code.
|
||||
scale: [f64; 2],
|
||||
/// `umin, vmin, umax, vmax` (positive extent in both directions).
|
||||
domain: [f64; 4],
|
||||
},
|
||||
/// Tensor-product B-spline surface in U-major control-net order.
|
||||
BSpline {
|
||||
/// U degree.
|
||||
u_degree: usize,
|
||||
/// V degree.
|
||||
v_degree: usize,
|
||||
/// Number of U control points, not the highest index.
|
||||
u_count: usize,
|
||||
/// Number of V control points, not the highest index.
|
||||
v_count: usize,
|
||||
/// Homogeneous points at `(u, v)`, indexed `u * v_count + v`.
|
||||
control_points: Vec<[f64; 4]>,
|
||||
/// U knots, length `u_count + u_degree + 1`.
|
||||
u_knots: Vec<f64>,
|
||||
/// V knots, length `v_count + v_degree + 1`.
|
||||
v_knots: Vec<f64>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Actions on the executive's actual extended-surface map.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum CacheAction {
|
||||
/// Call `GetCreateExtendedSurface`; `None` exercises the valid native NER error.
|
||||
Get(Option<usize>),
|
||||
/// Remove one map key without deleting any returned surface.
|
||||
Forget(usize),
|
||||
/// Clear map entries without deleting returned surfaces.
|
||||
Clear,
|
||||
}
|
||||
|
||||
/// Arguments for [`extend`].
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Extension {
|
||||
/// Base surfaces, indexed by [`CacheAction`] keys.
|
||||
pub surfaces: Vec<SurfaceFixture>,
|
||||
/// Signed distance; the native method extends by five times this value.
|
||||
pub offset_distance: f64,
|
||||
/// Stored approximation tolerance (not used by this extension operation).
|
||||
pub approx_tol: f64,
|
||||
/// Repeated gets, eviction and clear operations, executed in order.
|
||||
pub actions: Vec<CacheAction>,
|
||||
/// Sample fractions in `[0, 1]^2` of each returned extended natural domain.
|
||||
pub samples: Vec<[f64; 2]>,
|
||||
}
|
||||
|
||||
/// Independent native topology paths, never implicitly mixed into arithmetic cases.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(i32)]
|
||||
pub enum Probe {
|
||||
/// `ReplaceImplicitOffsets` on an empty B-rep.
|
||||
ReplaceEmpty = 0,
|
||||
/// `RemoveExcessFaces` on an empty B-rep, merging disabled.
|
||||
RemoveNoMerge = 1,
|
||||
/// `RemoveExcessFaces` on an empty B-rep, merging enabled.
|
||||
RemoveEmpty = 2,
|
||||
/// `BuildSideWallOfEdgeuse(NULL)`.
|
||||
SideWallNull = 3,
|
||||
/// `BuildSideWallOfEdgeuse` with a real, unmapped wire EU.
|
||||
SideWallUnmapped = 4,
|
||||
/// `FillGapEdgeuses(NULL, empty, empty)`.
|
||||
FillEmpty = 5,
|
||||
/// `BuildOffsetOfVertex(NULL, ...)` (native NER).
|
||||
VertexNull = 6,
|
||||
/// `BuildOffsetOfVertex` with a real isolated shell vertex.
|
||||
VertexIsolated = 7,
|
||||
/// `BuildOffsetOfFaceuse(NULL, ...)` (native NER).
|
||||
FaceuseNull = 8,
|
||||
/// Real planar faceuse, null offset surface (native early success).
|
||||
FaceuseNoSurface = 9,
|
||||
/// `PiecewiseMerge` on an empty offset B-rep, merge disabled.
|
||||
PiecewiseEmpty = 10,
|
||||
/// `MapEdgeuses` from a real planar face's upward use to the same face.
|
||||
MapPlane = 11,
|
||||
/// `BuildOffsetOfEdgeuses` on a real planar face edge and its radial mate,
|
||||
/// with no mapped offsets (no geometry-creator branch is entered).
|
||||
EdgeusesUnmapped = 12,
|
||||
}
|
||||
|
||||
mod sys {
|
||||
extern "C" {
|
||||
pub fn sc_offset_offset_executive_p1_eval(
|
||||
mode: i32,
|
||||
input: *const f64,
|
||||
input_len: i64,
|
||||
out: *mut f64,
|
||||
cap: i64,
|
||||
out_len: *mut i64,
|
||||
) -> i32;
|
||||
}
|
||||
}
|
||||
|
||||
fn bad(message: &str) -> FfiError {
|
||||
FfiError::InvalidArgument(format!("offset.offset_executive_p1: {message}"))
|
||||
}
|
||||
|
||||
fn finite(values: &[f64]) -> Result<(), FfiError> {
|
||||
if values.iter().all(|v| v.is_finite()) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(bad("non-finite fixture value"))
|
||||
}
|
||||
}
|
||||
|
||||
fn count(n: usize) -> Result<(), FfiError> {
|
||||
if n <= MAX_ITEMS {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(bad("fixture count exceeds MAX_ITEMS"))
|
||||
}
|
||||
}
|
||||
|
||||
fn interval([a, b]: [f64; 2], positive: bool) -> Result<(), FfiError> {
|
||||
finite(&[a, b, b - a])?;
|
||||
if a > b || (positive && a == b) {
|
||||
return Err(bad("invalid parameter interval"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn knots(degree: usize, n: usize, values: &[f64]) -> Result<[f64; 2], FfiError> {
|
||||
if !(1..=MAX_DEGREE).contains(°ree)
|
||||
|| n <= degree
|
||||
|| n > MAX_CONTROL_POINTS
|
||||
|| n.checked_add(degree).and_then(|v| v.checked_add(1)) != Some(values.len())
|
||||
{
|
||||
return Err(bad("NURBS degree/count/knot length mismatch"));
|
||||
}
|
||||
finite(values)?;
|
||||
if values.windows(2).any(|v| matches!(v, [a, b] if a > b)) {
|
||||
return Err(bad("decreasing knots"));
|
||||
}
|
||||
// Also prevents a zero-width support interval / excessive knot multiplicity.
|
||||
for (a, b) in values.iter().zip(values.iter().skip(degree + 1)) {
|
||||
if a >= b || !(b - a).is_finite() {
|
||||
return Err(bad("zero-width NURBS basis support"));
|
||||
}
|
||||
}
|
||||
let domain = [
|
||||
*values
|
||||
.get(degree)
|
||||
.ok_or_else(|| bad("missing start knot"))?,
|
||||
*values.get(n).ok_or_else(|| bad("missing end knot"))?,
|
||||
];
|
||||
interval(domain, true)?;
|
||||
Ok(domain)
|
||||
}
|
||||
|
||||
fn points(values: &[[f64; 4]]) -> Result<(), FfiError> {
|
||||
if values.len() > MAX_CONTROL_POINTS {
|
||||
return Err(bad("control net exceeds MAX_CONTROL_POINTS"));
|
||||
}
|
||||
for &[x, y, z, w] in values {
|
||||
finite(&[x, y, z, w, x / w, y / w, z / w])?;
|
||||
if w <= 0.0 {
|
||||
return Err(bad("fixture homogeneous weights must be positive"));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Every append checks the aggregate budget before allocating. Counts are derived
|
||||
// from validated slice lengths, never from a caller-supplied packed header.
|
||||
#[derive(Default)]
|
||||
struct Pack(Vec<f64>);
|
||||
|
||||
impl Pack {
|
||||
fn add(&mut self, values: &[f64]) -> Result<(), FfiError> {
|
||||
if self
|
||||
.0
|
||||
.len()
|
||||
.checked_add(values.len())
|
||||
.is_none_or(|n| n > MAX_PACKED_VALUES)
|
||||
{
|
||||
return Err(bad("aggregate fixture exceeds MAX_PACKED_VALUES"));
|
||||
}
|
||||
self.0.extend_from_slice(values);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn size(&mut self, n: usize) -> Result<(), FfiError> {
|
||||
self.add(&[n as f64])
|
||||
}
|
||||
|
||||
fn control_points(&mut self, values: &[[f64; 4]]) -> Result<(), FfiError> {
|
||||
points(values)?;
|
||||
for point in values {
|
||||
self.add(point)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn curve(&mut self, curve: &CurveFixture) -> Result<[f64; 2], FfiError> {
|
||||
match curve {
|
||||
CurveFixture::Line { start, end, domain } => {
|
||||
finite(start)?;
|
||||
finite(end)?;
|
||||
interval(*domain, true)?;
|
||||
for (a, b) in start.iter().zip(end) {
|
||||
finite(&[b - a])?;
|
||||
}
|
||||
self.add(&[0.0])?;
|
||||
self.add(start)?;
|
||||
self.add(end)?;
|
||||
self.add(domain)?;
|
||||
Ok(*domain)
|
||||
}
|
||||
CurveFixture::BSpline {
|
||||
degree,
|
||||
control_points,
|
||||
knots: k,
|
||||
} => {
|
||||
let domain = knots(*degree, control_points.len(), k)?;
|
||||
self.add(&[1.0, *degree as f64, control_points.len() as f64])?;
|
||||
self.control_points(control_points)?;
|
||||
self.add(k)?;
|
||||
Ok(domain)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn edge(&mut self, edge: &EdgeFixture) -> Result<(), FfiError> {
|
||||
let [a, b] = self.curve(&edge.curve)?;
|
||||
interval(edge.interval, false)?;
|
||||
let [min, max] = edge.interval;
|
||||
if min < a || max > b {
|
||||
return Err(bad("edge interval is outside the curve domain"));
|
||||
}
|
||||
self.add(&edge.interval)
|
||||
}
|
||||
|
||||
fn surface(&mut self, surface: &SurfaceFixture) -> Result<(), FfiError> {
|
||||
match surface {
|
||||
SurfaceFixture::Plane {
|
||||
origin,
|
||||
x_axis,
|
||||
y_axis,
|
||||
scale,
|
||||
domain,
|
||||
} => {
|
||||
finite(origin)?;
|
||||
finite(x_axis)?;
|
||||
finite(y_axis)?;
|
||||
finite(scale)?;
|
||||
let [a, b, c, d] = *domain;
|
||||
interval([a, c], true)?;
|
||||
interval([b, d], true)?;
|
||||
// Zero axes are not rejected: native Unitize has a defined fallback.
|
||||
self.add(&[0.0])?;
|
||||
self.add(origin)?;
|
||||
self.add(x_axis)?;
|
||||
self.add(y_axis)?;
|
||||
self.add(scale)?;
|
||||
self.add(domain)
|
||||
}
|
||||
SurfaceFixture::BSpline {
|
||||
u_degree,
|
||||
v_degree,
|
||||
u_count,
|
||||
v_count,
|
||||
control_points,
|
||||
u_knots,
|
||||
v_knots,
|
||||
} => {
|
||||
if u_count.checked_mul(*v_count) != Some(control_points.len()) {
|
||||
return Err(bad("surface control net length mismatch/overflow"));
|
||||
}
|
||||
knots(*u_degree, *u_count, u_knots)?;
|
||||
knots(*v_degree, *v_count, v_knots)?;
|
||||
self.add(&[
|
||||
1.0,
|
||||
*u_degree as f64,
|
||||
*v_degree as f64,
|
||||
*u_count as f64,
|
||||
*v_count as f64,
|
||||
])?;
|
||||
self.control_points(control_points)?;
|
||||
self.add(u_knots)?;
|
||||
self.add(v_knots)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn config_input(input: &Config) -> Result<Pack, FfiError> {
|
||||
finite(&[
|
||||
input.offset_distance,
|
||||
input.approx_tol,
|
||||
input.tangency_tol_radians,
|
||||
])?;
|
||||
if input.operation > 4 {
|
||||
return Err(bad("invalid offset operation"));
|
||||
}
|
||||
for n in [
|
||||
input.original_edges,
|
||||
input.offset_edges,
|
||||
input.face_count,
|
||||
input.shell_face_sets.len(),
|
||||
input.relations.len(),
|
||||
] {
|
||||
count(n)?;
|
||||
}
|
||||
let mut pack = Pack::default();
|
||||
pack.add(&[
|
||||
input.offset_distance,
|
||||
input.approx_tol,
|
||||
input.tangency_tol_radians,
|
||||
f64::from(input.operation),
|
||||
f64::from(input.inset),
|
||||
f64::from(input.extend_convex),
|
||||
f64::from(input.merge_results),
|
||||
f64::from(input.create_solid),
|
||||
f64::from(input.install_offset_brep),
|
||||
input.original_edges as f64,
|
||||
input.offset_edges as f64,
|
||||
input.face_count as f64,
|
||||
])?;
|
||||
pack.size(input.shell_face_sets.len())?;
|
||||
for selectors in &input.shell_face_sets {
|
||||
count(selectors.len())?;
|
||||
pack.size(selectors.len())?;
|
||||
for &id in selectors {
|
||||
if id >= input.face_count {
|
||||
return Err(bad("shell face selector out of range"));
|
||||
}
|
||||
pack.size(id)?;
|
||||
}
|
||||
}
|
||||
pack.size(input.relations.len())?;
|
||||
for &[a, b] in &input.relations {
|
||||
if a >= input.original_edges * 2 || b >= input.offset_edges * 2 {
|
||||
return Err(bad("relation EU selector out of range"));
|
||||
}
|
||||
pack.add(&[a as f64, b as f64])?;
|
||||
}
|
||||
Ok(pack)
|
||||
}
|
||||
|
||||
fn sync_input(input: &Synchronize) -> Result<Pack, FfiError> {
|
||||
count(input.first.len())?;
|
||||
count(input.second.len())?;
|
||||
if input
|
||||
.first
|
||||
.len()
|
||||
.checked_add(input.second.len())
|
||||
.is_none_or(|n| n > MAX_SYNC_EDGES)
|
||||
{
|
||||
return Err(bad("synchronization exceeds MAX_SYNC_EDGES"));
|
||||
}
|
||||
let mut pack = Pack::default();
|
||||
pack.add(&[f64::from(input.null_original), f64::from(input.null_other)])?;
|
||||
for edges in [&input.first, &input.second] {
|
||||
pack.size(edges.len())?;
|
||||
for edge in edges {
|
||||
pack.edge(edge)?;
|
||||
}
|
||||
}
|
||||
Ok(pack)
|
||||
}
|
||||
|
||||
fn extension_input(input: &Extension) -> Result<Pack, FfiError> {
|
||||
finite(&[
|
||||
input.offset_distance,
|
||||
input.offset_distance * 5.0,
|
||||
input.approx_tol,
|
||||
])?;
|
||||
for n in [
|
||||
input.surfaces.len(),
|
||||
input.actions.len(),
|
||||
input.samples.len(),
|
||||
] {
|
||||
count(n)?;
|
||||
}
|
||||
let gets = input
|
||||
.actions
|
||||
.iter()
|
||||
.filter(|a| matches!(a, CacheAction::Get(_)))
|
||||
.count();
|
||||
let output_bound = input
|
||||
.samples
|
||||
.len()
|
||||
.checked_mul(8)
|
||||
.and_then(|n| n.checked_add(11))
|
||||
.and_then(|n| n.checked_mul(gets))
|
||||
.and_then(|n| n.checked_add(input.actions.len() * 3 + 2));
|
||||
if output_bound.is_none_or(|n| n > MAX_PACKED_VALUES) {
|
||||
return Err(bad("extension snapshot exceeds MAX_PACKED_VALUES"));
|
||||
}
|
||||
let mut pack = Pack::default();
|
||||
pack.add(&[input.offset_distance, input.approx_tol])?;
|
||||
pack.size(input.surfaces.len())?;
|
||||
for surface in &input.surfaces {
|
||||
pack.surface(surface)?;
|
||||
}
|
||||
pack.size(input.samples.len())?;
|
||||
for sample in &input.samples {
|
||||
finite(sample)?;
|
||||
if sample.iter().any(|v| !(0.0..=1.0).contains(v)) {
|
||||
return Err(bad("surface sample fractions must be in [0, 1]"));
|
||||
}
|
||||
pack.add(sample)?;
|
||||
}
|
||||
pack.size(input.actions.len())?;
|
||||
for action in &input.actions {
|
||||
match *action {
|
||||
CacheAction::Get(key) => {
|
||||
if let Some(key) = key {
|
||||
if key >= input.surfaces.len() {
|
||||
return Err(bad("surface key out of range"));
|
||||
}
|
||||
}
|
||||
pack.add(&[0.0, key.map_or(-1.0, |k| k as f64)])?;
|
||||
}
|
||||
CacheAction::Forget(key) => {
|
||||
if key >= input.surfaces.len() {
|
||||
return Err(bad("surface key out of range"));
|
||||
}
|
||||
pack.add(&[1.0, key as f64])?;
|
||||
}
|
||||
CacheAction::Clear => pack.add(&[2.0])?,
|
||||
}
|
||||
}
|
||||
Ok(pack)
|
||||
}
|
||||
|
||||
fn run(mode: i32, pack: &Pack) -> Result<Vec<f64>, FfiError> {
|
||||
call_with_buffer(1024, |out, len| {
|
||||
// SAFETY: only the typed, validated packers call this private function.
|
||||
// All counts, knot layouts, selectors and scalar finiteness were checked;
|
||||
// input/output slices and len remain live and C++ honors out's capacity.
|
||||
unsafe {
|
||||
sys::sc_offset_offset_executive_p1_eval(
|
||||
mode,
|
||||
pack.0.as_ptr(),
|
||||
pack.0.len() as i64,
|
||||
out.as_mut_ptr(),
|
||||
out.len() as i64,
|
||||
&raw mut *len,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Constructs real native topology and snapshots executive state and relationships.
|
||||
/// See the module-level protocol for the returned flat record.
|
||||
pub fn config(input: &Config) -> Result<Vec<f64>, FfiError> {
|
||||
run(0, &config_input(input)?)
|
||||
}
|
||||
|
||||
/// Synchronizes actual line/B-spline edgeuses through the native public method.
|
||||
/// Native split-path statuses/results are retained even when the Rust side defers.
|
||||
pub fn synchronize(input: &Synchronize) -> Result<Vec<f64>, FfiError> {
|
||||
run(1, &sync_input(input)?)
|
||||
}
|
||||
|
||||
/// Exercises real surface extension, cache hits/eviction and evaluated samples.
|
||||
/// Native operation/evaluation errors remain in the snapshot.
|
||||
pub fn extend(input: &Extension) -> Result<Vec<f64>, FfiError> {
|
||||
run(2, &extension_input(input)?)
|
||||
}
|
||||
|
||||
/// Runs one independent topology probe. The tolerance is forwarded unchanged;
|
||||
/// only non-finite values are rejected before calling native code.
|
||||
pub fn probe(operation: Probe, approx_tol: f64) -> Result<Vec<f64>, FfiError> {
|
||||
finite(&[approx_tol])?;
|
||||
run(3, &Pack(vec![f64::from(operation as i32), approx_tol]))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn line(interval: [f64; 2]) -> EdgeFixture {
|
||||
EdgeFixture {
|
||||
curve: CurveFixture::Line {
|
||||
start: [0.0, 0.0, 0.0],
|
||||
end: [1.0, 0.0, 0.0],
|
||||
domain: [0.0, 1.0],
|
||||
},
|
||||
interval,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_accepts_gaps_zero_width_and_nulls() {
|
||||
assert!(sync_input(&Synchronize {
|
||||
first: vec![line([0.8, 1.0]), line([0.0, 0.0]), line([0.0, 0.2])],
|
||||
second: vec![],
|
||||
null_original: true,
|
||||
null_other: true,
|
||||
})
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_rejects_bad_ranges_and_nonfinite() {
|
||||
for range in [[0.5, 0.4], [-0.1, 0.5], [0.0, 1.1], [0.0, f64::NAN]] {
|
||||
assert!(sync_input(&Synchronize {
|
||||
first: vec![line(range)],
|
||||
second: vec![],
|
||||
null_original: false,
|
||||
null_other: false,
|
||||
})
|
||||
.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validates_native_nurbs_storage() {
|
||||
assert!(knots(1, 2, &[0.0, 0.0, 1.0, 1.0]).is_ok());
|
||||
assert!(knots(MAX_DEGREE + 1, 32, &[]).is_err());
|
||||
assert!(knots(1, 2, &[0.0, 0.0, 0.0, 1.0]).is_err());
|
||||
assert!(knots(2, 3, &[0.0, 0.0, 0.0, 1.0, 0.5, 1.0]).is_err());
|
||||
assert!(points(&[[0.0, 0.0, 0.0, 0.0]]).is_err());
|
||||
assert!(points(&[[f64::INFINITY, 0.0, 0.0, 1.0]]).is_err());
|
||||
assert!(points(&[[2.0, 0.0, 0.0, 2.0]]).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_checks_net_overflow_keys_and_samples() {
|
||||
let surface = SurfaceFixture::BSpline {
|
||||
u_degree: 1,
|
||||
v_degree: 1,
|
||||
u_count: usize::MAX,
|
||||
v_count: 2,
|
||||
control_points: vec![],
|
||||
u_knots: vec![],
|
||||
v_knots: vec![],
|
||||
};
|
||||
assert!(Pack::default().surface(&surface).is_err());
|
||||
let mut ext = Extension {
|
||||
surfaces: vec![],
|
||||
offset_distance: 0.5,
|
||||
approx_tol: 1.0e-6,
|
||||
actions: vec![CacheAction::Get(None), CacheAction::Clear],
|
||||
samples: vec![],
|
||||
};
|
||||
assert!(extension_input(&ext).is_ok());
|
||||
ext.actions.push(CacheAction::Get(Some(0)));
|
||||
assert!(extension_input(&ext).is_err());
|
||||
ext.actions.clear();
|
||||
ext.samples.push([1.01, 0.0]);
|
||||
assert!(extension_input(&ext).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_checks_selectors_not_duplicate_relations() {
|
||||
let mut cfg = Config {
|
||||
offset_distance: -0.2,
|
||||
approx_tol: 0.0,
|
||||
tangency_tol_radians: 2.0 * std::f64::consts::PI / 180.0,
|
||||
operation: 4,
|
||||
inset: false,
|
||||
extend_convex: false,
|
||||
merge_results: false,
|
||||
create_solid: false,
|
||||
install_offset_brep: true,
|
||||
original_edges: 1,
|
||||
offset_edges: 1,
|
||||
face_count: 1,
|
||||
shell_face_sets: vec![vec![0, 0], vec![]],
|
||||
relations: vec![[0, 1], [0, 1]],
|
||||
};
|
||||
assert!(config_input(&cfg).is_ok());
|
||||
cfg.relations.push([2, 0]);
|
||||
assert!(config_input(&cfg).is_err());
|
||||
cfg.relations.clear();
|
||||
cfg.shell_face_sets.push(vec![1]);
|
||||
assert!(config_input(&cfg).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_validates_tangency_finiteness_only_and_packs_before_operation() {
|
||||
let mut cfg = Config {
|
||||
offset_distance: 0.25,
|
||||
approx_tol: 1.0e-6,
|
||||
tangency_tol_radians: 2.0 * std::f64::consts::PI / 180.0,
|
||||
operation: 4,
|
||||
inset: false,
|
||||
extend_convex: false,
|
||||
merge_results: false,
|
||||
create_solid: false,
|
||||
install_offset_brep: false,
|
||||
original_edges: 0,
|
||||
offset_edges: 0,
|
||||
face_count: 0,
|
||||
shell_face_sets: vec![],
|
||||
relations: vec![],
|
||||
};
|
||||
for tangency in [
|
||||
cfg.tangency_tol_radians,
|
||||
0.0,
|
||||
-0.0,
|
||||
-0.25,
|
||||
f64::from_bits(1),
|
||||
f64::MIN_POSITIVE,
|
||||
f64::MIN,
|
||||
f64::MAX,
|
||||
] {
|
||||
cfg.tangency_tol_radians = tangency;
|
||||
let packed = config_input(&cfg).unwrap();
|
||||
assert_eq!(packed.0.len(), 14);
|
||||
assert_eq!(packed.0.first(), Some(&cfg.offset_distance));
|
||||
assert_eq!(packed.0.get(1), Some(&cfg.approx_tol));
|
||||
assert_eq!(
|
||||
packed.0.get(2).map(|v| v.to_bits()),
|
||||
Some(tangency.to_bits())
|
||||
);
|
||||
assert_eq!(packed.0.get(3), Some(&f64::from(cfg.operation)));
|
||||
}
|
||||
for tangency in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
|
||||
cfg.tangency_tol_radians = tangency;
|
||||
assert!(matches!(
|
||||
config_input(&cfg),
|
||||
Err(FfiError::InvalidArgument(_))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn synchronization_and_snapshot_growth_are_bounded() {
|
||||
assert!(sync_input(&Synchronize {
|
||||
first: vec![line([0.0, 1.0]); MAX_SYNC_EDGES + 1],
|
||||
second: vec![],
|
||||
null_original: false,
|
||||
null_other: false,
|
||||
})
|
||||
.is_err());
|
||||
assert!(extension_input(&Extension {
|
||||
surfaces: vec![],
|
||||
offset_distance: 1.0,
|
||||
approx_tol: 0.0,
|
||||
actions: vec![CacheAction::Get(None); MAX_ITEMS],
|
||||
samples: vec![[0.5, 0.5]; MAX_ITEMS],
|
||||
})
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepts_real_bspline_and_plane_layouts() {
|
||||
let curve = CurveFixture::BSpline {
|
||||
degree: 2,
|
||||
control_points: vec![
|
||||
[0.0, 0.0, 0.0, 1.0],
|
||||
[0.5, 1.0, 0.0, 1.0],
|
||||
[1.0, 0.0, 0.0, 1.0],
|
||||
],
|
||||
knots: vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0],
|
||||
};
|
||||
assert!(sync_input(&Synchronize {
|
||||
first: vec![EdgeFixture {
|
||||
curve,
|
||||
interval: [0.2, 0.8]
|
||||
}],
|
||||
second: vec![],
|
||||
null_original: false,
|
||||
null_other: false,
|
||||
})
|
||||
.is_ok());
|
||||
assert!(extension_input(&Extension {
|
||||
surfaces: vec![SurfaceFixture::Plane {
|
||||
origin: [0.0, 0.0, 0.0],
|
||||
x_axis: [1.0, 0.0, 0.0],
|
||||
y_axis: [0.0, 1.0, 0.0],
|
||||
scale: [2.0, -3.0],
|
||||
domain: [0.0, 0.0, 1.0, 1.0],
|
||||
}],
|
||||
offset_distance: 0.25,
|
||||
approx_tol: 1.0e-6,
|
||||
actions: vec![
|
||||
CacheAction::Get(Some(0)),
|
||||
CacheAction::Forget(0),
|
||||
CacheAction::Clear
|
||||
],
|
||||
samples: vec![[0.0, 0.0], [0.5, 0.5], [1.0, 1.0]],
|
||||
})
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aggregate_budget_is_checked_before_append() {
|
||||
let mut pack = Pack(vec![0.0; MAX_PACKED_VALUES]);
|
||||
assert!(pack.add(&[1.0]).is_err());
|
||||
assert_eq!(pack.0.len(), MAX_PACKED_VALUES);
|
||||
}
|
||||
}
|
||||
@@ -3,3 +3,5 @@
|
||||
//! Upstream: `SmOffsetExecutive`, `SmOffsetGeometryCreation`, `SmOffsetSurface`,
|
||||
//! `SmOffsetCurve` in `source/SMLib`, and the offset/shell entry points of
|
||||
//! `SM_API/SmApiBrep`.
|
||||
|
||||
pub mod offset_executive;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
//! Topological offset executive, split by callgraph ownership.
|
||||
//!
|
||||
//! Upstream: `source/SMLib/inc/SmOffsetExecutive.h`,
|
||||
//! `source/SMLib/src/SmOffsetExecutive.cpp`.
|
||||
//!
|
||||
//! | Rust | Upstream |
|
||||
//! |------|----------|
|
||||
//! | [`p1::OffsetExecutive`] | `SmOffsetExecutive` |
|
||||
//!
|
||||
//! Deviations: typed arena ids replace pointers; each part documents its dependency boundaries.
|
||||
|
||||
pub mod p1;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,332 @@
|
||||
{
|
||||
"cases": [
|
||||
{
|
||||
"name": "operation_0",
|
||||
"inputs": {
|
||||
"op": "config",
|
||||
"offset_distance": 1.0,
|
||||
"approx_tol": 1e-06,
|
||||
"operation": 0,
|
||||
"inset": false,
|
||||
"extend_convex": false,
|
||||
"merge_results": false,
|
||||
"create_solid": false,
|
||||
"install_offset_brep": false,
|
||||
"original_edges": 2,
|
||||
"offset_edges": 3,
|
||||
"face_count": 3,
|
||||
"shell_face_sets": [],
|
||||
"relations": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "operation_1",
|
||||
"inputs": {
|
||||
"op": "config",
|
||||
"offset_distance": 1.0,
|
||||
"approx_tol": 1e-06,
|
||||
"operation": 1,
|
||||
"inset": true,
|
||||
"extend_convex": false,
|
||||
"merge_results": true,
|
||||
"create_solid": false,
|
||||
"install_offset_brep": true,
|
||||
"original_edges": 2,
|
||||
"offset_edges": 3,
|
||||
"face_count": 3,
|
||||
"shell_face_sets": [],
|
||||
"relations": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "operation_2",
|
||||
"inputs": {
|
||||
"op": "config",
|
||||
"offset_distance": 1.0,
|
||||
"approx_tol": 1e-06,
|
||||
"operation": 2,
|
||||
"inset": false,
|
||||
"extend_convex": true,
|
||||
"merge_results": false,
|
||||
"create_solid": true,
|
||||
"install_offset_brep": false,
|
||||
"original_edges": 2,
|
||||
"offset_edges": 3,
|
||||
"face_count": 3,
|
||||
"shell_face_sets": [],
|
||||
"relations": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "operation_3",
|
||||
"inputs": {
|
||||
"op": "config",
|
||||
"offset_distance": 1.0,
|
||||
"approx_tol": 1e-06,
|
||||
"operation": 3,
|
||||
"inset": true,
|
||||
"extend_convex": true,
|
||||
"merge_results": true,
|
||||
"create_solid": true,
|
||||
"install_offset_brep": true,
|
||||
"original_edges": 2,
|
||||
"offset_edges": 3,
|
||||
"face_count": 3,
|
||||
"shell_face_sets": [],
|
||||
"relations": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "operation_4",
|
||||
"inputs": {
|
||||
"op": "config",
|
||||
"offset_distance": 1.0,
|
||||
"approx_tol": 1e-06,
|
||||
"operation": 4,
|
||||
"inset": false,
|
||||
"extend_convex": false,
|
||||
"merge_results": false,
|
||||
"create_solid": false,
|
||||
"install_offset_brep": false,
|
||||
"original_edges": 2,
|
||||
"offset_edges": 3,
|
||||
"face_count": 3,
|
||||
"shell_face_sets": [],
|
||||
"relations": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "upstream_prog_offset_setters",
|
||||
"inputs": {
|
||||
"op": "config",
|
||||
"offset_distance": 0.125,
|
||||
"approx_tol": 1e-06,
|
||||
"operation": 0,
|
||||
"inset": true,
|
||||
"extend_convex": false,
|
||||
"merge_results": true,
|
||||
"create_solid": false,
|
||||
"install_offset_brep": true,
|
||||
"original_edges": 2,
|
||||
"offset_edges": 3,
|
||||
"face_count": 3,
|
||||
"shell_face_sets": [
|
||||
[
|
||||
0
|
||||
]
|
||||
],
|
||||
"relations": []
|
||||
},
|
||||
"upstream_ref": "tests/prog_test/src/smOffsetTest.cpp:58-71",
|
||||
"note": "Re-expresses the executive settings prefix, not DoSolidOffset or its file input."
|
||||
},
|
||||
{
|
||||
"name": "upstream_shell_duplicates_and_replacement",
|
||||
"inputs": {
|
||||
"op": "config",
|
||||
"offset_distance": 1.0,
|
||||
"approx_tol": 1e-06,
|
||||
"operation": 0,
|
||||
"inset": false,
|
||||
"extend_convex": true,
|
||||
"merge_results": true,
|
||||
"create_solid": false,
|
||||
"install_offset_brep": false,
|
||||
"original_edges": 2,
|
||||
"offset_edges": 3,
|
||||
"face_count": 3,
|
||||
"shell_face_sets": [
|
||||
[
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
2,
|
||||
1,
|
||||
2,
|
||||
0
|
||||
],
|
||||
[],
|
||||
[
|
||||
1,
|
||||
1
|
||||
]
|
||||
],
|
||||
"relations": []
|
||||
},
|
||||
"upstream_ref": "tests/SM_API_test/src/TestSmBrep.cpp:2619-2638",
|
||||
"note": "Duplicate shell selectors from TestSmShellBrepFull; extends coverage to repeated SetShellFaces replacement."
|
||||
},
|
||||
{
|
||||
"name": "upstream_offset_settings_prefix",
|
||||
"inputs": {
|
||||
"op": "config",
|
||||
"offset_distance": 1.0,
|
||||
"approx_tol": 1e-06,
|
||||
"operation": 0,
|
||||
"inset": false,
|
||||
"extend_convex": true,
|
||||
"merge_results": true,
|
||||
"create_solid": true,
|
||||
"install_offset_brep": true,
|
||||
"original_edges": 2,
|
||||
"offset_edges": 3,
|
||||
"face_count": 3,
|
||||
"shell_face_sets": [],
|
||||
"relations": []
|
||||
},
|
||||
"upstream_ref": "tests/SM_API_test/src/TestSmBrep.cpp:2745-2755",
|
||||
"note": "Settings prefix inspired by the box offset entry point; this is not a complete box offset fixture."
|
||||
},
|
||||
{
|
||||
"name": "duplicate_pairs_exact_one_and_many",
|
||||
"inputs": {
|
||||
"op": "config",
|
||||
"offset_distance": 1.0,
|
||||
"approx_tol": 1e-06,
|
||||
"operation": 0,
|
||||
"inset": false,
|
||||
"extend_convex": false,
|
||||
"merge_results": false,
|
||||
"create_solid": false,
|
||||
"install_offset_brep": false,
|
||||
"original_edges": 2,
|
||||
"offset_edges": 3,
|
||||
"face_count": 3,
|
||||
"shell_face_sets": [],
|
||||
"relations": [
|
||||
[
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
1,
|
||||
1
|
||||
],
|
||||
[
|
||||
1,
|
||||
1
|
||||
],
|
||||
[
|
||||
2,
|
||||
1
|
||||
],
|
||||
[
|
||||
2,
|
||||
1
|
||||
],
|
||||
[
|
||||
3,
|
||||
5
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "empty_arenas_and_empty_shell_replacement",
|
||||
"inputs": {
|
||||
"op": "config",
|
||||
"offset_distance": 1.0,
|
||||
"approx_tol": 1e-06,
|
||||
"operation": 0,
|
||||
"inset": false,
|
||||
"extend_convex": false,
|
||||
"merge_results": false,
|
||||
"create_solid": false,
|
||||
"install_offset_brep": false,
|
||||
"original_edges": 0,
|
||||
"offset_edges": 0,
|
||||
"face_count": 0,
|
||||
"shell_face_sets": [
|
||||
[],
|
||||
[]
|
||||
],
|
||||
"relations": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "config_positive_zero",
|
||||
"inputs": {
|
||||
"op": "config",
|
||||
"offset_distance": 0.0,
|
||||
"approx_tol": 1e-06,
|
||||
"operation": 0,
|
||||
"inset": false,
|
||||
"extend_convex": false,
|
||||
"merge_results": false,
|
||||
"create_solid": false,
|
||||
"install_offset_brep": false,
|
||||
"original_edges": 2,
|
||||
"offset_edges": 3,
|
||||
"face_count": 3,
|
||||
"shell_face_sets": [],
|
||||
"relations": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "config_negative_zero",
|
||||
"inputs": {
|
||||
"op": "config",
|
||||
"offset_distance": -0.0,
|
||||
"approx_tol": 1e-06,
|
||||
"operation": 0,
|
||||
"inset": false,
|
||||
"extend_convex": false,
|
||||
"merge_results": false,
|
||||
"create_solid": false,
|
||||
"install_offset_brep": false,
|
||||
"original_edges": 2,
|
||||
"offset_edges": 3,
|
||||
"face_count": 3,
|
||||
"shell_face_sets": [],
|
||||
"relations": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "config_tiny_distance",
|
||||
"inputs": {
|
||||
"op": "config",
|
||||
"offset_distance": 1e-12,
|
||||
"approx_tol": 1e-06,
|
||||
"operation": 0,
|
||||
"inset": false,
|
||||
"extend_convex": false,
|
||||
"merge_results": false,
|
||||
"create_solid": false,
|
||||
"install_offset_brep": false,
|
||||
"original_edges": 2,
|
||||
"offset_edges": 3,
|
||||
"face_count": 3,
|
||||
"shell_face_sets": [],
|
||||
"relations": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "config_huge_negative_distance",
|
||||
"inputs": {
|
||||
"op": "config",
|
||||
"offset_distance": -1000000000.0,
|
||||
"approx_tol": 1e-06,
|
||||
"operation": 0,
|
||||
"inset": false,
|
||||
"extend_convex": false,
|
||||
"merge_results": false,
|
||||
"create_solid": false,
|
||||
"install_offset_brep": false,
|
||||
"original_edges": 2,
|
||||
"offset_edges": 3,
|
||||
"face_count": 3,
|
||||
"shell_face_sets": [],
|
||||
"relations": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,108 @@
|
||||
{
|
||||
"cases": [
|
||||
{
|
||||
"name": "probe_replace_empty",
|
||||
"inputs": {
|
||||
"op": "probe",
|
||||
"branch": "replace_empty",
|
||||
"approx_tol": 1e-06
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "probe_remove_no_merge",
|
||||
"inputs": {
|
||||
"op": "probe",
|
||||
"branch": "remove_no_merge",
|
||||
"approx_tol": 1e-06
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "probe_remove_empty",
|
||||
"inputs": {
|
||||
"op": "probe",
|
||||
"branch": "remove_empty",
|
||||
"approx_tol": 1e-06
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "probe_side_wall_null",
|
||||
"inputs": {
|
||||
"op": "probe",
|
||||
"branch": "side_wall_null",
|
||||
"approx_tol": 1e-06
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "probe_side_wall_unmapped",
|
||||
"inputs": {
|
||||
"op": "probe",
|
||||
"branch": "side_wall_unmapped",
|
||||
"approx_tol": 1e-06
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "probe_fill_empty",
|
||||
"inputs": {
|
||||
"op": "probe",
|
||||
"branch": "fill_empty",
|
||||
"approx_tol": 1e-06
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "probe_vertex_null",
|
||||
"inputs": {
|
||||
"op": "probe",
|
||||
"branch": "vertex_null",
|
||||
"approx_tol": 1e-06
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "probe_vertex_isolated",
|
||||
"inputs": {
|
||||
"op": "probe",
|
||||
"branch": "vertex_isolated",
|
||||
"approx_tol": 1e-06
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "probe_faceuse_null",
|
||||
"inputs": {
|
||||
"op": "probe",
|
||||
"branch": "faceuse_null",
|
||||
"approx_tol": 1e-06
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "probe_faceuse_no_surface",
|
||||
"inputs": {
|
||||
"op": "probe",
|
||||
"branch": "faceuse_no_surface",
|
||||
"approx_tol": 1e-06
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "probe_piecewise_empty",
|
||||
"inputs": {
|
||||
"op": "probe",
|
||||
"branch": "piecewise_empty",
|
||||
"approx_tol": 1e-06
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "probe_map_plane",
|
||||
"inputs": {
|
||||
"op": "probe",
|
||||
"branch": "map_plane",
|
||||
"approx_tol": 1e-06
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "probe_edgeuses_unmapped",
|
||||
"inputs": {
|
||||
"op": "probe",
|
||||
"branch": "edgeuses_unmapped",
|
||||
"approx_tol": 1e-06
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,166 @@
|
||||
{
|
||||
"cases": [
|
||||
{
|
||||
"name": "config_tangency_positive_zero",
|
||||
"inputs": {
|
||||
"op": "config",
|
||||
"offset_distance": 0.125,
|
||||
"approx_tol": 1e-6,
|
||||
"tangency_tol_radians": 0.0,
|
||||
"operation": 0,
|
||||
"inset": false,
|
||||
"extend_convex": true,
|
||||
"merge_results": true,
|
||||
"create_solid": false,
|
||||
"install_offset_brep": true,
|
||||
"original_edges": 1,
|
||||
"offset_edges": 2,
|
||||
"face_count": 2,
|
||||
"shell_face_sets": [[1, 1], [0, 1, 0], []],
|
||||
"relations": [[0, 0], [0, 0], [1, 2], [1, 3]]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "config_tangency_negative_zero",
|
||||
"inputs": {
|
||||
"op": "config",
|
||||
"offset_distance": -0.125,
|
||||
"approx_tol": 1e-6,
|
||||
"tangency_tol_radians": -0.0,
|
||||
"operation": 1,
|
||||
"inset": true,
|
||||
"extend_convex": false,
|
||||
"merge_results": false,
|
||||
"create_solid": true,
|
||||
"install_offset_brep": false,
|
||||
"original_edges": 1,
|
||||
"offset_edges": 2,
|
||||
"face_count": 2,
|
||||
"shell_face_sets": [[1, 1], [0, 1, 0], []],
|
||||
"relations": [[0, 0], [0, 0], [1, 2], [1, 3]]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "config_tangency_tiny_positive",
|
||||
"inputs": {
|
||||
"op": "config",
|
||||
"offset_distance": 0.125,
|
||||
"approx_tol": 1e-12,
|
||||
"tangency_tol_radians": 1e-12,
|
||||
"operation": 2,
|
||||
"inset": false,
|
||||
"extend_convex": true,
|
||||
"merge_results": true,
|
||||
"create_solid": true,
|
||||
"install_offset_brep": true,
|
||||
"original_edges": 1,
|
||||
"offset_edges": 2,
|
||||
"face_count": 2,
|
||||
"shell_face_sets": [[1, 1], [0, 1, 0], []],
|
||||
"relations": [[0, 0], [0, 0], [1, 2], [1, 3]]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "config_tangency_tiny_negative",
|
||||
"inputs": {
|
||||
"op": "config",
|
||||
"offset_distance": -0.125,
|
||||
"approx_tol": 1e-12,
|
||||
"tangency_tol_radians": -1e-12,
|
||||
"operation": 3,
|
||||
"inset": true,
|
||||
"extend_convex": true,
|
||||
"merge_results": false,
|
||||
"create_solid": false,
|
||||
"install_offset_brep": false,
|
||||
"original_edges": 1,
|
||||
"offset_edges": 2,
|
||||
"face_count": 2,
|
||||
"shell_face_sets": [[1, 1], [0, 1, 0], []],
|
||||
"relations": [[0, 0], [0, 0], [1, 2], [1, 3]]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "config_tangency_positive_pi",
|
||||
"inputs": {
|
||||
"op": "config",
|
||||
"offset_distance": 1.0,
|
||||
"approx_tol": 1e-6,
|
||||
"tangency_tol_radians": 3.141592653589793,
|
||||
"operation": 4,
|
||||
"inset": false,
|
||||
"extend_convex": false,
|
||||
"merge_results": true,
|
||||
"create_solid": false,
|
||||
"install_offset_brep": true,
|
||||
"original_edges": 1,
|
||||
"offset_edges": 2,
|
||||
"face_count": 2,
|
||||
"shell_face_sets": [[1, 1], [0, 1, 0], []],
|
||||
"relations": [[0, 0], [0, 0], [1, 2], [1, 3]]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "config_tangency_negative_pi",
|
||||
"inputs": {
|
||||
"op": "config",
|
||||
"offset_distance": -1.0,
|
||||
"approx_tol": 1e-6,
|
||||
"tangency_tol_radians": -3.141592653589793,
|
||||
"operation": 0,
|
||||
"inset": true,
|
||||
"extend_convex": false,
|
||||
"merge_results": true,
|
||||
"create_solid": true,
|
||||
"install_offset_brep": false,
|
||||
"original_edges": 1,
|
||||
"offset_edges": 2,
|
||||
"face_count": 2,
|
||||
"shell_face_sets": [[1, 1], [0, 1, 0], []],
|
||||
"relations": [[0, 0], [0, 0], [1, 2], [1, 3]]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "config_tangency_nonround_positive_order_sensitive",
|
||||
"note": "Compute radians * 180.0 / PI in that order, not radians * (180.0 / PI).",
|
||||
"inputs": {
|
||||
"op": "config",
|
||||
"offset_distance": 0.125,
|
||||
"approx_tol": 1e-6,
|
||||
"tangency_tol_radians": 0.1234567890123456,
|
||||
"operation": 1,
|
||||
"inset": false,
|
||||
"extend_convex": true,
|
||||
"merge_results": false,
|
||||
"create_solid": true,
|
||||
"install_offset_brep": true,
|
||||
"original_edges": 1,
|
||||
"offset_edges": 2,
|
||||
"face_count": 2,
|
||||
"shell_face_sets": [[1, 1], [0, 1, 0], []],
|
||||
"relations": [[0, 0], [0, 0], [1, 2], [1, 3]]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "config_tangency_nonround_negative_order_sensitive",
|
||||
"note": "The custom geometry tolerance remains signed in both constructor and final snapshots.",
|
||||
"inputs": {
|
||||
"op": "config",
|
||||
"offset_distance": -0.125,
|
||||
"approx_tol": 1e-6,
|
||||
"tangency_tol_radians": -0.1234567890123456,
|
||||
"operation": 2,
|
||||
"inset": true,
|
||||
"extend_convex": true,
|
||||
"merge_results": true,
|
||||
"create_solid": true,
|
||||
"install_offset_brep": false,
|
||||
"original_edges": 1,
|
||||
"offset_edges": 2,
|
||||
"face_count": 2,
|
||||
"shell_face_sets": [[1, 1], [0, 1, 0], []],
|
||||
"relations": [[0, 0], [0, 0], [1, 2], [1, 3]]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,652 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Native adapter for offset.offset_executive_p1 (PR #175).
|
||||
// Upstream: source/SMLib/inc/SmOffsetExecutive.h, src/SmOffsetExecutive.cpp.
|
||||
//
|
||||
// Rust API/protocol: crates/solidcore-ffi/src/units/offset_offset_executive_p1.rs.
|
||||
// config -> default/custom geometry ctor views, native executive ctor/dtor,
|
||||
// state setters/getters, SmRelation/GetOffsetEdgeuse.
|
||||
// Config input begins distance, approx_tol, tangency_tol_radians, operation.
|
||||
// Its constructor-state and final-state snapshots both use the supplied geometry;
|
||||
// other modes retain the native default 2-degree tangency constructor behavior.
|
||||
// synchronize -> SyncronizeOffsetEdgeuses, which calls all four owned static helpers.
|
||||
// extend -> GetCreateExtendedSurface with real SmPlane/SmBSplineSurface geometry.
|
||||
// probe -> independent safe topology paths; not a replacement for arithmetic tests.
|
||||
// Deviations: bounded plain-C fixture protocol, traversal IDs instead of pointers.
|
||||
// No implementation file is included; no native numerical/helper code is copied.
|
||||
#include "sc_shim.h"
|
||||
#include "StdAfx.h"
|
||||
#include <SmBSplineCurve.h>
|
||||
#include <SmBSplineSurface.h>
|
||||
#include <SmBrep.h>
|
||||
#include <SmContext.h>
|
||||
#include <SmEdge.h>
|
||||
#include <SmEdgeuse.h>
|
||||
#include <SmFace.h>
|
||||
#include <SmFaceuse.h>
|
||||
#include <SmLine.h>
|
||||
#include <SmNurbsCrv.h>
|
||||
#include <SmNurbsSrf.h>
|
||||
#include <SmOffsetExecutive.h>
|
||||
#include <SmOffsetGeometryCreation.h>
|
||||
#include <SmPlane.h>
|
||||
#include <SmSSIData.h>
|
||||
#include <SmVertex.h>
|
||||
#include <nurbs.h>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
constexpr int64_t max_values = 1048576;
|
||||
constexpr int64_t max_items = 4096;
|
||||
constexpr int64_t max_points = 65536;
|
||||
constexpr int64_t max_degree = 17; // NL_MAXDEG, not an arbitrary evaluation degree.
|
||||
struct Argument {};
|
||||
struct Rejected { SmStatus status; };
|
||||
|
||||
// A second bounds check here makes malformed direct ABI calls fail as arguments,
|
||||
// not read beyond the caller's buffer. Geometric invariants are checked in Rust.
|
||||
struct Reader {
|
||||
const double* data;
|
||||
int64_t size;
|
||||
int64_t pos = 0;
|
||||
double number() {
|
||||
if (pos >= size) throw Argument{};
|
||||
double v = data[pos++];
|
||||
if (!std::isfinite(v)) throw Argument{};
|
||||
return v;
|
||||
}
|
||||
int64_t index(int64_t limit = max_items) {
|
||||
double v = number();
|
||||
if (v < 0 || v > (double)limit || std::floor(v) != v) throw Argument{};
|
||||
return (int64_t)v;
|
||||
}
|
||||
bool flag() { return index(1) != 0; }
|
||||
SmPoint3d point() {
|
||||
double x = number(), y = number(), z = number();
|
||||
return SmPoint3d(x, y, z);
|
||||
}
|
||||
SmPoint2d uv() {
|
||||
double x = number(), y = number();
|
||||
return SmPoint2d(x, y);
|
||||
}
|
||||
SmExtent1d interval() {
|
||||
double a = number(), b = number();
|
||||
if (a > b || !std::isfinite(b-a)) throw Argument{};
|
||||
return SmExtent1d(a, b);
|
||||
}
|
||||
SmExtent2d domain() {
|
||||
SmPoint2d a = uv(), b = uv();
|
||||
if (!(a.x < b.x && a.y < b.y)) throw Argument{};
|
||||
return SmExtent2d(a, b);
|
||||
}
|
||||
void done() const { if (pos != size) throw Argument{}; }
|
||||
};
|
||||
|
||||
void constructed(SmStatus status) {
|
||||
if (status != SM_SUCCESS) throw Rejected{status};
|
||||
}
|
||||
void put_point(std::vector<double>& out, const SmPoint3d& p) {
|
||||
out.insert(out.end(), {p.x, p.y, p.z});
|
||||
}
|
||||
void put_domain(std::vector<double>& out, const SmExtent2d& d) {
|
||||
out.insert(out.end(), {d.GetMin().x, d.GetMin().y, d.GetMax().x, d.GetMax().y});
|
||||
}
|
||||
template<class T> double id(const SmTArray<T*>& list, const T* p) {
|
||||
if (!p) return -1;
|
||||
for (ULONG i=0; i<list.GetSize(); ++i) if (list[i] == p) return (double)i;
|
||||
return -2;
|
||||
}
|
||||
void put_ids(std::vector<double>& out, const SmTArray<SmEdgeuse*>& values,
|
||||
const SmTArray<SmEdgeuse*>& traversal) {
|
||||
out.push_back(values.GetSize());
|
||||
for (ULONG i=0; i<values.GetSize(); ++i) out.push_back(id(traversal, values[i]));
|
||||
}
|
||||
std::unique_ptr<SmBrep> brep(SmContext& ctx) {
|
||||
return std::unique_ptr<SmBrep>(new (ctx) SmBrep(SM_USE_DEFAULT, SM_USE_DEFAULT, &ctx));
|
||||
}
|
||||
|
||||
// Config-only geometry-subclass fixture. Preserve base initialization and the
|
||||
// inherited GetTangencyTolDegrees calculation; only the protected scalar changes.
|
||||
class ConfigGeometry : public SmOffsetGeometryCreation {
|
||||
public:
|
||||
ConfigGeometry(double distance, double tol, double tangency_tol_radians)
|
||||
: SmOffsetGeometryCreation(distance, tol) {
|
||||
m_dTangencyTolRadians = tangency_tol_radians;
|
||||
}
|
||||
};
|
||||
|
||||
// Only fixture injection/observation: all behavior under test remains native.
|
||||
class Executive : public SmOffsetExecutive {
|
||||
public:
|
||||
Executive(SmContext& ctx, SmOffsetOperationType op, SmOffsetGeometryCreation& geometry,
|
||||
SmBrep* original) : SmOffsetExecutive(ctx, op, geometry, original) {}
|
||||
void install(std::unique_ptr<SmBrep> offset) { m_pOffsetBrep = offset.release(); }
|
||||
SmStatus relate(SmEdgeuse* a, SmEdgeuse* b) { return m_vEUToEU.RelatePair(a, b); }
|
||||
void seconds(SmEdgeuse* a, SmTArray<SmEdgeuse*>& b) { m_vEUToEU.GetSeconds(a, b); }
|
||||
bool merge() const { return m_bMergeResults != FALSE; }
|
||||
bool solid() const { return m_bCreateOffsetSolid != FALSE; }
|
||||
SmSurface* cached(SmSurface* base) { return m_vSurfExtSurf.At(base); }
|
||||
void forget(SmSurface* base) { m_vSurfExtSurf.Remove(base); }
|
||||
void clear() { m_vSurfExtSurf.RemoveAll(); }
|
||||
ULONG cache_count() const { return m_vSurfExtSurf.Count(); }
|
||||
};
|
||||
|
||||
std::unique_ptr<SmCurve> read_curve(Reader& rd, SmContext& ctx) {
|
||||
int64_t kind = rd.index(1);
|
||||
if (kind == 0) {
|
||||
SmPoint3d start = rd.point(), end = rd.point();
|
||||
SmExtent1d domain = rd.interval();
|
||||
SmLine* line = nullptr;
|
||||
SmStatus status = SmLine::CreateLineSegment(ctx, 3, start, end, line, &domain);
|
||||
std::unique_ptr<SmCurve> result(line);
|
||||
constructed(status);
|
||||
if (!result) throw std::bad_alloc();
|
||||
return result;
|
||||
}
|
||||
auto degree = (gw_DEGREE)rd.index(max_degree);
|
||||
auto npoints = (gw_INDEX)rd.index(max_points);
|
||||
if (degree < 1 || npoints <= degree) throw Argument{};
|
||||
gw_INDEX n = npoints - 1, m = npoints + degree;
|
||||
std::unique_ptr<gw_CURVE, decltype(&sm_FreeNurbCurve)> raw(
|
||||
sm_AllocateNurbCurve(n, degree, m), &sm_FreeNurbCurve);
|
||||
gw_CURVE* curve = raw.get();
|
||||
if (!curve) throw std::bad_alloc();
|
||||
// The native constructor tunes knots, so initialize storage BEFORE invoking it.
|
||||
for (gw_INDEX i=0; i<npoints; ++i) {
|
||||
curve->pol->Pw[i].x = rd.number(); curve->pol->Pw[i].y = rd.number();
|
||||
curve->pol->Pw[i].z = rd.number(); curve->pol->Pw[i].w = rd.number();
|
||||
}
|
||||
for (gw_INDEX i=0; i<=m; ++i) curve->knt->U[i] = rd.number();
|
||||
std::unique_ptr<SmCurve> result(new (ctx) SmBSplineCurve(curve, 3, FALSE, &ctx));
|
||||
raw.release();
|
||||
return result;
|
||||
}
|
||||
|
||||
SmEdge* make_wire(SmContext& ctx, SmBrep* target, std::unique_ptr<SmCurve> curve,
|
||||
const SmExtent1d& interval) {
|
||||
SmPoint3d a(0,0,0), b(0,0,0);
|
||||
constructed(curve->EvaluatePoint(interval.GetMin(), a));
|
||||
constructed(curve->EvaluatePoint(interval.GetMax(), b));
|
||||
SmShell* shell = nullptr;
|
||||
SmVertex *v1 = nullptr, *v2 = nullptr;
|
||||
constructed(target->MakeShellVertex(nullptr, a, shell, v1));
|
||||
constructed(target->MakeShellVertex(nullptr, b, shell, v2));
|
||||
SmEdge* edge = nullptr;
|
||||
// MakeWireEdge can fail after assigning the curve to a newly made edge.
|
||||
// The guard must not delete a curve already owned by that topology.
|
||||
struct Pending {
|
||||
SmCurve* p;
|
||||
~Pending() { if (p && !p->GetOwner()) delete p; }
|
||||
} pending{curve.release()};
|
||||
constructed(target->MakeWireEdge(nullptr, v1, v2, pending.p, interval, SM_OT_SAME, edge));
|
||||
if (!edge || !edge->GetPrimaryEdgeuse()) throw Argument{};
|
||||
(void)ctx;
|
||||
return edge;
|
||||
}
|
||||
|
||||
SmEdge* unit_wire(SmContext& ctx, SmBrep* target, double y) {
|
||||
SmLine* curve = nullptr;
|
||||
SmExtent1d interval(0,1);
|
||||
constructed(SmLine::CreateLineSegment(ctx, 3, SmPoint3d(0,y,0), SmPoint3d(1,y,0),
|
||||
curve, &interval));
|
||||
return make_wire(ctx, target, std::unique_ptr<SmCurve>(curve), interval);
|
||||
}
|
||||
|
||||
SmFace* plane_face(SmContext& ctx, SmBrep* target, double z) {
|
||||
SmExtent2d domain(SmPoint2d(0,0), SmPoint2d(1,1));
|
||||
SmPlane* plane = new (ctx) SmPlane(SmPoint3d(0,0,z), SmVector3d(1,0,0),
|
||||
SmVector3d(0,1,0), SmVector2d(1,1), domain, &ctx);
|
||||
struct Pending {
|
||||
SmSurface* p;
|
||||
~Pending() { if (p && !p->GetOwner()) delete p; }
|
||||
} pending{plane};
|
||||
SmFace* face = nullptr;
|
||||
constructed(target->CreateFaceFromSurface(plane, domain, face));
|
||||
if (!face) throw Argument{};
|
||||
return face;
|
||||
}
|
||||
|
||||
std::unique_ptr<SmSurface> read_surface(Reader& rd, SmContext& ctx) {
|
||||
int64_t kind = rd.index(1);
|
||||
if (kind == 0) {
|
||||
SmPoint3d origin = rd.point();
|
||||
SmVector3d x = rd.point(), y = rd.point();
|
||||
SmVector2d scale = rd.uv();
|
||||
SmExtent2d domain = rd.domain();
|
||||
return std::unique_ptr<SmSurface>(new (ctx) SmPlane(origin, x, y, scale, domain, &ctx));
|
||||
}
|
||||
auto p = (gw_DEGREE)rd.index(max_degree), q = (gw_DEGREE)rd.index(max_degree);
|
||||
auto nu = (gw_INDEX)rd.index(max_points), nv = (gw_INDEX)rd.index(max_points);
|
||||
if (p < 1 || q < 1 || nu <= p || nv <= q || (int64_t)nu*nv > max_points)
|
||||
throw Argument{};
|
||||
gw_INDEX r = nu+p, s = nv+q;
|
||||
auto free_surface = [](gw_SURFACE* p) { smos_Free(p); };
|
||||
std::unique_ptr<gw_SURFACE, decltype(free_surface)> raw(
|
||||
sm_AllocateNurbSurface(nu-1, nv-1, p, q, r, s), free_surface);
|
||||
gw_SURFACE* surface = raw.get();
|
||||
if (!surface) throw std::bad_alloc();
|
||||
for (gw_INDEX i=0; i<nu; ++i) for (gw_INDEX j=0; j<nv; ++j) {
|
||||
surface->net->Pw[i][j].x = rd.number(); surface->net->Pw[i][j].y = rd.number();
|
||||
surface->net->Pw[i][j].z = rd.number(); surface->net->Pw[i][j].w = rd.number();
|
||||
}
|
||||
for (gw_INDEX i=0; i<=r; ++i) surface->knu->U[i] = rd.number();
|
||||
for (gw_INDEX i=0; i<=s; ++i) surface->knv->U[i] = rd.number();
|
||||
std::unique_ptr<SmBSplineSurface> result(new (ctx) SmBSplineSurface(surface, FALSE, &ctx));
|
||||
raw.release();
|
||||
result->SetBSplineSurfaceForm(SM_SF_UNSPECIFIED);
|
||||
return std::unique_ptr<SmSurface>(result.release());
|
||||
}
|
||||
|
||||
void state(std::vector<double>& out, Executive& executive, SmOffsetGeometryCreation& geometry) {
|
||||
SmTArray<SmFace*> faces;
|
||||
executive.GetShellFaces(faces);
|
||||
out.insert(out.end(), {
|
||||
geometry.GetOffsetDistance(), geometry.GetThisApproxTol3d(),
|
||||
(double)executive.GetOffsetOperation(), (double)executive.GetInset(),
|
||||
(double)executive.GetExtendConvexEdges(), (double)executive.merge(),
|
||||
(double)executive.solid(), executive.GetTangencyTolDegrees(),
|
||||
executive.GetOffsetBrep() ? 1.0 : 0.0, (double)faces.GetSize()
|
||||
});
|
||||
}
|
||||
|
||||
void config(Reader& rd, std::vector<double>& out) {
|
||||
double distance = rd.number(), tol = rd.number(), tangency_tol_radians = rd.number();
|
||||
auto operation = (SmOffsetOperationType)rd.index(4);
|
||||
bool inset = rd.flag(), extend = rd.flag(), merge = rd.flag(), solid = rd.flag();
|
||||
bool install = rd.flag();
|
||||
int64_t norig = rd.index(), noffset = rd.index(), nfaces = rd.index();
|
||||
SmContext ctx;
|
||||
auto original = brep(ctx), offset = brep(ctx), face_owner = brep(ctx);
|
||||
for (int64_t i=0; i<norig; ++i) unit_wire(ctx, original.get(), (double)i);
|
||||
for (int64_t i=0; i<noffset; ++i) unit_wire(ctx, offset.get(), (double)i);
|
||||
for (int64_t i=0; i<nfaces; ++i) plane_face(ctx, face_owner.get(), (double)i);
|
||||
SmTArray<SmEdgeuse*> originals, offsets;
|
||||
SmTArray<SmFace*> faces;
|
||||
original->GetEdgeuses(originals); offset->GetEdgeuses(offsets); face_owner->GetFaces(faces);
|
||||
ConfigGeometry geometry(distance, tol, tangency_tol_radians);
|
||||
Executive executive(ctx, operation, geometry, original.get());
|
||||
out.push_back(0);
|
||||
state(out, executive, geometry);
|
||||
executive.SetInset(inset ? TRUE : FALSE);
|
||||
executive.SetExtendConvexEdges(extend ? TRUE : FALSE);
|
||||
executive.SetMergeResults(merge ? TRUE : FALSE);
|
||||
executive.SetCreateOffsetSolid(solid ? TRUE : FALSE);
|
||||
if (install) executive.install(std::move(offset)); // only executive deletes this B-rep.
|
||||
std::vector<double> shell_records;
|
||||
int64_t nsets = rd.index();
|
||||
shell_records.push_back((double)nsets);
|
||||
for (int64_t i=0; i<nsets; ++i) {
|
||||
SmTArray<SmFace*> selectors, result;
|
||||
int64_t n = rd.index();
|
||||
for (int64_t j=0; j<n; ++j) {
|
||||
int64_t index = rd.index();
|
||||
if (index >= faces.GetSize()) throw Argument{};
|
||||
selectors.Add(faces[(ULONG)index]);
|
||||
}
|
||||
executive.SetShellFaces(selectors);
|
||||
// Mutating the caller's array checks SetShellFaces' copy, and prefilling
|
||||
// the destination checks that GetShellFaces resets, rather than appends.
|
||||
selectors.ReSet();
|
||||
if (faces.GetSize()) result.Add(faces[0]);
|
||||
executive.GetShellFaces(result);
|
||||
shell_records.push_back(result.GetSize());
|
||||
for (ULONG j=0; j<result.GetSize(); ++j) shell_records.push_back(id(faces, result[j]));
|
||||
}
|
||||
state(out, executive, geometry);
|
||||
out.insert(out.end(), shell_records.begin(), shell_records.end());
|
||||
int64_t npairs = rd.index();
|
||||
std::vector<double> statuses;
|
||||
for (int64_t i=0; i<npairs; ++i) {
|
||||
int64_t a = rd.index(max_items*2), b = rd.index(max_items*2);
|
||||
if (a >= originals.GetSize() || b >= offsets.GetSize()) throw Argument{};
|
||||
statuses.push_back(executive.relate(originals[(ULONG)a], offsets[(ULONG)b]));
|
||||
}
|
||||
rd.done();
|
||||
out.push_back(originals.GetSize());
|
||||
for (ULONG i=0; i<originals.GetSize(); ++i) {
|
||||
SmTArray<SmEdgeuse*> seconds;
|
||||
out.push_back(id(offsets, executive.GetOffsetEdgeuse(originals[i])));
|
||||
executive.seconds(originals[i], seconds);
|
||||
put_ids(out, seconds, offsets);
|
||||
}
|
||||
out.push_back((double)statuses.size());
|
||||
out.insert(out.end(), statuses.begin(), statuses.end());
|
||||
}
|
||||
|
||||
// Give each mapped wire a genuine two-edge loop. The second edge closes the
|
||||
// loop and is fixture scaffolding only. No UV trims are requested: synchronization
|
||||
// concerns the 3D edge ranges and must also accept nonplanar B-spline curves.
|
||||
SmEdge* loop_fixture(SmContext& ctx, SmBrep* target, std::unique_ptr<SmCurve> curve,
|
||||
const SmExtent1d& interval, std::vector<SmEdge*>& supports) {
|
||||
SmEdge* edge = make_wire(ctx, target, std::move(curve), interval);
|
||||
SmVertex* a = edge->GetStartVertex();
|
||||
SmVertex* b = edge->GetOtherVertex(a);
|
||||
// A B-spline closing segment also represents coincident endpoints without
|
||||
// requiring analytic-line direction normalization (zero-width inputs stay valid).
|
||||
SmBSplineCurve* closing = nullptr;
|
||||
SmStatus status = SmBSplineCurve::CreateLineSegment(ctx, 3, b->GetPoint(), a->GetPoint(), closing);
|
||||
std::unique_ptr<SmCurve> closing_owner(closing);
|
||||
constructed(status);
|
||||
if (!closing) throw std::bad_alloc();
|
||||
SmExtent1d unit = closing->GetNaturalInterval();
|
||||
struct PendingCurve {
|
||||
SmCurve* p;
|
||||
~PendingCurve() { if (p && !p->GetOwner()) delete p; }
|
||||
} pending_curve{closing_owner.release()};
|
||||
SmEdge* support = nullptr;
|
||||
constructed(target->MakeWireEdge(nullptr, b, a, pending_curve.p,
|
||||
unit, SM_OT_SAME, support));
|
||||
if (!support) throw Argument{};
|
||||
supports.push_back(support);
|
||||
SmTArray<ULONG> loop_sizes;
|
||||
SmTArray<SmEdgeuse*> uses;
|
||||
SmTArray<SmVertex*> vertices;
|
||||
loop_sizes.Add(2);
|
||||
uses.Add(edge->GetPrimaryEdgeuse()); uses.Add(support->GetPrimaryEdgeuse());
|
||||
SmExtent2d domain(SmPoint2d(0,0), SmPoint2d(1,1));
|
||||
struct PendingSurface {
|
||||
SmSurface* p;
|
||||
~PendingSurface() { if (p && !p->GetOwner()) delete p; }
|
||||
} plane{new (ctx) SmPlane(SmPoint3d(0,0,0), SmVector3d(1,0,0), SmVector3d(0,1,0),
|
||||
SmVector2d(1,1), domain, &ctx)};
|
||||
SmRegion* region = nullptr;
|
||||
SmShell* shell = nullptr;
|
||||
SmFace* face = nullptr;
|
||||
constructed(target->MakeFaceTopology(nullptr, plane.p, SM_OT_SAME, loop_sizes, uses, vertices,
|
||||
region, shell, face, FALSE, FALSE));
|
||||
SmEdgeuse* use = edge->GetPrimaryEdgeuse();
|
||||
if (!use || !use->GetCCWEdgeuse() || !use->GetCWEdgeuse()) throw Argument{};
|
||||
return edge;
|
||||
}
|
||||
|
||||
// Native traversal restricted to the measured edges: auxiliary closing edges are
|
||||
// not part of the unit's inputs or output topology. Split descendants remain here.
|
||||
void measured_traversal(SmBrep* target, const std::vector<SmEdge*>& supports,
|
||||
SmTArray<SmEdge*>& edges, SmTArray<SmEdgeuse*>& uses) {
|
||||
edges.ReSet(); uses.ReSet();
|
||||
SmTArray<SmEdge*> all;
|
||||
target->GetEdges(all);
|
||||
for (ULONG i=0; i<all.GetSize(); ++i) {
|
||||
if (std::find(supports.begin(), supports.end(), all[i]) != supports.end()) continue;
|
||||
edges.Add(all[i]);
|
||||
SmTArray<SmEdgeuse*> pair;
|
||||
all[i]->GetEdgeuses(pair);
|
||||
uses.Append(pair);
|
||||
}
|
||||
}
|
||||
|
||||
void put_eu_records(std::vector<double>& out, const SmTArray<SmEdge*>& edges,
|
||||
const SmTArray<SmEdgeuse*>& uses) {
|
||||
out.push_back(uses.GetSize());
|
||||
for (ULONG i=0; i<uses.GetSize(); ++i) {
|
||||
SmEdge* edge = uses[i]->GetEdge();
|
||||
const SmExtent1d& interval = edge->GetInterval();
|
||||
SmCurve* curve = edge->GetCurve();
|
||||
out.insert(out.end(), {(double)i, id(edges, edge), interval.GetMin(), interval.GetMax(),
|
||||
curve->ApproximateLength(interval, 10)});
|
||||
SmPoint3d point(0,0,0);
|
||||
out.push_back(curve->EvaluatePoint(interval.GetMin(), point));
|
||||
put_point(out, point);
|
||||
point.Set(0,0,0);
|
||||
out.push_back(curve->EvaluatePoint(interval.GetMax(), point));
|
||||
put_point(out, point);
|
||||
}
|
||||
}
|
||||
|
||||
void synchronize(Reader& rd, std::vector<double>& out) {
|
||||
bool null_original = rd.flag(), null_other = rd.flag();
|
||||
SmContext ctx;
|
||||
auto original = brep(ctx), offset = brep(ctx);
|
||||
SmEdgeuse* a = unit_wire(ctx, original.get(), 0)->GetPrimaryEdgeuse();
|
||||
SmEdgeuse* b = unit_wire(ctx, original.get(), 1)->GetPrimaryEdgeuse();
|
||||
SmTArray<SmEdgeuse*> first, second;
|
||||
std::vector<SmEdge*> supports;
|
||||
for (int side=0; side<2; ++side) {
|
||||
int64_t n = rd.index();
|
||||
for (int64_t i=0; i<n; ++i) {
|
||||
auto curve = read_curve(rd, ctx);
|
||||
SmExtent1d interval = rd.interval();
|
||||
SmEdge* edge = loop_fixture(ctx, offset.get(), std::move(curve), interval, supports);
|
||||
(side == 0 ? first : second).Add(edge->GetPrimaryEdgeuse());
|
||||
}
|
||||
}
|
||||
rd.done();
|
||||
SmOffsetGeometryCreation geometry(1, 1e-6);
|
||||
Executive executive(ctx, SM_OO_SOLID_OFFSET, geometry, original.get());
|
||||
SmBrep* target = offset.get();
|
||||
executive.install(std::move(offset));
|
||||
for (ULONG i=0; i<first.GetSize(); ++i) {
|
||||
SmStatus status = executive.relate(a, first[i]);
|
||||
if (status != SM_SUCCESS) throw Argument{};
|
||||
}
|
||||
if (!null_other) for (ULONG i=0; i<second.GetSize(); ++i) {
|
||||
SmStatus status = executive.relate(b, second[i]);
|
||||
if (status != SM_SUCCESS) throw Argument{};
|
||||
}
|
||||
if (null_original) a = nullptr;
|
||||
if (null_other) b = nullptr;
|
||||
SmTArray<SmEdgeuse*> before, related_a, related_b;
|
||||
SmTArray<SmEdge*> measured_edges;
|
||||
measured_traversal(target, supports, measured_edges, before);
|
||||
double lookup_a = id(before, executive.GetOffsetEdgeuse(a));
|
||||
double lookup_b = id(before, executive.GetOffsetEdgeuse(b));
|
||||
executive.seconds(a, related_a); executive.seconds(b, related_b);
|
||||
std::vector<double> relation_records;
|
||||
put_ids(relation_records, related_a, before); put_ids(relation_records, related_b, before);
|
||||
SmTArray<SmEdgeuse*> matched_a, matched_b, gap_a, gap_b;
|
||||
SmStatus status = executive.SyncronizeOffsetEdgeuses(a, b, matched_a, matched_b, gap_a, gap_b);
|
||||
out.insert(out.end(), {0, (double)status, lookup_a, lookup_b});
|
||||
SmTArray<SmEdgeuse*> after, fixtures;
|
||||
measured_traversal(target, supports, measured_edges, after);
|
||||
fixtures.Append(first); fixtures.Append(second);
|
||||
put_ids(out, fixtures, after);
|
||||
out.insert(out.end(), relation_records.begin(), relation_records.end());
|
||||
put_eu_records(out, measured_edges, after);
|
||||
put_ids(out, matched_a, after); put_ids(out, matched_b, after);
|
||||
put_ids(out, gap_a, after); put_ids(out, gap_b, after);
|
||||
}
|
||||
|
||||
// NL_CrvAdv.cpp's G1 extension retries a zero end tangent using Pw[2] or
|
||||
// Pw[n-2], even for a two-point curve. Check the native isocurves sampled by
|
||||
// NL_SrfAdv.cpp first: otherwise an accepted degenerate bilinear fixture can
|
||||
// read outside its control polygon. This is a safety check, not an extension
|
||||
// implementation, and does not reject short but normalizable tangents.
|
||||
void guard_two_point_isocurves(SmSurface* base) {
|
||||
SmBSplineSurface* spline = SM_CAST_PTR(SmBSplineSurface, base);
|
||||
if (!spline) return;
|
||||
gw_SURFACE* surface = spline->GetOrCreateGwNurbPointer();
|
||||
if (!surface) throw Argument{};
|
||||
for (int direction=0; direction<2; ++direction) {
|
||||
if ((direction == 0 ? surface->net->n : surface->net->m) != 1) continue;
|
||||
gw_KNOTVECTOR* cross_knots = direction == 0 ? surface->knv : surface->knu;
|
||||
for (double fraction : {0.07, 0.19, 0.45, 0.73, 0.92}) {
|
||||
double parameter = (1.0-fraction)*cross_knots->U[0]
|
||||
+ fraction*cross_knots->U[cross_knots->m];
|
||||
std::unique_ptr<gw_CURVE, decltype(&sm_FreeNurbCurve)> curve(
|
||||
sm_AllocateNurbCurve(1, 1, 3), &sm_FreeNurbCurve);
|
||||
if (!curve) throw std::bad_alloc();
|
||||
if (N_SrfExtractIsoCrv(surface, parameter, direction == 0 ? NL_UDIR : NL_VDIR,
|
||||
curve.get(), nullptr) != 0) continue;
|
||||
NL_POINT a, b;
|
||||
NL_VECTOR tangent, unit;
|
||||
NL_REAL magnitude = 0;
|
||||
N_CPtToPtEuclid(curve->pol->Pw[0], &a);
|
||||
N_CPtToPtEuclid(curve->pol->Pw[1], &b);
|
||||
N_VectorDir(a, b, &tangent);
|
||||
if (N_VectorNormalize(tangent, &unit, &magnitude) != 0) throw Argument{};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void extend(Reader& rd, std::vector<double>& out) {
|
||||
double distance = rd.number(), tol = rd.number();
|
||||
SmContext ctx;
|
||||
std::vector<std::unique_ptr<SmSurface>> bases, extended;
|
||||
int64_t n = rd.index();
|
||||
for (int64_t i=0; i<n; ++i) bases.push_back(read_surface(rd, ctx));
|
||||
std::vector<SmPoint2d> samples;
|
||||
int64_t nsamples = rd.index();
|
||||
for (int64_t i=0; i<nsamples; ++i) samples.push_back(rd.uv());
|
||||
auto original = brep(ctx);
|
||||
SmOffsetGeometryCreation geometry(distance, tol);
|
||||
Executive executive(ctx, SM_OO_SOLID_OFFSET, geometry, original.get());
|
||||
int64_t nactions = rd.index();
|
||||
out.insert(out.end(), {0, (double)nactions});
|
||||
for (int64_t i=0; i<nactions; ++i) {
|
||||
int64_t action = rd.index(2);
|
||||
if (action == 2) {
|
||||
out.insert(out.end(), {2, (double)executive.cache_count(), 0});
|
||||
executive.clear();
|
||||
continue;
|
||||
}
|
||||
double raw_key = rd.number();
|
||||
if (raw_key < -1 || std::floor(raw_key) != raw_key || raw_key >= (double)bases.size()
|
||||
|| (action == 1 && raw_key < 0)) throw Argument{};
|
||||
SmSurface* base = raw_key == -1 ? nullptr : bases.at((size_t)raw_key).get();
|
||||
bool hit = executive.cached(base) != nullptr;
|
||||
if (action == 1) {
|
||||
executive.forget(base);
|
||||
out.insert(out.end(), {1, hit ? 1.0 : 0.0, (double)executive.cache_count()});
|
||||
continue;
|
||||
}
|
||||
if (base && !hit) guard_two_point_isocurves(base);
|
||||
SmExtent2d domain;
|
||||
SmSurface* result = nullptr;
|
||||
SmStatus status = executive.GetCreateExtendedSurface(base, domain, result);
|
||||
// SmOffsetExecutive deletes its map, NOT the extended surfaces. Keep each
|
||||
// distinct returned allocation alive through eviction/clear and clean it
|
||||
// after the executive has destroyed the map. Never wrap a base alias twice.
|
||||
double result_id = -1;
|
||||
if (result) {
|
||||
for (size_t j=0; j<extended.size(); ++j)
|
||||
if (extended[j].get() == result) result_id = (double)j;
|
||||
if (result_id == -1) {
|
||||
if (std::any_of(bases.begin(), bases.end(),
|
||||
[result](const std::unique_ptr<SmSurface>& p){ return p.get() == result; }))
|
||||
throw Argument{}; // unsupported ownership contract, not a fixture rejection.
|
||||
result_id = (double)extended.size();
|
||||
extended.emplace_back(result);
|
||||
}
|
||||
}
|
||||
out.insert(out.end(), {0, (double)status, hit ? 1.0 : 0.0, result_id, result ? 1.0 : 0.0});
|
||||
if (status != SM_SUCCESS) continue;
|
||||
if (!result) throw Argument{};
|
||||
out.push_back(result->IsKindOf(SmPlane_TYPE) ? 1 :
|
||||
result->IsKindOf(SmBSplineSurface_TYPE) ? 2 : 3);
|
||||
put_domain(out, domain);
|
||||
out.push_back((double)samples.size());
|
||||
for (const SmPoint2d& fraction : samples) {
|
||||
SmPoint2d uv((1.0-fraction.x)*domain.GetMin().x + fraction.x*domain.GetMax().x,
|
||||
(1.0-fraction.y)*domain.GetMin().y + fraction.y*domain.GetMax().y);
|
||||
SmPoint3d point(0,0,0);
|
||||
SmVector3d normal(0,0,0);
|
||||
out.push_back(result->EvaluatePoint(uv, point)); put_point(out, point);
|
||||
out.push_back(result->EvaluateNormal(uv, TRUE, TRUE, normal)); put_point(out, normal);
|
||||
}
|
||||
}
|
||||
rd.done();
|
||||
}
|
||||
|
||||
void probe(Reader& rd, std::vector<double>& out) {
|
||||
int64_t operation = rd.index(12);
|
||||
double tol = rd.number();
|
||||
rd.done();
|
||||
SmContext ctx;
|
||||
auto original = brep(ctx), offset = brep(ctx);
|
||||
SmOffsetGeometryCreation geometry(0.25, tol);
|
||||
Executive executive(ctx, SM_OO_SOLID_OFFSET, geometry, original.get());
|
||||
executive.install(std::move(offset));
|
||||
SmTArray<SmCurve*> curves;
|
||||
SmTArray<SmOrientType> orientations;
|
||||
SmSurface* surface = nullptr;
|
||||
SmBrep* result = nullptr;
|
||||
SmStatus status = SM_SUCCESS;
|
||||
SmTArray<SmEdgeuse*> originals, offsets;
|
||||
switch (operation) {
|
||||
case 0: status = SmOffsetExecutive::ReplaceImplicitOffsets(original.get(), tol); break;
|
||||
case 1: status = executive.RemoveExcessFaces(executive.GetOffsetBrep()); break;
|
||||
case 2:
|
||||
executive.SetMergeResults(TRUE);
|
||||
status = executive.RemoveExcessFaces(executive.GetOffsetBrep()); break;
|
||||
case 3: status = executive.BuildSideWallOfEdgeuse(nullptr); break;
|
||||
case 4: status = executive.BuildSideWallOfEdgeuse(unit_wire(ctx, original.get(), 0)->GetPrimaryEdgeuse()); break;
|
||||
case 5: {
|
||||
SmTArray<SmEdgeuse*> gaps;
|
||||
SmTArray<SmFace*> faces;
|
||||
status = executive.FillGapEdgeuses(nullptr, gaps, faces); break;
|
||||
}
|
||||
case 6: status = executive.BuildOffsetOfVertex(nullptr, curves, orientations, surface); break;
|
||||
case 7: {
|
||||
SmShell* shell = nullptr;
|
||||
SmVertex* vertex = nullptr;
|
||||
constructed(original->MakeShellVertex(nullptr, SmPoint3d(0,0,0), shell, vertex));
|
||||
status = executive.BuildOffsetOfVertex(vertex, curves, orientations, surface); break;
|
||||
}
|
||||
case 8: case 9: {
|
||||
SmSSIData intersections;
|
||||
SmFaceuse* use = nullptr;
|
||||
if (operation == 9) use = plane_face(ctx, original.get(), 0)->GetUpwardFaceuse();
|
||||
status = executive.BuildOffsetOfFaceuse(use, surface, intersections); break;
|
||||
}
|
||||
case 10: status = executive.PiecewiseMerge(result); break;
|
||||
case 11: {
|
||||
SmFace* face = plane_face(ctx, original.get(), 0);
|
||||
status = executive.MapEdgeuses(face->GetUpwardFaceuse(), face);
|
||||
original->GetEdgeuses(originals); offsets.Append(originals);
|
||||
break;
|
||||
}
|
||||
case 12: {
|
||||
SmFace* face = plane_face(ctx, original.get(), 0);
|
||||
face->GetUpwardFaceuse()->GetEdgeuses(originals);
|
||||
if (!originals.GetSize()) throw Argument{};
|
||||
SmEdgeuse* use = originals[0];
|
||||
if (!use->GetRadial()) throw Argument{};
|
||||
status = executive.BuildOffsetOfEdgeuses(use, use->GetRadial(), SM_MT_MARK);
|
||||
originals.ReSet();
|
||||
break;
|
||||
}
|
||||
}
|
||||
// PiecewiseMerge transfers ownership out of m_pOffsetBrep before stitching.
|
||||
std::unique_ptr<SmBrep> result_owner(result);
|
||||
std::unique_ptr<SmSurface> surface_owner(surface);
|
||||
out.insert(out.end(), {0, (double)status, (double)curves.GetSize(), (double)orientations.GetSize(),
|
||||
surface ? 1.0 : 0.0, result ? 1.0 : 0.0,
|
||||
executive.GetOffsetBrep() ? 1.0 : 0.0});
|
||||
out.push_back(originals.GetSize());
|
||||
for (ULONG i=0; i<originals.GetSize(); ++i)
|
||||
out.push_back(id(offsets, executive.GetOffsetEdgeuse(originals[i])));
|
||||
for (ULONG i=0; i<curves.GetSize(); ++i) delete curves[i];
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// Plain-C ABI, mode 0 config / 1 synchronize / 2 extend / 3 probe.
|
||||
// Caller owns every buffer; no native allocation or object address crosses it.
|
||||
SC_EXPORT int32_t sc_offset_offset_executive_p1_eval(
|
||||
int32_t mode, const double* input, int64_t input_len,
|
||||
double* out, int64_t cap, int64_t* out_len) {
|
||||
if (!out_len || cap < 0 || (cap && !out) || !input || input_len < 0 || input_len > max_values)
|
||||
return SC_ERR_ARG;
|
||||
try {
|
||||
Reader rd{input, input_len};
|
||||
std::vector<double> result;
|
||||
try {
|
||||
switch (mode) {
|
||||
case 0: config(rd, result); break;
|
||||
case 1: synchronize(rd, result); break;
|
||||
case 2: extend(rd, result); break;
|
||||
case 3: probe(rd, result); break;
|
||||
default: throw Argument{};
|
||||
}
|
||||
} catch (const Rejected& rejection) {
|
||||
result = {-1, (double)rejection.status};
|
||||
}
|
||||
return sc_copy_out(result.data(), (int64_t)result.size(), out, cap, out_len);
|
||||
} catch (const Argument&) {
|
||||
return SC_ERR_ARG;
|
||||
} catch (...) {
|
||||
return SC_ERR_EXCEPTION;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user