// 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. // Extension takes an internal nonnegative magnitude, not a public signed offset: // SmOffsetExecutive.cpp:114-120,206-223 pass fabs(distance) to the geometry creator // and preserve the sign via SetInset; SmBrep.cpp:12004 uses bounding-sphere radius/2. // Reject negative extension distances before geometry construction as SC_ERR_ARG; // both signed zeros remain valid. Config only observes state, so stays sign-agnostic. // probe -> independent safe topology paths; not a replacement for arithmetic tests. // face tail -> BuildOffsetOfFaceuse's contained-domain branch on a MakeFaceWithCurves // plane face, including MapEdgeuses and self-intersection merging. // map twin -> MapEdgeuses alone, between two MakeFaceWithCurves rectangle faces. // replace -> ReplaceImplicitOffsets on one CreateFaceFromSurface face whose surface is // an owning SmOffsetSurface(d, base) of a real SmPlane, SmBSplineSurface or // SmSurfOfExtrusion (lazy: no NURB until GetOrCreateGwNurbPointer). // Deviations: bounded plain-C fixture protocol, traversal IDs instead of pointers; // extension-only nonnegative-distance validation. Native array ordering is unchanged. // No implementation file is included; no native numerical/helper code is copied. #include "sc_shim.h" #include "StdAfx.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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& out, const SmPoint3d& p) { out.insert(out.end(), {p.x, p.y, p.z}); } void put_domain(std::vector& out, const SmExtent2d& d) { out.insert(out.end(), {d.GetMin().x, d.GetMin().y, d.GetMax().x, d.GetMax().y}); } template double id(const SmTArray& list, const T* p) { if (!p) return -1; for (ULONG i=0; i& out, const SmTArray& values, const SmTArray& traversal) { out.push_back(values.GetSize()); for (ULONG i=0; i brep(SmContext& ctx) { return std::unique_ptr(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 offset) { m_pOffsetBrep = offset.release(); } SmStatus relate(SmEdgeuse* a, SmEdgeuse* b) { return m_vEUToEU.RelatePair(a, b); } void seconds(SmEdgeuse* a, SmTArray& b) { m_vEUToEU.GetSeconds(a, b); } SmStatus relate_face(SmFaceuse* a, SmFace* b) { return m_vFUToF.RelatePair(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(); } void faces_of(SmFaceuse* use, SmTArray& faces) { m_vFUToF.GetSeconds(use, faces); } }; std::unique_ptr 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 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 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; ipol->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 result(new (ctx) SmBSplineCurve(curve, 3, FALSE, &ctx)); raw.release(); return result; } SmEdge* make_wire(SmContext& ctx, SmBrep* target, std::unique_ptr 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(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 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(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 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; inet->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 result(new (ctx) SmBSplineSurface(surface, FALSE, &ctx)); raw.release(); result->SetBSplineSurfaceForm(SM_SF_UNSPECIFIED); return std::unique_ptr(result.release()); } void state(std::vector& out, Executive& executive, SmOffsetGeometryCreation& geometry) { SmTArray 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& 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 originals, offsets; SmTArray 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 shell_records; int64_t nsets = rd.index(); shell_records.push_back((double)nsets); for (int64_t i=0; i selectors, result; int64_t n = rd.index(); for (int64_t j=0; j= 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 statuses; for (int64_t i=0; i= 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 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()); } // A closing segment requires distinct endpoints (SmBSplineCurve.cpp's SAME_PNT_ER). // For exactly coincident endpoints, use a nonzero closed cubic instead; only this // unmeasured support changes kind. Its fixed unit weights and clamped [0,1] knots // are valid native constructor data, not a replacement for any measured geometry. std::unique_ptr closing_curve(SmContext& ctx, const SmPoint3d& start, const SmPoint3d& end) { if (start.x == end.x && start.y == end.y && start.z == end.z) { std::unique_ptr raw( sm_AllocateNurbCurve(3, 3, 7), &sm_FreeNurbCurve); if (!raw) throw std::bad_alloc(); const SmPoint3d points[] = {start, SmPoint3d(1,0,0), SmPoint3d(0,1,0), end}; for (gw_INDEX i=0; i<4; ++i) { raw->pol->Pw[i].x = points[i].x; raw->pol->Pw[i].y = points[i].y; raw->pol->Pw[i].z = points[i].z; raw->pol->Pw[i].w = 1; } for (gw_INDEX i=0; i<8; ++i) raw->knt->U[i] = i < 4 ? 0 : 1; std::unique_ptr result(new (ctx) SmBSplineCurve(raw.get(), 3, FALSE, &ctx)); raw.release(); return result; } SmBSplineCurve* closing = nullptr; SmStatus status = SmBSplineCurve::CreateLineSegment(ctx, 3, start, end, closing); std::unique_ptr result(closing); constructed(status); if (!result) throw std::bad_alloc(); return result; } // 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 curve, const SmExtent1d& interval, std::vector& supports) { SmEdge* edge = make_wire(ctx, target, std::move(curve), interval); SmVertex* a = edge->GetStartVertex(); SmVertex* b = edge->GetOtherVertex(a); auto closing_owner = closing_curve(ctx, b->GetPoint(), a->GetPoint()); SmExtent1d unit = closing_owner->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 loop_sizes; SmTArray uses; SmTArray 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& supports, SmTArray& edges, SmTArray& uses) { edges.ReSet(); uses.ReSet(); SmTArray all; target->GetEdges(all); for (ULONG i=0; i pair; all[i]->GetEdgeuses(pair); uses.Append(pair); } } void put_eu_records(std::vector& out, const SmTArray& edges, const SmTArray& uses) { out.push_back(uses.GetSize()); for (ULONG i=0; iGetEdge(); 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& 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 first, second; std::vector supports; for (int side=0; side<2; ++side) { int64_t n = rd.index(); for (int64_t i=0; iGetPrimaryEdgeuse()); } } 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 before, related_a, related_b; SmTArray 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 relation_records; put_ids(relation_records, related_a, before); put_ids(relation_records, related_b, before); SmTArray 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 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 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& out) { double distance = rd.number(), tol = rd.number(); if (distance < 0.0) throw Argument{}; SmContext ctx; std::vector> bases, extended; int64_t n = rd.index(); for (int64_t i=0; i samples; int64_t nsamples = rd.index(); for (int64_t i=0; i= (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& 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(); } // All new geometry remains locally owned even if construction/evaluation throws. // This guard is created before BuildOffsetOfVertex so partial native outputs are // cleaned on operation failure as well as on snapshot-allocation exceptions. struct VertexResult { SmTArray curves; SmTArray orientations; SmSurface* surface = nullptr; ~VertexResult() { for (ULONG i=0; i surface, const SmTArray& uses) { struct Pending { SmSurface* p; ~Pending() { if (p && !p->GetOwner()) delete p; } } pending{surface.release()}; SmTArray loops; loops.Add(uses.GetSize()); SmTArray vertices; SmRegion* region = nullptr; SmShell* shell = nullptr; SmFace* face = nullptr; constructed(target->MakeFace(nullptr, loops, uses, vertices, pending.p, pending.p->GetNaturalUVDomain(), SM_OT_SAME, region, shell, face, FALSE, FALSE)); if (!face) throw Argument{}; return face; } // A bounded plane, an interior vertex-loop, and a genuine new edge joining it // to the outer boundary. The interior endpoint has exactly one incident edge // and IsStrut is true. No source/result cap is preseeded. SmVertex* source_strut(SmContext& ctx, SmBrep* target, const SmPoint3d& point, SmFaceuse*& source_use) { SmExtent2d domain(SmPoint2d(-2,-2), SmPoint2d(2,2)); struct Pending { SmSurface* p; ~Pending() { if (p && !p->GetOwner()) delete p; } } pending{new (ctx) SmPlane(point, SmVector3d(1,0,0), SmVector3d(0,1,0), SmVector2d(1,1), domain, &ctx)}; SmFace* face = nullptr; constructed(target->CreateFaceFromSurface(pending.p, domain, face)); if (!face) throw Argument{}; SmTArray boundary; face->GetVertices(boundary); if (!boundary.GetSize()) throw Argument{}; SmVertex* start = boundary[0]; if (!start) throw Argument{}; SmVertex* vertex = nullptr; SmLoop* loop = nullptr; target->m_bEditingEnabled = TRUE; constructed(target->MakeVertexLoop(face, point, loop, vertex)); if (!vertex) throw Argument{}; SmBSplineCurve* curve = nullptr; SmStatus curve_status = SmBSplineCurve::CreateLineSegment(ctx, 3, start->GetPoint(), point, curve); struct PendingCurve { SmCurve* p; ~PendingCurve() { if (p && !p->GetOwner()) delete p; } } pending_curve{curve}; constructed(curve_status); if (!curve) throw Argument{}; SmEdge* edge = nullptr; SmFace* split_face = nullptr; constructed(target->MakeEdgeInFace(face, start, vertex, curve, nullptr, curve->GetNaturalInterval(), SM_OT_SAME, 0.0, edge, loop, split_face)); if (!edge || !edge->GetPrimaryEdgeuse()) throw Argument{}; SmEdgeuse* use = edge->GetPrimaryEdgeuse(); if (!use->GetVertexuse()) throw Argument{}; if (use->GetVertexuse()->GetVertex() != vertex) use = use->GetRadial(); // Safety invariant of this constructor, not an arithmetic parity assertion. if (!use || !use->IsStrut() || !use->GetFaceuse()) throw Argument{}; source_use = use->GetFaceuse(); return vertex; } SmFace* circular_disk(SmContext& ctx, SmBrep* target, const SmPoint3d& center, double radius) { SmAxis2Placement placement; placement.Translate(center); SmBSplineCurve* circle = nullptr; SmStatus status = SmBSplineCurve::CreateCircleSegment(ctx, 3, placement, radius, 0.0, 360.0, SM_CO_QUADRATIC, circle); std::unique_ptr circle_owner(circle); constructed(status); if (!circle) throw Argument{}; SmPoint3d point(0,0,0); constructed(circle->EvaluatePoint(circle->GetNaturalInterval().GetMin(), point)); SmShell* shell = nullptr; SmVertex* vertex = nullptr; constructed(target->MakeShellVertex(nullptr, point, shell, vertex)); struct Pending { SmCurve* p; ~Pending() { if (p && !p->GetOwner()) delete p; } } pending{circle_owner.release()}; SmEdge* edge = nullptr; constructed(target->MakeWireEdge(nullptr, vertex, vertex, pending.p, circle->GetNaturalInterval(), SM_OT_SAME, edge)); if (!edge || !edge->GetPrimaryEdgeuse()) throw Argument{}; SmTArray uses; uses.Add(edge->GetPrimaryEdgeuse()); SmExtent2d domain(SmPoint2d(-radius,-radius), SmPoint2d(radius,radius)); std::unique_ptr plane(new (ctx) SmPlane(center, SmVector3d(1,0,0), SmVector3d(0,1,0), SmVector2d(1,1), domain, &ctx)); return face_on_edges(target, std::move(plane), uses); } void put_vertex_surface(std::vector& out, SmSurface* surface, const std::vector& samples) { out.push_back(surface->GetType()); SmExtent2d domain = surface->GetNaturalUVDomain(); put_domain(out, domain); // GetType is compared separately: never turn an analytic Sphere into a // B-spline merely to make the samples agree. SmBSplineSurface* spline = SM_CAST_PTR(SmBSplineSurface, surface); gw_SURFACE* nurb = spline ? spline->GetGwNurbPointer() : nullptr; out.push_back(nurb ? 1.0 : 0.0); if (nurb) { gw_INDEX n=nurb->net->n, m=nurb->net->m, r=nurb->knu->m, s=nurb->knv->m; out.insert(out.end(), {(double)n, (double)m, (double)nurb->p, (double)nurb->q, (double)r, (double)s}); for (gw_INDEX i=0; i<=n; ++i) for (gw_INDEX j=0; j<=m; ++j) { const auto& point = nurb->net->Pw[i][j]; out.insert(out.end(), {point.x,point.y,point.z,point.w}); } for (gw_INDEX i=0; i<=r; ++i) out.push_back(nurb->knu->U[i]); for (gw_INDEX i=0; i<=s; ++i) out.push_back(nurb->knv->U[i]); } out.push_back(samples.size()); for (const auto& sample : samples) { SmPoint2d uv((1.0-sample.x)*domain.GetMin().x + sample.x*domain.GetMax().x, (1.0-sample.y)*domain.GetMin().y + sample.y*domain.GetMax().y); SmPoint3d point(0,0,0); SmVector3d du(0,0,0), dv(0,0,0), duv(0,0,0), duu(0,0,0), dvv(0,0,0); SmStatus status = surface->Evaluate2ndDerivatives(uv, TRUE, TRUE, point, du, dv, duv, duu, dvv); out.push_back(status); if (status != SM_SUCCESS) { out.insert(out.end(), 18, 0.0); } else { put_point(out, point); put_point(out, du); put_point(out, dv); put_point(out, duv); put_point(out, duu); put_point(out, dvv); } } } void vertex(Reader& rd, std::vector& out) { SmPoint3d point = rd.point(); double distance=rd.number(), approx_tol=rd.number(); double original_tol=rd.number(), result_tol=rd.number(); bool has_strut=rd.flag(); double radius=0.0, height=0.0; bool map_mate=false; if (has_strut) { radius=rd.number(); height=rd.number(); map_mate=rd.flag(); for (double value : {point.x-2.0, point.x+2.0, point.y-2.0, point.y+2.0, point.z+height, point.x+radius, point.x-radius, point.y+radius, point.y-radius}) if (!std::isfinite(value)) throw Argument{}; } std::vector samples; int64_t count=rd.index(); for (int64_t i=0; i1 || sample.y<0 || sample.y>1) throw Argument{}; samples.push_back(sample); } rd.done(); SmContext ctx; auto original=brep(ctx), offset=brep(ctx); original->SetTolerance(original_tol, FALSE, FALSE); offset->SetTolerance(result_tol, FALSE, FALSE); SmVertex* source=nullptr; SmFaceuse* source_use=nullptr; SmFace* mapped_face=nullptr; if (has_strut) { source=source_strut(ctx, original.get(), point, source_use); mapped_face=circular_disk(ctx, offset.get(), SmPoint3d(point.x,point.y,point.z+height), radius); } else { SmShell* shell=nullptr; constructed(original->MakeShellVertex(nullptr, point, shell, source)); } SmOffsetGeometryCreation geometry(distance, approx_tol); Executive executive(ctx, SM_OO_SOLID_OFFSET, geometry, original.get()); executive.install(std::move(offset)); if (has_strut) { if (map_mate) source_use=source_use->GetMate(); if (executive.relate_face(source_use, mapped_face) != SM_SUCCESS) throw Argument{}; } VertexResult result; SmStatus status=executive.BuildOffsetOfVertex(source, result.curves, result.orientations, result.surface); out.insert(out.end(), {0, (double)status, original->GetTolerance(), executive.GetOffsetBrep()->GetTolerance(), (double)result.curves.GetSize(), (double)result.orientations.GetSize(), result.surface ? 1.0 : 0.0}); for (ULONG i=0; iGetNaturalInterval(); out.insert(out.end(), {(double)curve->GetType(), interval.GetMin(), interval.GetMax(), 4}); for (double fraction : {0.113,0.371,0.729,0.913}) { double parameter=(1.0-fraction)*interval.GetMin()+fraction*interval.GetMax(); SmPoint3d point(0,0,0); SmStatus status=curve->EvaluatePoint(parameter,point); out.push_back(status); if (status==SM_SUCCESS) put_point(out,point); else out.insert(out.end(),3,0.0); } } if (result.surface) put_vertex_surface(out, result.surface, samples); } void probe(Reader& rd, std::vector& 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 curves; SmTArray orientations; SmSurface* surface = nullptr; SmBrep* result = nullptr; SmStatus status = SM_SUCCESS; SmTArray 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 gaps; SmTArray 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 result_owner(result); std::unique_ptr 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 curves; SmSurface* surface = nullptr; Unowned() = default; Unowned(const Unowned&) = delete; Unowned& operator=(const Unowned&) = delete; ~Unowned() { for (SmCurve* c : curves) if (c && !c->GetOwner()) delete c; if (surface && !surface->GetOwner()) delete surface; } }; SmPoint3d vertex_point(SmEdgeuse* use) { if (!use || !use->GetVertexuse() || !use->GetVertexuse()->GetVertex()) throw Argument{}; return use->GetVertexuse()->GetVertex()->GetPoint(); } // A plane face made by MakeFaceWithCurves from explicit 3D and UV lines // (CreateFaceFromSurface's natural trim curves have no Rust fixture twin): the // straight-edged loop `points` at height z on the plane with axes X, Y (or Y, X // when `flip`, so its normal points down and UV is (y, x)), the plane's domain = // the loop's UV bounding box. SmFace* curve_face(SmContext& ctx, SmBrep* target, const std::vector& points, double z, bool flip) { const size_t n = points.size(); if (n < 3) throw Argument{}; double x0 = points[0].x, y0 = points[0].y, x1 = x0, y1 = y0; for (const SmPoint2d& p : points) { x0 = std::min(x0, p.x); y0 = std::min(y0, p.y); x1 = std::max(x1, p.x); y1 = std::max(y1, p.y); } Unowned owned; SmTArray curves3d; SmTArray uv; // MakeFaceWithCurves deletes the UV curves it is given (SmObjsDelete, // SmBrep.cpp:15743), so they leave this guard when the call is made. struct UvGuard { SmTArray* list; ~UvGuard() { if (list) for (ULONG i=0; iGetSize(); ++i) delete (*list)[i]; } } uv_guard{&uv}; SmTArray orients; SmTArray vertex_points; // MakeFaceWithCurves loop points (none) SmTArray loops; SmExtent1d unit(0,1); auto uv_of = [flip](double x, double y) { return flip ? SmPoint3d(y, x, 0) : SmPoint3d(x, y, 0); }; for (size_t i=0; iMakeFaceWithCurves(target->GetInfiniteRegion(), loops, &curves3d, &uv, orients, vertex_points, owned.surface, domain, SM_OT_SAME, region, shell, face)); if (!face || !face->GetUpwardFaceuse() || !face->GetUpwardFaceuse()->GetMate()) throw Argument{}; return face; } // The loop of a rectangle, counter-clockwise from its lower-left corner, with the // bottom edge split at `split` when given. std::vector rectangle_loop(const SmExtent2d& rect, const double* split) { const double x0 = rect.GetMin().x, y0 = rect.GetMin().y, x1 = rect.GetMax().x, y1 = rect.GetMax().y; std::vector points{SmPoint2d(x0, y0)}; if (split) points.push_back(SmPoint2d(*split, y0)); points.insert(points.end(), {SmPoint2d(x1, y0), SmPoint2d(x1, y1), SmPoint2d(x0, y1)}); return points; } SmFace* curve_face(SmContext& ctx, SmBrep* target) { return curve_face(ctx, target, rectangle_loop(SmExtent2d(SmPoint2d(0,0), SmPoint2d(1,1)), nullptr), 0.0, false); } // Per original edge use of `use`, sorted by the use's own start point (GetEdgeuses // starts a loop at a construction-dependent use): start xyz, then -1 when // GetOffsetEdgeuse is null, else 1 and the start points of the offset use and of // its mate. void put_mapped_uses(std::vector& out, Executive& executive, SmFaceuse* use) { SmTArray uses; use->GetEdgeuses(uses); std::vector> records; for (ULONG i=0; i record; put_point(record, vertex_point(uses[i])); SmEdgeuse* mapped = executive.GetOffsetEdgeuse(uses[i]); if (!mapped) { record.push_back(-1); } else { record.push_back(1); put_point(record, vertex_point(mapped)); put_point(record, vertex_point(mapped->GetMate())); } records.push_back(record); } std::sort(records.begin(), records.end()); out.push_back((double)records.size()); for (auto& r : records) out.insert(out.end(), r.begin(), r.end()); } // BuildOffsetOfFaceuse with a real offset plane z = distance over the given domain. // Input: distance, approx_tol, domain, local, extend, merge, mate, then a selector // 0 / 1 / 2 and that many UV segments of one self-intersection: the 3D line at // z = distance runs along the first segment; the second UV curve is the same line // (selector 1) or the second segment (selector 2). Output: status, the related // faces (edge count and UV domain each, sorted by domain), offset B-rep // face/edge/vertex counts, its sorted vertex points, then per original edge use // (sorted by its start point) GetOffsetEdgeuse's end points. void face_tail(Reader& rd, std::vector& out) { double distance = rd.number(), tol = rd.number(); SmExtent2d domain = rd.domain(); bool local = rd.flag(), extend = rd.flag(), merge = rd.flag(), mate = rd.flag(); int64_t segments = rd.index(2); bool has_ssi = segments > 0; SmPoint2d s0(0,0), s1(0,0), t0(0,0), t1(0,0); if (has_ssi) { s0 = rd.uv(); s1 = rd.uv(); t0 = s0; t1 = s1; } if (segments == 2) { t0 = rd.uv(); t1 = rd.uv(); } rd.done(); SmContext ctx; auto original = brep(ctx), offset = brep(ctx); SmFace* face = curve_face(ctx, original.get()); SmFaceuse* use = face->GetUpwardFaceuse(); if (mate) use = use->GetMate(); SmOffsetGeometryCreation geometry(distance, tol); Executive executive(ctx, local ? SM_OO_LOCAL_OPERATION : SM_OO_SOLID_OFFSET, geometry, original.get()); executive.install(std::move(offset)); executive.SetExtendConvexEdges(extend ? TRUE : FALSE); executive.SetMergeResults(merge ? TRUE : FALSE); // Declared after the executive: checks ownership before the offset B-rep dies. Unowned owned; SmSSIData intersections; if (has_ssi) { SmExtent1d unit(0,1); SmLine* line = nullptr; SmStatus status = SmLine::CreateLineSegment(ctx, 3, SmPoint3d(s0.x, s0.y, distance), SmPoint3d(s1.x, s1.y, distance), line, &unit); owned.curves.push_back(line); constructed(status); intersections.m_v3DCurves.Add(line); for (int i=0; i<2; ++i) { SmBSplineCurve* trim = nullptr; const SmPoint2d& a = i ? t0 : s0; const SmPoint2d& b = i ? t1 : s1; status = SmBSplineCurve::CreateLineSegment(ctx, 2, SmPoint3d(a.x, a.y, 0), SmPoint3d(b.x, b.y, 0), trim); owned.curves.push_back(trim); constructed(status); (i ? intersections.m_vUVCurves2 : intersections.m_vUVCurves1).Add(trim); } } owned.surface = new (ctx) SmPlane(SmPoint3d(0,0,distance), SmVector3d(1,0,0), SmVector3d(0,1,0), SmVector2d(1,1), domain, &ctx); SmSurface* surface = owned.surface; SmStatus status = executive.BuildOffsetOfFaceuse(use, surface, intersections); out.insert(out.end(), {0, (double)status}); SmTArray related; executive.faces_of(use, related); std::vector> summaries; for (ULONG i=0; i edges; related[i]->GetEdges(edges); SmExtent2d d = related[i]->GetUVDomain(); summaries.push_back({d.GetMin().x, d.GetMin().y, d.GetMax().x, d.GetMax().y, (double)edges.GetSize()}); } std::sort(summaries.begin(), summaries.end()); out.push_back((double)summaries.size()); for (auto& v : summaries) out.insert(out.end(), v.begin(), v.end()); SmBrep* result = executive.GetOffsetBrep(); if (!result) throw Argument{}; SmTArray faces; SmTArray edges; SmTArray vertices; result->GetFaces(faces); result->GetEdges(edges); result->GetVertices(vertices); out.insert(out.end(), {(double)faces.GetSize(), (double)edges.GetSize(), (double)vertices.GetSize()}); std::vector> points; for (ULONG i=0; iGetPoint(); points.push_back({p.x, p.y, p.z}); } std::sort(points.begin(), points.end()); for (auto& p : points) out.insert(out.end(), p.begin(), p.end()); put_mapped_uses(out, executive, use); } // MapEdgeuses alone, on two MakeFaceWithCurves faces: the original rectangle at // z = 0 and an offset rectangle at z = distance (its plane flipped when asked), // matching the upward or downward original use. Input: distance, original rect, // offset rect, flip, mate, then a flag and optionally the x at which the offset // rectangle's bottom edge is split into two collinear edges. Output: status, then // per original edge use (sorted by its start point) GetOffsetEdgeuse's end points // as in face_tail. void map_twin(Reader& rd, std::vector& out) { double distance = rd.number(); SmExtent2d original_rect = rd.domain(), offset_rect = rd.domain(); bool flip = rd.flag(), mate = rd.flag(), has_split = rd.flag(); double split = 0; if (has_split) { split = rd.number(); if (!(offset_rect.GetMin().x < split && split < offset_rect.GetMax().x)) throw Argument{}; } rd.done(); SmContext ctx; auto original = brep(ctx), offset = brep(ctx); SmFace* face = curve_face(ctx, original.get(), rectangle_loop(original_rect, nullptr), 0.0, false); SmFace* twin = curve_face(ctx, offset.get(), rectangle_loop(offset_rect, has_split ? &split : nullptr), distance, flip); SmFaceuse* use = face->GetUpwardFaceuse(); if (mate) use = use->GetMate(); SmOffsetGeometryCreation geometry(distance, 1e-6); Executive executive(ctx, SM_OO_SOLID_OFFSET, geometry, original.get()); executive.install(std::move(offset)); SmStatus status = executive.MapEdgeuses(use, twin); out.insert(out.end(), {0, (double)status}); put_mapped_uses(out, executive, use); } void replace(Reader& rd, std::vector& out) { SmContext ctx; int64_t base_kind = rd.index(1); std::unique_ptr base; if (base_kind == 0) { base = read_surface(rd, ctx); } else { std::unique_ptr curve = read_curve(rd, ctx); SmBSplineCurve* generator = SM_CAST_PTR(SmBSplineCurve, curve.get()); if (!generator) throw Argument{}; SmPoint3d origin = rd.point(); SmVector3d direction = rd.point(); SmExtent2d domain = rd.domain(); bool swap = rd.flag(); // The extrusion owns its generator curve. base.reset(new (ctx) SmSurfOfExtrusion(generator, origin, direction, domain, swap ? TRUE : FALSE)); curve.release(); } double distance = rd.number(), tol = rd.number(); int64_t nsamples = rd.index(64); std::vector samples; for (int64_t i=0; i1 || sample.y<0 || sample.y>1) throw Argument{}; samples.push_back(sample); } rd.done(); auto original = brep(ctx); struct Pending { SmSurface* p; ~Pending() { if (p && !p->GetOwner()) delete p; } } pending{new (ctx) SmOffsetSurface(distance, *base, TRUE)}; base.release(); SmExtent2d natural = pending.p->GetNaturalUVDomain(); SmFace* face = nullptr; constructed(original->CreateFaceFromSurface(pending.p, natural, face)); if (!face) throw Argument{}; SmStatus status = SmOffsetExecutive::ReplaceImplicitOffsets(original.get(), tol); SmTArray faces; original->GetFaces(faces); out.insert(out.end(), {0, (double)status, (double)faces.GetSize()}); for (ULONG i=0; iGetSurface(); out.push_back(surface ? (double)surface->GetType() : -1.0); put_domain(out, faces[i]->GetUVDomain()); if (!surface) continue; SmExtent2d domain = surface->GetNaturalUVDomain(); put_domain(out, domain); out.push_back((double)samples.size()); for (const SmPoint2d& f : samples) { SmPoint2d uv((1.0-f.x)*domain.GetMin().x + f.x*domain.GetMax().x, (1.0-f.y)*domain.GetMin().y + f.y*domain.GetMax().y); SmPoint3d point(0,0,0); SmStatus eval = surface->EvaluatePoint(uv, point); out.push_back(eval); if (eval == SM_SUCCESS) put_point(out, point); else out.insert(out.end(), 3, 0.0); } } } } // namespace // Plain-C ABI, mode 0 config / 1 synchronize / 2 extend / 3 probe / 4 face tail / // 5 map twin / 6 vertex / 7 replace. // 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 { SC_NATIVE_LOCK; Reader rd{input, input_len}; std::vector 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; case 6: vertex(rd, result); break; case 4: face_tail(rd, result); break; case 5: map_twin(rd, result); break; case 7: replace(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; } }