Files
solvecraft/harness/cpp/shim/surface_offset_surface.cpp
T
Pluto-XI ffc35b1716 surface.offset_surface: difftest harness (WIP), NLib and box fixes
Shim, FFI wrapper, runner and fixtures (upstream TestSmCreateOffsetSurface,
smTopoTest nested offsets, smMergeTest .sms offsets, edge cases). 3057 cases:
1479 bit-identical, 0 failed, 1578 deferred (GetSingularities, surface_p3).

Fixes found by the difftest: N_SrfIsClosedSmooth stores its flag before
returning a failed-angle error, so N_SrfEvalPtDerivsUnbounded keeps the copy
and flag for a retry (nlib.srf_nurb, nlib.srf_adv_p1); SmSurface's base
CalculateBoundingBox maps a failed cache to SM_ERR_NULL_POINTER (NER).

WIP: a 30k-seed run still has one C++ hang to guard (case gen120xx).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 14:35:42 -06:00

753 lines
22 KiB
C++

// SPDX-License-Identifier: Apache-2.0
// difftest shim for unit `surface.offset_surface`.
// Wraps SmOffsetSurface (source/SMLib/src/SmOffsetSurface.cpp, source/SMLib/inc/SmOffsetSurface.h):
// the constructor, Copy, operator==, CalculateBoundingBox, GetKnots, EvaluateNormal,
// EvaluatePoint, Evaluate, EvaluateSimple, GetNaturalUVDomain, Reverse, SplitAt,
// STEPInversion, SwapUV, Transform, TrimWithDomain, CreateOffsetSurface,
// CreateExtendedSurface (both), ReadFromDB, IsKindOf and the setters, run as a script on
// one offset so that the extension upstream's const evaluators store is seen by the
// following ops. WriteToDB is a generator helper.
//
// One entry point, sc_surface_offset_surface_eval(op, in, n_in, out, cap, out_len). Layouts
// are documented in crates/solidcore-ffi/src/units/surface_offset_surface.rs, which
// validates every input so upstream only touches valid memory. The surface-of-revolution
// block and its builder are those of surface_surf_of_revolution_p1.cpp.
#include "sc_shim.h"
#include "StdAfx.h"
#include <SmAxis2Placement.h>
#include <SmBSplineCurve.h>
#include <SmBSplineSurface.h>
#include <SmCircle.h>
#include <SmContext.h>
#include <SmCurve.h>
#include <SmCurveBoundedSurface.h>
#include <SmDatabaseIO.h>
#include <SmExtent1d.h>
#include <SmExtent2d.h>
#include <SmExtent3d.h>
#include <SmLine.h>
#include <SmNurbsCrv.h>
#include <SmNurbsSrf.h>
#include <SmOffsetSurface.h>
#include <SmPlane.h>
#include <SmPolarBox.h>
#include <SmPseudoBox.h>
#include <SmSTEPSurface.h>
#include <SmSurfOfRevolution.h>
#include <SmSurface.h>
#include <SmTArray.h>
#include <SmVector2d.h>
#include <SmVector3d.h>
#include <nurbs.h>
#include <sstream>
#include <string>
#include <vector>
namespace
{
// Entry ops (keep in sync with crates/solidcore-ffi/src/units/surface_offset_surface.rs).
enum : int32_t
{
OP_SCRIPT = 0,
OP_WRITE_DB = 1,
OP_READ_DB = 2,
};
// Script steps.
enum : int64_t
{
S_STATE = 0,
S_EVAL = 1,
S_EVAL_SIMPLE = 2,
S_EVAL_PT = 3,
S_NORMAL = 4,
S_KNOTS = 5,
S_MAX_ANAL = 6,
S_BBOX = 7,
S_STEP_INV = 8,
S_REVERSE = 9,
S_SWAP = 10,
S_TRANSFORM = 11,
S_TRIM = 12,
S_SPLIT = 13,
S_CREATE_OFFSET = 14,
S_EXTEND = 15,
S_EXTEND_DIR = 16,
S_COPY = 17,
S_EQ = 18,
S_SET_DIST = 19,
S_SET_ALLOW = 20,
S_IS_PLANAR = 21,
S_IS_KIND_OF = 22,
};
// Generator kinds and construction modes of a surface-of-revolution block.
enum : int32_t
{
GK_BSPLINE = 0,
GK_LINE = 1,
GK_CIRCLE = 2,
};
enum : int32_t
{
BM_CANONICAL = 0,
BM_CTOR = 1,
};
// Reads the protected members of SmOffsetSurface through pointers to members named in a
// derived class.
struct PeekOff : SmOffsetSurface
{
static SmBoolean SmOffsetSurface::*allow() { return &PeekOff::m_bAllowExtension; }
static SmBoolean SmOffsetSurface::*owns() { return &PeekOff::m_bOwnsSurface; }
static SmBoolean SmOffsetSurface::*per_u() { return &PeekOff::m_bIsPeriodicOnU; }
static SmBoolean SmOffsetSurface::*per_v() { return &PeekOff::m_bIsPeriodicOnV; }
};
// Reaches the protected SmSurfOfRevolution constructor.
struct PeekRev : SmSurfOfRevolution
{
PeekRev(SmBSplineCurve* c, const SmPoint3d& o, const SmVector3d& x, const SmVector3d& y,
const SmExtent2d& d, SmBoolean swap)
: SmSurfOfRevolution(c, o, x, y, d, swap)
{
}
};
struct Rd
{
const double* in;
int64_t at;
double num() { return in[at++]; }
int64_t idx() { return (int64_t)in[at++]; }
bool flag() { return in[at++] != 0.0; }
SmBoolean bflag() { return flag() ? TRUE : FALSE; }
SmVector3d vec()
{
double x = num();
double y = num();
double z = num();
return SmVector3d(x, y, z);
}
SmPoint2d uv()
{
double u = num();
double v = num();
return SmPoint2d(u, v);
}
SmExtent2d dom()
{
double u0 = num();
double v0 = num();
double u1 = num();
double v1 = num();
return SmExtent2d(SmPoint2d(u0, v0), SmPoint2d(u1, v1));
}
};
double st(SmStatus s) { return s == SM_SUCCESS ? 0.0 : (double)s; }
double flg(SmBoolean b) { return b ? 1.0 : 0.0; }
void put_vec(std::vector<double>& o, const SmVector3d& v)
{
o.push_back(v.x);
o.push_back(v.y);
o.push_back(v.z);
}
void put_dom(std::vector<double>& o, const SmExtent2d& d)
{
o.push_back(d.GetMin().x);
o.push_back(d.GetMin().y);
o.push_back(d.GetMax().x);
o.push_back(d.GetMax().y);
}
void put_ivl(std::vector<double>& o, const SmExtent1d& e)
{
o.push_back(e.GetMin());
o.push_back(e.GetMax());
}
const SmContext& the_context()
{
static SmContext* p = new SmContext();
return *p;
}
// ---- surface-of-revolution block (as surface_surf_of_revolution_p1.cpp) ----------------
SmBSplineCurve* read_rev_bspline(Rd& rd, const SmContext& ctx)
{
gw_INDEX n = (gw_INDEX)rd.idx();
gw_DEGREE p = (gw_DEGREE)rd.idx();
gw_INDEX m = (gw_INDEX)rd.idx();
gw_CURVE* cur = sm_AllocateNurbCurve(n, p, m);
for (gw_INDEX i = 0; i <= n; i++)
{
cur->pol->Pw[i].x = rd.num();
cur->pol->Pw[i].y = rd.num();
cur->pol->Pw[i].z = rd.num();
cur->pol->Pw[i].w = rd.num();
}
for (gw_INDEX i = 0; i <= m; i++)
cur->knt->U[i] = rd.num();
return new (ctx) SmBSplineCurve(cur, 3, FALSE);
}
SmBSplineCurve* read_gen(Rd& rd, const SmContext& ctx)
{
int64_t kind = rd.idx();
switch (kind)
{
case GK_BSPLINE: return read_rev_bspline(rd, ctx);
case GK_LINE:
{
SmPoint3d a = rd.vec();
SmPoint3d b = rd.vec();
SmLine* l = new (ctx) SmLine(a, b, 3, &ctx);
l->GetOrCreateGwNurbPointer();
return l;
}
case GK_CIRCLE:
{
SmPoint3d c = rd.vec();
SmVector3d x = rd.vec();
SmVector3d y = rd.vec();
double d0 = rd.num();
double d1 = rd.num();
double r = rd.num();
return new (ctx) SmCircle(c, x, y, SmExtent1d(d0, d1), r, 3, &ctx);
}
default: return NULL;
}
}
// Writes nothing; returns NULL and sets `stt` when the construction fails.
SmSurfOfRevolution* read_revolution(Rd& rd, const SmContext& ctx, SmStatus& stt)
{
int64_t mode = rd.idx();
SmBSplineCurve* gen = read_gen(rd, ctx);
bool oob = rd.flag();
bool swap = rd.flag();
SmSurfOfRevolution* s = NULL;
stt = SM_SUCCESS;
if (mode == BM_CANONICAL)
{
SmPoint3d p = rd.vec();
SmVector3d a = rd.vec();
stt = SmSurfOfRevolution::CreateCanonical(ctx, gen, p, a, s, swap ? TRUE : FALSE);
}
else
{
SmPoint3d org = rd.vec();
SmVector3d x = rd.vec();
SmVector3d y = rd.vec();
double a = rd.num();
double b = rd.num();
double c = rd.num();
double d = rd.num();
s = new (ctx) PeekRev(gen, org, x, y, SmExtent2d(a, b, c, d), swap ? TRUE : FALSE);
stt = s->MakeNurb();
}
if (stt != SM_SUCCESS)
return NULL; // (leaked, as the other construction failures)
s->SetOutOfBoundsEnabled(oob ? TRUE : FALSE);
return s;
}
// ---- base and offset blocks -------------------------------------------------------------
SmOffsetSurface* read_offset(Rd& rd, const SmContext& ctx, SmStatus& stt);
// A base block; NULL (with `stt`) when a surface of revolution fails to build.
SmSurface* read_surface(Rd& rd, const SmContext& ctx, SmStatus& stt)
{
int64_t kind = rd.idx();
switch (kind)
{
case 0:
{
bool oob = rd.flag();
SmBSplineSurfaceForm form = (SmBSplineSurfaceForm)rd.idx();
gw_INDEX n = (gw_INDEX)rd.idx();
gw_INDEX m = (gw_INDEX)rd.idx();
gw_DEGREE p = (gw_DEGREE)rd.idx();
gw_DEGREE q = (gw_DEGREE)rd.idx();
gw_INDEX r = (gw_INDEX)rd.idx();
gw_INDEX s = (gw_INDEX)rd.idx();
gw_SURFACE* sur = sm_AllocateNurbSurface(n, m, p, q, r, s);
for (gw_INDEX i = 0; i <= n; i++)
for (gw_INDEX j = 0; j <= m; j++)
{
sur->net->Pw[i][j].x = rd.num();
sur->net->Pw[i][j].y = rd.num();
sur->net->Pw[i][j].z = rd.num();
sur->net->Pw[i][j].w = rd.num();
}
for (gw_INDEX i = 0; i <= r; i++)
sur->knu->U[i] = rd.num();
for (gw_INDEX i = 0; i <= s; i++)
sur->knv->U[i] = rd.num();
SmBSplineSurface* b = new (ctx) SmBSplineSurface(sur, FALSE, NULL);
b->GetOutOfBoundsEnabled() = oob ? TRUE : FALSE;
b->SetBSplineSurfaceForm(form);
return b;
}
case 1:
{
SmPoint3d o = rd.vec();
SmVector3d n = rd.vec();
return new (ctx) SmPlane(o, n, &ctx);
}
case 2:
{
SmSurface* basis = read_surface(rd, ctx, stt);
bool implicit = rd.flag();
if (basis == NULL)
return NULL;
SmTArray<SmCurve*> c3d, cuv;
return new (ctx) SmCurveBoundedSurface(*basis, &c3d, &cuv, implicit ? TRUE : FALSE);
}
case 3:
{
SmSurface* base = read_surface(rd, ctx, stt);
if (base == NULL)
return NULL;
return new (ctx) SmSTEPSurface(*base, TRUE);
}
case 4: return read_revolution(rd, ctx, stt);
default: return read_offset(rd, ctx, stt);
}
}
// An offset block `d owns sing allow base`.
SmOffsetSurface* read_offset(Rd& rd, const SmContext& ctx, SmStatus& stt)
{
double d = rd.num();
SmBoolean owns = rd.bflag();
ULONG sing = (ULONG)rd.idx();
SmBoolean allow = rd.bflag();
SmSurface* base = read_surface(rd, ctx, stt);
if (base == NULL)
return NULL;
SmOffsetSurface* off = new (ctx) SmOffsetSurface(d, *base, owns, sing);
off->SetAllowExtension(allow);
return off;
}
// ---- outputs ------------------------------------------------------------------------------
// A 3x3 EvaluatePoint grid over the natural domain: each status and point.
void put_grid(std::vector<double>& o, const SmSurface* s)
{
SmExtent2d d = s->GetNaturalUVDomain();
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
{
SmPoint2d uv = d.Evaluate(i / 2.0, j / 2.0);
SmPoint3d p;
SmStatus stt = s->EvaluatePoint(uv, p);
o.push_back(st(stt));
if (stt == SM_SUCCESS)
put_vec(o, p);
}
}
void put_state(std::vector<double>& o, SmOffsetSurface* s)
{
put_dom(o, s->GetNaturalUVDomain());
o.push_back(s->GetOffsetDistance());
o.push_back(flg(s->*PeekOff::allow()));
o.push_back(flg(s->*PeekOff::owns()));
o.push_back((double)(s->*PeekOff::per_u()));
o.push_back((double)(s->*PeekOff::per_v()));
o.push_back((double)s->GetSingularities());
SmSurface* e = s->GetExtendedBaseSurface();
o.push_back(e ? 1.0 : 0.0);
if (e)
{
put_dom(o, e->GetNaturalUVDomain());
o.push_back((double)e->GetType());
}
SmSurface* b = s->GetBaseSurface();
o.push_back((double)b->GetType());
put_dom(o, b->GetNaturalUVDomain());
o.push_back((double)s->GetType());
put_grid(o, s);
}
// A surface an op returns: its type, then the offset's state, or (another kind) its
// natural domain and point grid.
void put_result(std::vector<double>& o, SmSurface* r)
{
o.push_back((double)r->GetType());
SmOffsetSurface* off = SM_CAST_PTR(SmOffsetSurface, r);
if (off)
{
put_state(o, off);
}
else
{
put_dom(o, r->GetNaturalUVDomain());
put_grid(o, r);
}
}
// The entries upstream writes: all, or with bOnlyUpperHalf those with i + j up to the
// larger order (the others are untouched caller memory).
void put_matrix(std::vector<double>& o, const std::vector<SmVector3d>& a, ULONG hu, ULONG hv, bool upper)
{
ULONG hod = hu > hv ? hu : hv;
for (ULONG i = 0; i <= hu; i++)
for (ULONG j = 0; j <= hv; j++)
if (!upper || i + j <= hod)
put_vec(o, a[i * (hv + 1) + j]);
}
// ---- the script -----------------------------------------------------------------------------
void run_step(int64_t step, Rd& rd, const SmContext& ctx, SmOffsetSurface* s, std::vector<double>& o)
{
switch (step)
{
case S_STATE:
o.push_back(0.0);
put_state(o, s);
break;
case S_EVAL:
case S_EVAL_SIMPLE:
{
SmPoint2d uv = rd.uv();
ULONG hu = (ULONG)rd.idx();
ULONG hv = (ULONG)rd.idx();
SmBoolean ul = rd.bflag();
SmBoolean vl = rd.bflag();
SmBoolean upper = rd.bflag();
std::vector<SmVector3d> a((hu + 1) * (hv + 1)); // SM_UNDEF_DOUBLE entries
SmStatus stt;
if (step == S_EVAL)
{
SmBoolean nzt = rd.bflag();
stt = s->Evaluate(uv, hu, hv, ul, vl, upper, a.data(), nzt);
}
else
{
stt = s->EvaluateSimple(uv, hu, hv, ul, vl, upper, a.data());
}
o.push_back(st(stt));
if (stt == SM_SUCCESS)
put_matrix(o, a, hu, hv, upper != FALSE);
break;
}
case S_EVAL_PT:
{
SmPoint2d uv = rd.uv();
SmPoint3d p;
SmStatus stt = s->EvaluatePoint(uv, p);
o.push_back(st(stt));
if (stt == SM_SUCCESS)
put_vec(o, p);
break;
}
case S_NORMAL:
{
SmPoint2d uv = rd.uv();
SmBoolean ul = rd.bflag();
SmBoolean vl = rd.bflag();
SmVector3d n;
SmStatus stt = s->EvaluateNormal(uv, ul, vl, n);
o.push_back(st(stt));
if (stt == SM_SUCCESS)
put_vec(o, n);
break;
}
case S_KNOTS:
{
SmSurfParamType param = (SmSurfParamType)rd.idx();
bool has_ivl = rd.flag();
double a = rd.num();
double b = rd.num();
SmExtent1d ivl(a, b);
SmTArray<double> knots;
SmTArray<ULONG> mults;
SmStatus stt = s->GetKnots(param, knots, &mults, has_ivl ? &ivl : NULL);
o.push_back(st(stt));
if (stt == SM_SUCCESS)
{
o.push_back((double)knots.GetSize());
for (ULONG i = 0; i < knots.GetSize(); i++)
o.push_back(knots[i]);
o.push_back((double)mults.GetSize());
for (ULONG i = 0; i < mults.GetSize(); i++)
o.push_back((double)mults[i]);
}
break;
}
case S_MAX_ANAL:
o.push_back(0.0);
put_dom(o, s->GetMaxAnalyticDomain());
break;
case S_BBOX:
{
SmExtent2d d = rd.dom();
bool w0 = rd.flag();
bool w1 = rd.flag();
bool w2 = rd.flag();
SmBoolean expand = rd.bflag();
SmExtent3d nb;
SmPseudoBox pb;
SmPolarBox qb;
SmStatus stt = s->CalculateBoundingBox(d, w0 ? &nb : NULL, w1 ? &pb : NULL, w2 ? &qb : NULL, NULL,
NULL, expand);
o.push_back(st(stt));
if (stt == SM_SUCCESS)
{
if (w0)
{
put_vec(o, nb.GetMin());
put_vec(o, nb.GetMax());
}
if (w1)
{
for (ULONG i = 0; i < 3; i++)
put_vec(o, pb.GetBasis(i));
for (ULONG i = 0; i < 3; i++)
put_ivl(o, pb.GetInterval(i));
}
if (w2)
{
o.push_back((double)qb.GetPolarBoxState());
for (ULONG i = 0; i < 3; i++)
put_vec(o, *qb.GetBasis(i));
put_dom(o, qb.GetDomain());
}
}
break;
}
case S_STEP_INV:
{
SmExtent2d d = rd.dom();
SmPoint3d p = rd.vec();
double tol = rd.num();
bool has_guess = rd.flag();
SmPoint2d g = rd.uv();
SmPoint2d uv(0.0, 0.0);
SmLocationType loc = SM_LT_INTERIOR;
SmStatus stt = s->STEPInversion(d, p, tol, uv, loc, has_guess ? &g : NULL);
o.push_back(st(stt));
if (stt == SM_SUCCESS)
{
o.push_back(uv.x);
o.push_back(uv.y);
o.push_back((double)loc);
}
break;
}
case S_REVERSE:
o.push_back(st(s->Reverse((SmSurfParamType)rd.idx())));
break;
case S_SWAP:
o.push_back(st(s->SwapUV()));
break;
case S_TRANSFORM:
{
SmPoint3d org = rd.vec();
SmVector3d x = rd.vec();
SmVector3d y = rd.vec();
bool has_scale = rd.flag();
SmVector3d scale = rd.vec();
SmAxis2Placement ax(org, x, y);
o.push_back(st(s->Transform(ax, has_scale ? &scale : NULL)));
break;
}
case S_TRIM:
{
SmExtent2d d = rd.dom();
SmStatus stt = s->TrimWithDomain(d);
o.push_back(st(stt));
if (stt == SM_SUCCESS)
put_dom(o, d);
break;
}
case S_SPLIT:
{
double t = rd.num();
SmSurfParamType param = (SmSurfParamType)rd.idx();
SmSurface* l = NULL;
SmSurface* r = NULL;
SmStatus stt = s->SplitAt(ctx, t, param, l, r);
o.push_back(st(stt));
if (stt == SM_SUCCESS)
{
put_result(o, l);
put_result(o, r);
delete l;
delete r;
}
break;
}
case S_CREATE_OFFSET:
case S_EXTEND:
case S_EXTEND_DIR:
case S_COPY:
{
SmSurface* r = NULL;
SmStatus stt;
if (step == S_CREATE_OFFSET)
{
double d = rd.num();
stt = s->CreateOffsetSurface(ctx, d, 0.0, r);
}
else if (step == S_EXTEND)
{
double dist = rd.num();
SmContinuityType cont = (SmContinuityType)rd.idx();
stt = s->CreateExtendedSurface(ctx, dist, cont, r);
}
else if (step == S_EXTEND_DIR)
{
SmSurfParamType param = (SmSurfParamType)rd.idx();
double dist = rd.num();
SmContinuityType cont = (SmContinuityType)rd.idx();
stt = s->CreateExtendedSurface(ctx, param, dist, cont, r);
}
else
{
stt = s->Copy(ctx, r);
}
o.push_back(st(stt));
if (stt == SM_SUCCESS && r)
{
put_result(o, r);
delete r;
}
break;
}
case S_EQ:
{
SmStatus bst = SM_SUCCESS;
SmOffsetSurface* other = read_offset(rd, ctx, bst);
o.push_back(st(bst));
if (other)
{
o.push_back(flg(*s == *other));
o.push_back(flg(*s == *s));
delete other;
}
break;
}
case S_SET_DIST:
s->SetOffsetDistance(rd.num());
o.push_back(0.0);
break;
case S_SET_ALLOW:
s->SetAllowExtension(rd.bflag());
o.push_back(0.0);
break;
case S_IS_PLANAR:
{
double tol = rd.num();
SmPoint3d p(SM_UNDEF_DOUBLE, SM_UNDEF_DOUBLE, SM_UNDEF_DOUBLE);
SmVector3d n(SM_UNDEF_DOUBLE, SM_UNDEF_DOUBLE, SM_UNDEF_DOUBLE);
double dev = SM_UNDEF_DOUBLE;
SmBoolean r = s->IsPlanar(tol, &p, &n, &dev);
o.push_back(0.0);
o.push_back(flg(r));
put_vec(o, p);
put_vec(o, n);
o.push_back(dev);
break;
}
case S_IS_KIND_OF:
o.push_back(0.0);
o.push_back(flg(s->IsKindOf((SM_TYPE)rd.idx())));
break;
default: o.push_back((double)SC_ERR_ARG); break;
}
}
void run_script(Rd& rd, const SmContext& ctx, std::vector<double>& o)
{
SmStatus bst = SM_SUCCESS;
SmOffsetSurface* s = read_offset(rd, ctx, bst);
o.push_back(st(bst));
int64_t n_steps = rd.idx();
if (s == NULL)
{
// skip the steps (the record is the construction status only)
rd.at = -1;
return;
}
for (int64_t i = 0; i < n_steps; i++)
run_step(rd.idx(), rd, ctx, s, o);
delete s; // its base when owned, and its extended surface
}
void write_db(Rd& rd, const SmContext& ctx, std::vector<double>& o)
{
SmStatus bst = SM_SUCCESS;
SmOffsetSurface* s = read_offset(rd, ctx, bst);
bool binary = rd.flag();
ULONG version = (ULONG)rd.idx();
o.push_back(st(bst));
if (s == NULL)
return;
std::ostringstream oss;
oss.precision(17);
SmDatabaseIOStream db(binary ? SM_BINARY : SM_ASCII, &oss);
SmStatus stt = s->WriteToDB(db, version);
o.push_back(st(stt));
std::string bytes = oss.str();
for (char ch : bytes)
o.push_back((double)(unsigned char)ch);
delete s;
}
void read_db(Rd& rd, const SmContext& ctx, std::vector<double>& o)
{
bool binary = rd.flag();
ULONG version = (ULONG)rd.idx();
int64_t nb = rd.idx();
std::string bytes;
for (int64_t i = 0; i < nb; i++)
bytes.push_back((char)(unsigned char)rd.idx());
std::istringstream iss(bytes);
SmDatabaseIOStream db(binary ? SM_BINARY : SM_ASCII, &iss);
SmSurface* s = NULL;
SmStatus stt = SmOffsetSurface::ReadFromDB(SmOffsetSurface_TYPE, db, ctx, s, version);
o.push_back(st(stt));
if (stt == SM_SUCCESS && s)
put_state(o, (SmOffsetSurface*)s);
delete s;
}
} // namespace
SC_EXPORT int32_t sc_surface_offset_surface_eval(int32_t op, const double* in, int64_t n_in, double* out,
int64_t cap, int64_t* out_len)
{
if (in == NULL || n_in < 1 || out_len == NULL)
return SC_ERR_ARG;
SC_TRY
const SmContext& ctx = the_context();
Rd rd{in, 0};
std::vector<double> o;
switch (op)
{
case OP_SCRIPT: run_script(rd, ctx, o); break;
case OP_WRITE_DB: write_db(rd, ctx, o); break;
case OP_READ_DB: read_db(rd, ctx, o); break;
default: *out_len = 0; return SC_ERR_ARG;
}
// (a failed construction skips the rest of the input)
if (rd.at != n_in && rd.at != -1)
{
*out_len = 0;
return SC_ERR_ARG;
}
return sc_copy_out(o.data(), (int64_t)o.size(), out, cap, out_len);
SC_CATCH
}