Files
solvecraft/harness/cpp/shim/offset_offset_geometry_creation.cpp
T
Christian Svensson 65035a6ab6 offset.offset_geometry_creation: wire surface.offset_surface; HANG-26/27
- CreateOffsetSurface dispatches through Surface::create_offset_surface.
- SetAllowExtension(FALSE) on the analytic offset copies.
- The offset-surface rail builds the SmOffsetSurface and defers at
  SmCrvOnSurf (UNPORTED(curve.crv_on_surf), main's label).
- Hang guards renamed HANG-21/22 -> HANG-26/27 (main's #62 took 21/22);
  the HANG-27 bound is chosen for the fuzz time budget, not the
  termination boundary (~1e250).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBgcxze64BBVRNjGewaEuN
2026-10-18 01:42:56 +02:00

955 lines
34 KiB
C++

// SPDX-License-Identifier: Apache-2.0
// difftest shim for unit `offset.offset_geometry_creation`: SmOffsetGeometryCreation and
// SmLocalOperationGeometryCreation (source/SMLib/src/SmOffsetGeometryCreation.cpp) on two
// real SmBreps (the "original" and the "offset" B-rep of an SmOffsetExecutive).
//
// One entry point, sc_offset_offset_geometry_creation_eval(op, path, in, n_in, out, cap,
// out_len). Layouts are documented in
// crates/solidcore-ffi/src/units/offset_offset_geometry_creation.rs, which validates every
// input. The B-rep construction, traversal and export are those of
// harness/cpp/shim/sc_topology_fixture.h (two B-rep blocks, as the
// `boolean.topology_intersector_p1` shim).
//
// A RUN call makes an SmOffsetGeometryCreation (and, with a face/surface list, an
// SmLocalOperationGeometryCreation), an SmOffsetExecutive over the first B-rep whose
// flags, shell faces and original->offset edge-use relation (`m_vEUToEU`, a protected
// member reached through a derived class, filled with the pairs given) come from the
// input, locks a mark, and runs a script: fixture edits (store curves/surfaces, replace an
// edge's curve, set an edge use's UV trim curve, mark an edge use) and the unit's
// functions. Each op emits its code, a status (0 success, the SmStatus code, -2 when
// the shim refused the op because upstream would dereference null, or -6 when the script
// stops at an op the Rust side defers at, see ST_NOT_RUN) and its outputs; the
// objects the unit makes are emitted as geometry (`put_curve` of the fixture header,
// surfaces as their type, UV domain, a 3x3 point sample and, for a plain B-spline surface
// or a surface of revolution, their NURB), the B-rep edits as edge / edge-use state blocks.
//
// Before every op the executive's extended-surface cache (`m_vSurfExtSurf`) is seeded
// with every face surface of both B-reps and every store surface mapped to itself, so
// `SmOffsetExecutive::GetCreateExtendedSurface` returns the base surface unextended and
// `CreateExtendedSurface` never runs here (its loops do not return for a negative offset
// distance, HANG-16 of PR #175, nor for a closed surface short against the extension,
// HANG-17). The Rust side defers at `UNPORTED(offset.offset_executive_p1)` at the same
// call, so no output that follows a seeded lookup is compared.
#include "sc_topology_fixture.h"
#include <SmOffsetExecutive.h>
#include <SmOffsetGeometryCreation.h>
#include <SmRelation.h>
#include <SmSSIData.h>
#include <cstdlib>
#include <string>
#include <array>
namespace
{
// ---- private member access (pointer-to-member through explicit instantiation) -------------
// The executive with its protected relation and extended-surface cache reachable.
struct ExecAccess : SmOffsetExecutive
{
using SmOffsetExecutive::SmOffsetExecutive;
void relate(SmEdgeuse* a, SmEdgeuse* b) { m_vEUToEU.RelatePair(a, b); }
void seed_extended(SmSurface* s)
{
if (s != nullptr)
m_vSurfExtSurf.Insert(s, s);
}
};
enum OgOp : int32_t
{
OG_EXPORT = 0,
OG_RUN = 1,
};
// Script op codes.
enum OgScriptOp : int64_t
{
S_ADD_CURVE = 1,
S_ADD_SURFACE = 2,
S_SET_EDGE_CURVE = 3,
S_SET_EU_UV = 4,
S_MARK = 5,
S_GETTERS = 6,
S_FACEUSE_OFFSET = 7,
S_VERTEX_OFFSET = 8,
S_UPDATE_FACE_DOMAIN = 9,
S_REPLACE_EDGE_CURVES = 10,
S_TRIM_EXTENDED_EDGE = 11,
S_EXTEND_TRIM_EDGES = 12,
S_MATE_EDGES = 13,
S_HAVE_TANGENT_GAPS = 14,
S_EDGEUSE_OFFSET = 15,
S_LOCAL_FACEUSE_OFFSET = 16,
S_EDGE_STATE = 17,
S_SET_EDGE_TOL = 18,
};
// Handle kinds (`kind` of a `brep kind index` triple).
enum HKind : int64_t
{
H_VERTEX = 0,
H_EDGE = 1,
H_FACE = 2,
H_EDGE_CURVE = 3,
H_FACE_SURFACE = 4,
H_CURVE = 5, // the script's curve store
H_BREP = 6,
H_SURFACE = 7, // the script's surface store
H_NULL = 8,
H_EDGEUSE = 9,
H_FACEUSE = 10,
H_EU_UV = 11, // the UV trim curve stored on an edge use
};
struct Two
{
SmBrep* brep[2] = {nullptr, nullptr};
Graph g[2];
std::vector<SmBSplineCurve*> curves;
std::vector<SmSurface*> surfaces;
SmObject* resolve(int64_t b, int64_t kind, int64_t i) const
{
if (kind == H_NULL || i < 0)
return nullptr;
if (kind == H_BREP)
return (b == 0 || b == 1) ? (SmObject*)brep[b] : nullptr;
if (kind == H_CURVE)
return (i < (int64_t)curves.size()) ? (SmObject*)curves[(size_t)i] : nullptr;
if (kind == H_SURFACE)
return (i < (int64_t)surfaces.size()) ? (SmObject*)surfaces[(size_t)i] : nullptr;
if (b != 0 && b != 1)
return nullptr;
const Graph& gr = g[b];
auto at = [i](auto& v) -> SmTopology* { return (i >= 0 && i < (int64_t)v.size()) ? (SmTopology*)v[(size_t)i] : nullptr; };
switch (kind)
{
case H_VERTEX: return at(gr.vertices);
case H_EDGE: return at(gr.edges);
case H_FACE: return at(gr.faces);
case H_EDGEUSE: return at(gr.edgeuses);
case H_FACEUSE: return at(gr.faceuses);
case H_EDGE_CURVE:
{
SmEdge* e = (SmEdge*)at(gr.edges);
return e ? (SmObject*)e->GetCurve() : nullptr;
}
case H_FACE_SURFACE:
{
SmFace* f = (SmFace*)at(gr.faces);
return f ? (SmObject*)f->GetSurface() : nullptr;
}
case H_EU_UV:
{
SmEdgeuse* eu = (SmEdgeuse*)at(gr.edgeuses);
return eu ? (SmObject*)eu->GetUVTrimCurvePointer() : nullptr;
}
default: return nullptr;
}
}
};
const double ST_OK = 0.0;
// The op the Rust side defers at without running anything comparable, so the script
// stops here (the runner mirrors each predicate): a non-local FaceuseOffset of a face
// whose surface the export brings in as its B-spline part (an SmSurfOfRevolution's
// offset self-intersection takes 20 s to minutes, HANG-26), and an EdgeuseOffset with
// |offset distance| above MAX_EDGEUSE_OFFSET_DISTANCE (HANG-27).
const double ST_NOT_RUN = -6.0;
// HANG-27: SmCrvOnSurf::ApproximateCurve of an EdgeuseOffset rail grows with the
// offset distance (Mouse.smb: 1 s at 1e4, 10 s at 1e6, 28 s at 1e8, no return within
// 90 s at 1e250 and 1e300); the bound keeps every script under the fuzz's 20 s.
const double MAX_EDGEUSE_OFFSET_DISTANCE = 1.0e6;
bool eu_ok(SmEdgeuse* eu)
{
if (eu == nullptr || eu->GetEdge() == nullptr || eu->GetEdge()->GetCurve() == nullptr)
return false;
if (eu->GetVertexuse() == nullptr || eu->GetVertexuse()->GetVertex() == nullptr)
return false;
if (eu->GetMate() == nullptr || !eu->IsLoopEdgeuse())
return false;
if (eu->GetCWEdgeuse() == nullptr || eu->GetCCWEdgeuse() == nullptr)
return false;
if (eu->GetFaceuse() == nullptr || eu->GetFace() == nullptr || eu->GetFace()->GetSurface() == nullptr)
return false;
return true;
}
// Whether every edge use of the B-rep is a loop edge use with edge (with curve), vertex,
// mate, CW/CCW neighbours and a face with surface, and every edge has a vertex: what the
// topology-walking functions dereference without checking.
bool brep_ok(SmBrep* brep, Graph& g)
{
if (brep == nullptr)
return false;
for (SmEdgeuse* eu : g.edgeuses)
if (!eu_ok(eu))
return false;
for (SmEdge* e : g.edges)
if (e->GetVertex() == nullptr || e->GetCurve() == nullptr)
return false;
return true;
}
SmObject* rd_h(Rd& rd, const Two& t)
{
int64_t b = rd.idx();
int64_t kind = rd.idx();
int64_t i = rd.idx();
return t.resolve(b, kind, i);
}
void put_srf(std::vector<double>& o, const SmSurface* s)
{
if (s == nullptr)
{
o.push_back(-1.0);
return;
}
o.push_back((double)s->GetType());
SmExtent2d d = s->GetNaturalUVDomain();
o.push_back(d.GetMin().x);
o.push_back(d.GetMax().x);
o.push_back(d.GetMin().y);
o.push_back(d.GetMax().y);
for (int i = 0; i <= 2; i++)
for (int j = 0; j <= 2; j++)
{
SmPoint2d uv(d.GetMin().x + (d.GetMax().x - d.GetMin().x) * ((double)i / 2.0),
d.GetMin().y + (d.GetMax().y - d.GetMin().y) * ((double)j / 2.0));
SmPoint3d p;
SmStatus st_e = s->EvaluatePoint(uv, p);
if (st_e == SM_SUCCESS)
{
o.push_back(0.0);
put_vec(o, p);
}
else
o.push_back(st(st_e));
}
// the NURB of a plain B-spline surface or a surface of revolution (the kinds this
// unit builds whose sample does not pin degree, knots or net): `has_nurb [n m p q r s
// net (x y z w)* u_knots* v_knots*]`
long ty = s->GetType();
if (ty == SmBSplineSurface_TYPE || ty == SmSurfOfRevolution_TYPE)
{
const gw_SURFACE* sur = ((const SmBSplineSurface*)s)->GetGwNurbPointer();
o.push_back(sur != nullptr ? 1.0 : 0.0);
if (sur == nullptr)
return;
gw_INDEX n = sur->net->n;
gw_INDEX m = sur->net->m;
gw_INDEX r = sur->knu->m;
gw_INDEX ss = sur->knv->m;
o.push_back((double)n);
o.push_back((double)m);
o.push_back((double)sur->p);
o.push_back((double)sur->q);
o.push_back((double)r);
o.push_back((double)ss);
for (gw_INDEX i = 0; i <= n; i++)
for (gw_INDEX j = 0; j <= m; j++)
{
o.push_back(sur->net->Pw[i][j].x);
o.push_back(sur->net->Pw[i][j].y);
o.push_back(sur->net->Pw[i][j].z);
o.push_back(sur->net->Pw[i][j].w);
}
for (gw_INDEX i = 0; i <= r; i++)
o.push_back(sur->knu->U[i]);
for (gw_INDEX i = 0; i <= ss; i++)
o.push_back(sur->knv->U[i]);
}
}
void put_orients(std::vector<double>& o, const SmTArray<SmOrientType>& v)
{
o.push_back((double)v.GetSize());
for (ULONG k = 0; k < v.GetSize(); k++)
o.push_back((double)v[k]);
}
void put_curves(std::vector<double>& o, const SmTArray<SmCurve*>& v)
{
o.push_back((double)v.GetSize());
for (ULONG k = 0; k < v.GetSize(); k++)
put_curve(o, v[k]);
}
// `curve t0 t1 tolerance vertex_point other_vertex_point n_edgeuses uv_trim_curve*`.
void put_edge_state(std::vector<double>& o, SmEdge* e)
{
put_curve(o, e->GetCurve());
SmExtent1d ivl = e->GetInterval();
o.push_back(ivl.GetMin());
o.push_back(ivl.GetMax());
o.push_back((double)e->GetTolerance());
SmVertex* v = e->GetVertex();
if (v)
{
o.push_back(1.0);
put_vec(o, v->GetPoint());
SmVertex* w = e->GetOtherVertex(v);
if (w)
{
o.push_back(1.0);
put_vec(o, w->GetPoint());
}
else
o.push_back(0.0);
}
else
o.push_back(0.0);
SM_PTR_ARRAY(eus, SmEdgeuse, 16);
e->GetEdgeuses(eus);
o.push_back((double)eus.GetSize());
for (ULONG k = 0; k < eus.GetSize(); k++)
put_curve(o, eus[k]->GetUVTrimCurve());
}
template <class T> void delete_curves(SmTArray<T*>& v)
{
for (ULONG k = 0; k < v.GetSize(); k++)
delete v[k];
v.ReSet();
}
// Whether `ReplaceEdgeCurves(eu, c3, NULL, orig)` would reverse the curves (and dereference
// the null UV curve): the shape test of the function, replicated.
bool would_reverse(SmEdgeuse* eu, SmCurve* c3, SmEdgeuse* orig)
{
SmEdge* edge = eu->GetEdge();
SmCurve* euc = edge->GetCurve();
SmExtent1d ivl = edge->GetInterval();
double tol = edge->GetTolerance();
SmPoint3d es, ee, cs, ce;
euc->EvaluatePoint(ivl.GetMin(), es);
euc->EvaluatePoint(ivl.GetMax(), ee);
SmExtent1d civl = c3->GetNaturalInterval();
c3->EvaluatePoint(civl.GetMin(), cs);
c3->EvaluatePoint(civl.GetMax(), ce);
if (es.DistanceBetween(cs) < tol && ee.DistanceBetween(ce) < tol)
return false;
if (es.DistanceBetween(ce) < tol && ee.DistanceBetween(cs) < tol)
return false;
SmVector3d pv[2], pv2[2];
if (orig == nullptr)
euc->Evaluate(ivl.Evaluate(0.5), 1, TRUE, pv);
else
{
SmEdge* oe = orig->GetEdge();
oe->GetCurve()->Evaluate(oe->GetInterval().Evaluate(0.5), 1, TRUE, pv);
}
c3->Evaluate(civl.Evaluate(0.5), 1, TRUE, pv2);
return pv[1].Dot(pv2[1]) < 0.0;
}
bool build_two(Rd& rd, const char* path, Two& t, std::vector<double>& o)
{
std::string p(path);
std::string pa = p, pb;
size_t nl = p.find('\n');
if (nl != std::string::npos)
{
pa = p.substr(0, nl);
pb = p.substr(nl + 1);
}
SmStatus st_a;
t.brep[0] = build(rd, pa.c_str(), st_a);
if (t.brep[0] == nullptr)
{
o.push_back(-1.0);
o.push_back(st(st_a));
return false;
}
bool has_b = rd.flag();
if (has_b)
{
SmStatus st_b;
t.brep[1] = build(rd, pb.c_str(), st_b);
if (t.brep[1] == nullptr)
{
o.push_back(-3.0);
o.push_back(st(st_b));
return false;
}
}
walk(t.brep[0], t.g[0]);
if (t.brep[1])
walk(t.brep[1], t.g[1]);
return true;
}
void run_script(Rd& rd, Two& t, std::vector<double>& o)
{
const SmContext& ctx = the_context();
double dist = rd.num();
double approx = rd.num();
bool make_analytics = rd.flag();
SmOffsetGeometryCreation geom(dist, approx);
geom.SetMakeAnalytics(make_analytics ? TRUE : FALSE);
// the local-operation creation (its lists are the face/surface pairs given)
// The listed surfaces are store surfaces (the FFI refuses a face's own: the local
// creation owns them), named by handle; the store is filled by the script's ops, so
// the handles are resolved again before each local op (upstream copies the lists at
// construction; the local creation is rebuilt then). Its m_bMakeAnalytics stays
// FALSE, as the constructor leaves it (SetMakeAnalytics is never called on it), so
// FaceuseOffset returns the listed surface as is.
int64_t n_local = rd.idx();
SmTArray<SmFace*> local_faces(ctx);
SmTArray<SmSurface*> local_surfaces(ctx);
std::vector<std::array<int64_t, 3>> local_srf;
for (int64_t k = 0; k < n_local; k++)
{
SmFace* f = (SmFace*)rd_h(rd, t);
std::array<int64_t, 3> h;
h[0] = rd.idx();
h[1] = rd.idx();
h[2] = rd.idx();
local_faces.Add(f);
local_surfaces.Add((SmSurface*)t.resolve(h[0], h[1], h[2]));
local_srf.push_back(h);
}
SmLocalOperationGeometryCreation local(dist, approx, local_faces, local_surfaces);
// the executive
ExecAccess exec(ctx, SM_OO_SOLID_OFFSET, geom, t.brep[0]);
exec.SetInset(rd.bflag());
exec.SetExtendConvexEdges(rd.bflag());
int64_t n_shell = rd.idx();
if (n_shell > 0)
{
SmTArray<SmFace*> shell(ctx);
for (int64_t k = 0; k < n_shell; k++)
shell.Add((SmFace*)rd_h(rd, t));
exec.SetShellFaces(shell);
}
int64_t n_pairs = rd.idx();
for (int64_t k = 0; k < n_pairs; k++)
{
SmEdgeuse* a = (SmEdgeuse*)rd_h(rd, t);
SmEdgeuse* b = (SmEdgeuse*)rd_h(rd, t);
exec.relate(a, b);
}
// the mark the trim uses
SmNewMarkAndLock mark_lock(&ctx);
SmMarkType mark = mark_lock.GetMarkType();
bool ok[2] = {brep_ok(t.brep[0], t.g[0]), brep_ok(t.brep[1], t.g[1])};
bool all_ok = ok[0] && (t.brep[1] == nullptr || ok[1]);
int64_t n_ops = rd.idx();
for (int64_t k = 0; k < n_ops; k++)
{
int64_t op = rd.idx();
o.push_back((double)op);
// seed the extended-surface cache with the surfaces as they stand now (see the
// header): every face surface of both B-reps and every store surface
for (int b = 0; b < 2; b++)
{
if (t.brep[b] == nullptr)
continue;
for (size_t q = 0; q < t.g[b].faces.size(); q++)
if (t.g[b].faces[q] != nullptr)
exec.seed_extended(t.g[b].faces[q]->GetSurface());
}
for (size_t q = 0; q < t.surfaces.size(); q++)
exec.seed_extended(t.surfaces[q]);
switch (op)
{
case S_ADD_CURVE:
{
SmBSplineCurve* c = read_curve(rd, ctx);
t.curves.push_back(c);
o.push_back(ST_OK);
o.push_back((double)(t.curves.size() - 1));
break;
}
case S_ADD_SURFACE:
{
SmSurface* s = read_surface(rd, ctx);
t.surfaces.push_back(s);
o.push_back(ST_OK);
o.push_back((double)(t.surfaces.size() - 1));
break;
}
case S_SET_EDGE_CURVE:
{
SmEdge* e = (SmEdge*)rd_h(rd, t);
SmBSplineCurve* c = (SmBSplineCurve*)rd_h(rd, t);
if (e == nullptr || c == nullptr)
{
o.push_back(ST_NULL_DEREF);
break;
}
SmBSplineCurve* copy = new (ctx) SmBSplineCurve(*c);
e->SetCurve(copy, TRUE);
copy->SetOwner(e);
e->SetInterval(copy->GetNaturalInterval());
o.push_back(ST_OK);
put_edge_state(o, e);
break;
}
case S_SET_EU_UV:
{
SmEdgeuse* eu = (SmEdgeuse*)rd_h(rd, t);
SmBSplineCurve* c = (SmBSplineCurve*)rd_h(rd, t);
double max_dist = rd.num();
if (eu == nullptr)
{
o.push_back(ST_NULL_DEREF);
break;
}
SmBSplineCurve* copy = c ? new (ctx) SmBSplineCurve(*c) : nullptr;
eu->SetUVTrimCurve(copy, max_dist, TRUE);
o.push_back(ST_OK);
put_curve(o, eu->GetUVTrimCurve());
break;
}
case S_MARK:
{
SmEdgeuse* eu = (SmEdgeuse*)rd_h(rd, t);
if (eu == nullptr)
{
o.push_back(ST_NULL_DEREF);
break;
}
eu->Mark(mark);
o.push_back(ST_OK);
o.push_back(flg(eu->IsMarked(mark)));
break;
}
case S_GETTERS:
{
o.push_back(ST_OK);
o.push_back(geom.GetThisApproxTol3d());
o.push_back(geom.GetTangencyTolRadians());
o.push_back(geom.GetTangencyTolDegrees());
o.push_back(geom.GetOffsetDistance());
o.push_back(local.GetThisApproxTol3d());
o.push_back(local.GetTangencyTolDegrees());
o.push_back(local.GetOffsetDistance());
o.push_back(exec.GetTangencyTolDegrees());
o.push_back(flg(exec.GetInset()));
o.push_back(flg(exec.GetExtendConvexEdges()));
SmTArray<SmFace*> sh(ctx);
exec.GetShellFaces(sh);
o.push_back((double)sh.GetSize());
break;
}
case S_FACEUSE_OFFSET:
case S_LOCAL_FACEUSE_OFFSET:
{
SmFaceuse* fu = (SmFaceuse*)rd_h(rd, t);
bool skip = rd.flag();
if (fu == nullptr || fu->GetFace() == nullptr || fu->GetFace()->GetSurface() == nullptr)
{
o.push_back(ST_NULL_DEREF);
break;
}
if (op == S_FACEUSE_OFFSET)
{
long ty = fu->GetFace()->GetSurface()->GetType();
if (ty != SmPlane_TYPE && ty != SmBSplineSurface_TYPE)
{
o.push_back(ST_NOT_RUN); // the Rust defers here (analytic export)
return;
}
}
ULONG lidx = 0;
bool lfound = false;
if (op == S_LOCAL_FACEUSE_OFFSET)
{
// the listed surfaces, as the store stands now
for (size_t k = 0; k < local_srf.size(); k++)
local_surfaces[(ULONG)k] = (SmSurface*)t.resolve(local_srf[k][0], local_srf[k][1], local_srf[k][2]);
// a listed face without surface (not added yet, or deleted) is dereferenced
// upstream
lfound = local_faces.FindElement(fu->GetFace(), lidx) ? true : false;
if (lfound && local_surfaces[lidx] == nullptr)
{
o.push_back(ST_NULL_DEREF);
break;
}
}
SmLocalOperationGeometryCreation local_now(dist, approx, local_faces, local_surfaces);
SmSurface* ns = nullptr;
SmSSIData ssi;
SmStatus s = (op == S_LOCAL_FACEUSE_OFFSET)
? local_now.FaceuseOffset(ctx, fu, skip ? TRUE : FALSE, ns, ssi)
: geom.FaceuseOffset(ctx, fu, skip ? TRUE : FALSE, ns, ssi);
o.push_back(st(s));
if (s == SM_SUCCESS) // the out-parameters: only after success
{
o.push_back(ns ? 1.0 : 0.0);
if (ns)
put_srf(o, ns);
o.push_back((double)ssi.m_v3DCurves.GetSize());
for (ULONG j = 0; j < ssi.m_v3DCurves.GetSize(); j++)
{
put_curve(o, ssi.m_v3DCurves[j]);
put_curve(o, j < ssi.m_vUVCurves1.GetSize() ? ssi.m_vUVCurves1[j] : nullptr);
put_curve(o, j < ssi.m_vUVCurves2.GetSize() ? ssi.m_vUVCurves2[j] : nullptr);
o.push_back(j < ssi.m_vCurveTypes.GetSize() ? (double)ssi.m_vCurveTypes[j] : -1.0);
o.push_back(j < ssi.m_vDeviations.GetSize() ? ssi.m_vDeviations[j] : -1.0);
}
}
if (op == S_LOCAL_FACEUSE_OFFSET && ns != nullptr)
{
// the listed store surface, returned as is (owned by the store already)
bool listed = false;
for (size_t q = 0; q < t.surfaces.size(); q++)
if (t.surfaces[q] == ns)
listed = true;
if (!listed)
t.surfaces.push_back(ns);
}
else
delete ns;
delete_curves(ssi.m_v3DCurves);
delete_curves(ssi.m_vUVCurves1);
delete_curves(ssi.m_vUVCurves2);
break;
}
case S_VERTEX_OFFSET:
{
SmVertex* v = (SmVertex*)rd_h(rd, t);
int64_t n = rd.idx();
SmTArray<SmEdge*> edges(ctx);
bool bad = false;
for (int64_t j = 0; j < n; j++)
{
SmEdge* e = (SmEdge*)rd_h(rd, t);
if (e == nullptr || e->GetCurve() == nullptr)
bad = true;
edges.Add(e);
}
if (!bad && n == 1)
{
SM_PTR_ARRAY(faces, SmFace, 8);
edges[0]->GetFaces(faces);
if (faces.GetSize() == 0 || faces[0]->GetSurface() == nullptr ||
!edges[0]->GetCurve()->IsKindOf(SmBSplineCurve_TYPE))
bad = true;
}
if (bad || !all_ok)
{
o.push_back(ST_NULL_DEREF);
break;
}
SmTArray<SmCurve*> curves(ctx);
SmTArray<SmOrientType> orients(ctx);
SmSurface* ns = nullptr;
SmStatus s = geom.VertexOffset(ctx, v, edges, curves, orients, ns);
o.push_back(st(s));
if (s == SM_SUCCESS) // the out-parameters: only after success
{
put_curves(o, curves);
put_orients(o, orients);
o.push_back(ns ? 1.0 : 0.0);
if (ns)
put_srf(o, ns);
}
delete_curves(curves);
delete ns;
break;
}
case S_UPDATE_FACE_DOMAIN:
{
SmFace* f = (SmFace*)rd_h(rd, t);
SmSurface* ext = (SmSurface*)rd_h(rd, t);
if (f == nullptr || f->GetSurface() == nullptr || ext == nullptr)
{
o.push_back(ST_NULL_DEREF);
break;
}
SmStatus s = geom.UpdateFaceSurfaceDomain(f, ext);
o.push_back(st(s));
SmExtent2d d = f->GetUVDomain();
o.push_back(d.GetMin().x);
o.push_back(d.GetMax().x);
o.push_back(d.GetMin().y);
o.push_back(d.GetMax().y);
put_srf(o, f->GetSurface());
break;
}
case S_REPLACE_EDGE_CURVES:
{
SmEdgeuse* eu = (SmEdgeuse*)rd_h(rd, t);
SmBSplineCurve* c3 = (SmBSplineCurve*)rd_h(rd, t);
SmBSplineCurve* uv = (SmBSplineCurve*)rd_h(rd, t);
SmEdgeuse* orig = (SmEdgeuse*)rd_h(rd, t);
bool bad = eu == nullptr || eu->GetEdge() == nullptr;
if (!bad && c3 != nullptr)
{
SmEdge* e = eu->GetEdge();
bad = e->GetCurve() == nullptr || eu->GetVertexuse() == nullptr ||
eu->GetVertexuse()->GetVertex() == nullptr || eu->GetMate() == nullptr ||
eu->GetMate()->GetVertexuse() == nullptr || eu->GetMate()->GetVertexuse()->GetVertex() == nullptr;
if (!bad && orig != nullptr)
bad = orig->GetEdge() == nullptr || orig->GetEdge()->GetCurve() == nullptr;
if (!bad && uv == nullptr && would_reverse(eu, c3, orig))
bad = true;
}
if (bad)
{
o.push_back(ST_NULL_DEREF);
break;
}
SmCurve* c3c = c3 ? new (ctx) SmBSplineCurve(*c3) : nullptr;
SmBSplineCurve* uvc = uv ? new (ctx) SmBSplineCurve(*uv) : nullptr;
SmBoolean del3 = FALSE, deluv = FALSE;
SmStatus s = geom.ReplaceEdgeCurves(eu, c3c, uvc, orig, del3, deluv);
o.push_back(st(s));
if (s == SM_SUCCESS) // the out-parameters: only after success
{
o.push_back(flg(del3));
o.push_back(flg(deluv));
}
put_edge_state(o, eu->GetEdge()); // the edge's state: after every status
if (del3)
delete c3c;
if (deluv)
delete uvc;
break;
}
case S_TRIM_EXTENDED_EDGE:
{
SmEdgeuse* base = (SmEdgeuse*)rd_h(rd, t);
SmEdgeuse* other_base = (SmEdgeuse*)rd_h(rd, t);
SmEdgeuse* off = (SmEdgeuse*)rd_h(rd, t);
SmEdgeuse* other_off = (SmEdgeuse*)rd_h(rd, t);
SmCurve* c3 = (SmCurve*)rd_h(rd, t);
SmBSplineCurve* uv_base = (SmBSplineCurve*)rd_h(rd, t);
SmBSplineCurve* uv_other = (SmBSplineCurve*)rd_h(rd, t);
if (!all_ok || base == nullptr || c3 == nullptr)
{
o.push_back(ST_NULL_DEREF);
break;
}
SmBoolean good = FALSE;
SmExtent1d ivl;
SmStatus s = geom.TrimExtendedEdge(ctx, &exec, base, other_base, off, other_off, c3, uv_base, uv_other, good,
ivl, mark);
o.push_back(st(s));
if (s == SM_SUCCESS) // the out-parameters: only after success
{
o.push_back(flg(good));
if (good)
{
o.push_back(ivl.GetMin());
o.push_back(ivl.GetMax());
}
}
break;
}
case S_EXTEND_TRIM_EDGES:
{
SmEdgeuse* eu = (SmEdgeuse*)rd_h(rd, t);
SmEdgeuse* other = (SmEdgeuse*)rd_h(rd, t);
SmEdgeuse* off = (SmEdgeuse*)rd_h(rd, t);
SmEdgeuse* other_off = (SmEdgeuse*)rd_h(rd, t);
bool bad = !all_ok || eu == nullptr || off == nullptr || eu->GetUVTrimCurve() == nullptr;
if (!bad && !eu->GetEdge()->IsLamina() && eu->GetRadial() != nullptr &&
eu->GetRadial()->GetUVTrimCurve() == nullptr)
bad = true;
if (!bad && !eu->GetEdge()->IsLamina() && eu->GetRadial() == nullptr)
bad = true;
if (bad)
{
o.push_back(ST_NULL_DEREF);
break;
}
SmStatus s = geom.ExtendTrimEdges(ctx, &exec, eu, other, off, other_off, mark);
o.push_back(st(s));
put_edge_state(o, off->GetEdge());
o.push_back(other_off ? 1.0 : 0.0);
if (other_off)
put_edge_state(o, other_off->GetEdge());
break;
}
case S_MATE_EDGES:
{
SmEdgeuse* eu = (SmEdgeuse*)rd_h(rd, t);
SmEdgeuse* other = (SmEdgeuse*)rd_h(rd, t);
SmEdgeuse* off = (SmEdgeuse*)rd_h(rd, t);
SmEdgeuse* other_off = (SmEdgeuse*)rd_h(rd, t);
if (!all_ok)
{
o.push_back(ST_NULL_DEREF);
break;
}
SmStatus s = geom.MateEdges(ctx, &exec, eu, other, off, other_off, mark);
o.push_back(st(s));
o.push_back(off ? 1.0 : 0.0);
if (off)
put_edge_state(o, off->GetEdge());
o.push_back(other_off ? 1.0 : 0.0);
if (other_off)
put_edge_state(o, other_off->GetEdge());
break;
}
case S_HAVE_TANGENT_GAPS:
{
int64_t n = rd.idx();
SmEdgeuse* off = (SmEdgeuse*)rd_h(rd, t);
SmEdgeuse* other_off = (SmEdgeuse*)rd_h(rd, t);
if (!all_ok || off == nullptr || other_off == nullptr)
{
o.push_back(ST_NULL_DEREF);
break;
}
o.push_back(ST_OK);
o.push_back(flg(geom.HaveTangentGaps((ULONG)n, off, other_off)));
break;
}
case S_EDGEUSE_OFFSET:
{
SmEdgeuse* orig = (SmEdgeuse*)rd_h(rd, t);
SmEdgeuse* other_orig = (SmEdgeuse*)rd_h(rd, t);
SmEdgeuse* off = (SmEdgeuse*)rd_h(rd, t);
SmEdgeuse* other_off = (SmEdgeuse*)rd_h(rd, t);
if (!all_ok || orig == nullptr)
{
o.push_back(ST_NULL_DEREF);
break;
}
if (fabs(dist) > MAX_EDGEUSE_OFFSET_DISTANCE)
{
o.push_back(ST_NOT_RUN); // HANG-27: the Rust defers here
return;
}
if (exec.GetExtendConvexEdges())
{
// a tangent or lamina edge goes to ExtendTrimEdges, which dereferences the
// offset edge use (null is refused) and its UV trim curves
bool lamina = orig->GetEdge()->IsLamina();
bool tangent = orig->IsTangentSector(geom.GetTangencyTolDegrees());
if (tangent || lamina)
{
bool bad = off == nullptr || orig->GetUVTrimCurve() == nullptr;
if (!bad && !lamina && (orig->GetRadial() == nullptr || orig->GetRadial()->GetUVTrimCurve() == nullptr))
bad = true;
if (bad)
{
o.push_back(ST_NULL_DEREF);
break;
}
}
}
SmSurface* ns = nullptr;
SmTArray<SmCurve*> curves(ctx);
SmTArray<SmOrientType> orients(ctx);
SmSSIData ssi;
SmStatus s = geom.EdgeuseOffset(ctx, &exec, orig, other_orig, off, other_off, ns, curves, orients, ssi, mark);
o.push_back(st(s));
if (s == SM_SUCCESS) // the out-parameters: only after success
{
o.push_back(ns ? 1.0 : 0.0);
if (ns)
put_srf(o, ns);
put_curves(o, curves);
put_orients(o, orients);
o.push_back((double)ssi.m_v3DCurves.GetSize());
}
o.push_back(off ? 1.0 : 0.0); // the edges' states: after every status
if (off)
put_edge_state(o, off->GetEdge());
o.push_back(other_off ? 1.0 : 0.0);
if (other_off)
put_edge_state(o, other_off->GetEdge());
delete_curves(curves);
delete_curves(ssi.m_v3DCurves);
delete_curves(ssi.m_vUVCurves1);
delete_curves(ssi.m_vUVCurves2);
delete ns;
break;
}
case S_EDGE_STATE:
{
SmEdge* e = (SmEdge*)rd_h(rd, t);
if (e == nullptr)
{
o.push_back(ST_NULL_DEREF);
break;
}
o.push_back(ST_OK);
put_edge_state(o, e);
break;
}
case S_SET_EDGE_TOL:
{
SmEdge* e = (SmEdge*)rd_h(rd, t);
double tol = rd.num();
if (e == nullptr)
{
o.push_back(ST_NULL_DEREF);
break;
}
e->SetTolerance(tol, FALSE, FALSE);
o.push_back(ST_OK);
o.push_back((double)e->GetTolerance());
break;
}
default: o.push_back(-9.0); return;
}
}
}
void run_og(int32_t op, Rd& rd, const char* path, std::vector<double>& o)
{
Two t;
struct Clean
{
Two& t;
~Clean()
{
for (SmBSplineCurve* c : t.curves)
delete c;
for (SmSurface* s : t.surfaces)
delete s;
delete t.brep[0];
delete t.brep[1];
}
} clean{t};
if (!build_two(rd, path, t, o))
return;
o.push_back(0.0);
if (op == OG_EXPORT)
{
for (int b = 0; b < 2; b++)
{
if (t.brep[b] == nullptr)
{
o.push_back(0.0);
continue;
}
std::vector<double> ex;
ex.push_back(0.0);
export_graph(t.brep[b], t.g[b], ex);
o.push_back((double)ex.size());
o.insert(o.end(), ex.begin(), ex.end());
}
return;
}
run_script(rd, t, o);
}
} // namespace
SC_EXPORT int32_t sc_offset_offset_geometry_creation_eval(int32_t op, const char* path, 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 || path == NULL)
return SC_ERR_ARG;
SC_TRY
Rd rd{in, 0};
std::vector<double> o;
run_og(op, rd, path, o);
return sc_copy_out(o.data(), (int64_t)o.size(), out, cap, out_len);
SC_CATCH
}