Files
solvecraft/harness/cpp/shim/boolean_api_brep.cpp
T
Brett Lawson 593a168088 Merge origin/main into the sweep attributes branch
Main took boolean.api_brep op 18 for TessellateBoundaries; the attributed
sweeps move to ops 19 / 20.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MNXLXj2sJf943y2dDF36Vy
2026-10-30 06:14:49 +01:00

1150 lines
38 KiB
C++

// SPDX-License-Identifier: Apache-2.0
// difftest shim for unit `boolean.api_brep`: the boolean entry points of
// source/SM_API/src/SmApiBrep.cpp (SmApiBoolean, SmApiBooleanUnion, SmApiBooleanDifference,
// SmApiBooleanIntersection, SmApiBooleanMerge, SmApiMergeBreps, SmApiBooleanWithOptions,
// SmApiBooleanLists, SmApiBooleanTrees, SmApiBooleanWithCurves, SmApiBoolean2d,
// SmApiNonManifoldBoolean, SmApiPiecewiseMerge) and its non-manifold sweeps of a B-rep
// (SmApiNonManifoldSweep, SmApiNonManifoldRotationalSweep), and SmApiTessellateBoundaries of
// an operand, called exactly as a caller of the API would.
//
// One entry point, sc_boolean_api_brep_run(in, n_in, out, cap, out_len). `in` is
// `op args...`; layouts are documented in crates/solidcore-ffi/src/units/boolean_api_brep.rs,
// which validates every input so upstream only touches valid memory. Every read is bounds
// checked here as well (a malformed program is SC_ERR_ARG). The whole program is parsed before
// any upstream object is built. Operand B-reps are boxes (SmApiCreateBox), spheres
// (SmApiCreateSphere), sheets (SmApiCreatePlane), planar disks (SmApiCreatePlanarCircle),
// tori (SmApiCreateTorus), an operand moved
// by SmApiRotate / SmApiTranslate steps, a skin solid (SmApiCreateSkinPrimitive of NURB
// curves), cylinders (SmApiCreateCylinder) or the SmApiBoolean result of two operands; a failing
// constructor or operand Boolean is SC_ERR_ARG, except a failing SmApiCreateTorus, whose
// status becomes the call's (record `2`) so its radius guard is compared. `out` receives the
// API status (0 = SM_SUCCESS, else the SmStatus) and, on success, the op's outputs.
//
// Ownership follows source/SM_API/inc/SmApiBrep.h: a rejected or failed two-B-rep call leaves
// both operands with the caller (deleted here); on success the result is deleted and the second
// operand is deleted here only when upstream keeps it. OP_ATTRIBUTED tags every operand face
// and edge with an SmLongAttribute before SmApiBoolean and records the attributes the result's
// faces and edges carry (the Rust side tags its own operands the same way). A failed N-ary call after validation
// may have consumed some operands, so those are leaked rather than risk a double delete.
#include "sc_shim.h"
#include "StdAfx.h"
#include <SmApiBrep.h>
#include <SmApiGeneral.h>
#include <SmApiPrimitives.h>
#include <SmApiQueries.h>
#include <SmAttribute.h>
#include <SmTopology.h>
#include <SmBSplineCurve.h>
#include <SmBrep.h>
#include <SmContext.h>
#include <SmEdge.h>
#include <SmFace.h>
#include <SmFaceuse.h>
#include <SmPrimitiveCreation.h>
#include <SmVertex.h>
#include <SmRegion.h>
#include <SmShell.h>
#include <SmSurface.h>
#include <SmTArray.h>
#include <SmVector3d.h>
#include <nurbs.h>
#include <algorithm>
#include <stdexcept>
#include <vector>
#include "sc_sm.h"
using sc::flg;
using sc::st;
namespace
{
// Ops (keep in sync with crates/solidcore-ffi/src/units/boolean_api_brep.rs).
enum : int32_t
{
OP_BOOLEAN = 1,
OP_UNION = 2,
OP_DIFFERENCE = 3,
OP_INTERSECTION = 4,
OP_MERGE = 5,
OP_WITH_OPTIONS = 6,
OP_MERGE_BREPS = 7,
OP_LISTS = 8,
OP_TREES = 9,
OP_MEASURED = 10,
OP_WITH_CURVES = 11,
OP_BOOLEAN_2D = 12,
OP_NON_MANIFOLD = 13,
OP_PIECEWISE = 14,
OP_NM_SWEEP = 15,
OP_NM_ROTATIONAL_SWEEP = 16,
OP_ATTRIBUTED = 17,
OP_TESSELLATE_BOUNDARIES = 18,
OP_ATTRIBUTED_NM_SWEEP = 19, // `behavior` then OP_NM_SWEEP's program
OP_ATTRIBUTED_NM_ROTATIONAL_SWEEP = 20, // `behavior` then OP_NM_ROTATIONAL_SWEEP's
};
// Most entries of one non-manifold sweep selection array (MAX_SELECTION in the Rust module).
const int64_t MAX_SELECTION = 16;
// Operand kinds.
enum : int32_t
{
BK_BOX = 1, // SmApiCreateBox(origin, length, width, height)
BK_SPHERE = 2, // SmApiCreateSphere(center, radius)
BK_PLANE = 3, // SmApiCreatePlane(origin, x, y)
BK_BOOLEAN = 4, // SmApiBoolean(a, b, operation)'s result
BK_TORUS = 5, // SmApiCreateTorus(center, major, minor)
BK_MOVED = 6, // an operand moved by SmApiRotate / SmApiTranslate steps
BK_SKIN = 7, // SmApiCreateSkinPrimitive(counts, NURB curves, degree, cap)
BK_DISK = 8, // SmApiCreatePlanarCircle(origin, radius)
BK_CYLINDER = 9, // SmApiCreateCylinder(origin, radius, height), the SmBrep*& overload
};
// Most curves (and profiles) of one BK_SKIN operand (MAX_SKIN_CURVES in the Rust module),
// and most control points of one of its curves (MAX_SKIN_POINTS).
const int64_t MAX_SKIN_CURVES = 16;
const int64_t MAX_SKIN_POINTS = 512;
// Steps of a BK_MOVED operand.
enum : int32_t
{
STEP_ROTATE = 0, // SmApiRotate(brep, point, axis, angle_deg)
STEP_TRANSLATE = 1, // SmApiTranslate(brep, vector)
};
// Most steps of one BK_MOVED operand (MAX_STEPS in the Rust module).
const int64_t MAX_STEPS = 8;
// Deepest operand block (MAX_DEPTH in the Rust module).
const int MAX_DEPTH = 4;
struct Bad : std::runtime_error
{
Bad() : std::runtime_error("bad program") {}
};
// A failed SmApiCreateTorus: its status becomes the call's, with the record `2`.
struct TorusFailed
{
double status;
};
struct Rd
{
const double* in;
int64_t n;
int64_t at;
double num()
{
if (at >= n)
throw Bad();
return in[at++];
}
int64_t idx(int64_t lo, int64_t hi)
{
double d = num();
if (!(d >= (double)lo && d <= (double)hi) || d != (double)(int64_t)d)
throw Bad();
return (int64_t)d;
}
bool flag() { return idx(0, 1) != 0; }
};
struct BrepSpec
{
int32_t kind = BK_BOX;
double d[6] = {0, 0, 0, 0, 0, 0};
std::vector<BrepSpec> sub; // BK_BOOLEAN: a, b; BK_MOVED: the operand
std::vector<double> steps; // BK_MOVED: per step its kind then 7 (rotate) or 3 numbers
// BK_SKIN: profile counts, then per curve `p n+1 (n+1)*4 points (n+p+2) knots`, and in
// d[0] d[1] the loft degree and cap flag
std::vector<double> counts, curves;
};
BrepSpec read_spec(Rd& rd, int depth = 0)
{
if (depth >= MAX_DEPTH)
throw Bad();
BrepSpec s;
s.kind = (int32_t)rd.idx(1, 9);
int k = 0;
switch (s.kind)
{
case BK_BOX: k = 6; break;
case BK_SPHERE: k = 4; break;
case BK_PLANE: k = 5; break;
case BK_BOOLEAN: k = 0; break;
case BK_TORUS: k = 5; break;
case BK_MOVED: k = 0; break;
case BK_SKIN: k = 0; break;
case BK_DISK: k = 4; break;
case BK_CYLINDER: k = 5; break;
default: throw Bad();
}
if (s.kind == BK_BOOLEAN)
{
s.d[0] = (double)rd.idx(1, 10);
if (s.d[0] > 3 && s.d[0] < 10)
throw Bad();
}
for (int i = 0; i < k; i++)
s.d[i] = rd.num();
if (s.kind == BK_BOOLEAN)
{
s.sub.push_back(read_spec(rd, depth + 1));
s.sub.push_back(read_spec(rd, depth + 1));
}
if (s.kind == BK_MOVED)
{
int64_t n = rd.idx(0, MAX_STEPS);
for (int64_t i = 0; i < n; i++)
{
int64_t t = rd.idx(STEP_ROTATE, STEP_TRANSLATE);
s.steps.push_back((double)t);
int m = t == STEP_ROTATE ? 7 : 3;
for (int j = 0; j < m; j++)
s.steps.push_back(rd.num());
}
s.sub.push_back(read_spec(rd, depth + 1));
}
if (s.kind == BK_SKIN)
{
int64_t np = rd.idx(2, MAX_SKIN_CURVES);
int64_t total = 0;
for (int64_t i = 0; i < np; i++)
{
int64_t c = rd.idx(1, MAX_SKIN_CURVES);
total += c;
s.counts.push_back((double)c);
}
int64_t nc = rd.idx(1, MAX_SKIN_CURVES);
if (nc != total)
throw Bad();
for (int64_t i = 0; i < nc; i++)
{
int64_t p = rd.idx(1, 17);
int64_t n1 = rd.idx(2, MAX_SKIN_POINTS);
if (p >= n1)
throw Bad();
s.curves.push_back((double)p);
s.curves.push_back((double)n1);
for (int64_t j = 0; j < 4 * n1 + n1 + p + 1; j++)
s.curves.push_back(rd.num());
}
s.d[0] = (double)rd.idx(1, 17);
s.d[1] = rd.flag() ? 1.0 : 0.0;
}
return s;
}
// The BK_SKIN operand: SmApiCreateSkinPrimitive of its curves (each an
// SmBSplineCurve(gw_CURVE*, 3, FALSE)), which leaves the curves with the caller.
SmApiStatus build_skin(const BrepSpec& s, SmBrep*& r)
{
const SmContext& ctx = *SmApiGetOrCreateContext();
std::vector<SmBSplineCurve*> owned;
SmTArray<ULONG> counts;
SmTArray<const SmCurve*> curves;
for (size_t i = 0; i < s.counts.size(); i++)
counts.Add((ULONG)s.counts[i]);
const double* d = s.curves.data();
size_t at = 0;
while (at < s.curves.size())
{
int64_t p = (int64_t)d[at++];
int64_t n1 = (int64_t)d[at++];
gw_CURVE* cur = sm_AllocateNurbCurve((gw_INDEX)(n1 - 1), (gw_DEGREE)p, (gw_INDEX)(n1 + p));
for (int64_t j = 0; j < n1; j++)
{
cur->pol->Pw[j].x = d[at++];
cur->pol->Pw[j].y = d[at++];
cur->pol->Pw[j].z = d[at++];
cur->pol->Pw[j].w = d[at++];
}
for (int64_t j = 0; j <= n1 + p; j++)
cur->knt->U[j] = d[at++];
owned.push_back(new (ctx) SmBSplineCurve(cur, 3, FALSE));
curves.Add(owned.back());
}
SmApiStatus stat = SmApiCreateSkinPrimitive(counts, curves, (ULONG)s.d[0],
s.d[1] != 0.0 ? TRUE : FALSE, r, NULL, NULL, NULL);
for (size_t i = 0; i < owned.size(); i++)
delete owned[i];
return stat;
}
std::vector<BrepSpec> read_specs(Rd& rd, int64_t max)
{
int64_t k = rd.idx(0, max);
std::vector<BrepSpec> v;
for (int64_t i = 0; i < k; i++)
v.push_back(read_spec(rd));
return v;
}
SmBrep* build(const BrepSpec& s)
{
SmVector3d o(s.d[0], s.d[1], s.d[2]);
SmBrep* r = NULL;
SmApiStatus stat = SM_ERR;
switch (s.kind)
{
case BK_BOX: stat = SmApiCreateBox(o, s.d[3], s.d[4], s.d[5], r); break;
case BK_SPHERE: stat = SmApiCreateSphere(o, s.d[3], r); break;
case BK_PLANE: stat = SmApiCreatePlane(o, s.d[3], s.d[4], r); break;
case BK_TORUS: stat = SmApiCreateTorus(o, s.d[3], s.d[4], r); break;
case BK_SKIN: stat = build_skin(s, r); break;
case BK_DISK: stat = SmApiCreatePlanarCircle(o, s.d[3], r); break;
case BK_CYLINDER: stat = SmApiCreateCylinder(o, s.d[3], s.d[4], r); break;
case BK_MOVED:
{
r = build(s.sub[0]);
stat = SM_SUCCESS;
const std::vector<double>& p = s.steps;
for (size_t i = 0; i < p.size() && stat == SM_SUCCESS;)
{
if ((int32_t)p[i] == STEP_ROTATE)
{
stat = SmApiRotate(r, SmVector3d(p[i + 1], p[i + 2], p[i + 3]),
SmVector3d(p[i + 4], p[i + 5], p[i + 6]), p[i + 7]);
i += 8;
}
else
{
stat = SmApiTranslate(r, SmVector3d(p[i + 1], p[i + 2], p[i + 3]));
i += 4;
}
}
break;
}
case BK_BOOLEAN:
{
SmBrep* a = build(s.sub[0]);
SmBrep* b = NULL;
try
{
b = build(s.sub[1]);
}
catch (...)
{
delete a;
throw;
}
// Union, intersection, difference and slice delete `b` on success
// (source/SM_API/inc/SmApiBrep.h); both remain allocated on failure.
stat = SmApiBoolean(a, b, (SmBooleanOperationType)(int32_t)s.d[0], r);
if (stat != SM_SUCCESS || r != a)
{
if (r != NULL && r != a && r != b)
delete r;
delete a;
delete b;
throw Bad();
}
break;
}
}
if (s.kind == BK_TORUS && stat != SM_SUCCESS)
{
delete r;
throw TorusFailed{st(stat)};
}
if (stat != SM_SUCCESS || r == NULL)
{
delete r;
throw Bad();
}
return r;
}
// Builds every spec; on failure deletes what was built and rethrows.
std::vector<SmBrep*> build_all(const std::vector<BrepSpec>& specs)
{
std::vector<SmBrep*> v;
try
{
for (size_t i = 0; i < specs.size(); i++)
v.push_back(build(specs[i]));
}
catch (...)
{
for (size_t i = 0; i < v.size(); i++)
delete v[i];
throw;
}
return v;
}
// `faces edges vertices regions manifold_solid` of a B-rep, then per region (GetRegions
// order) `is_void is_infinite shells` and per shell (GetShells order) `faceuses mate_region`:
// the index in GetRegions of the region on the other side of the shell's first faceuse
// (-1 for a shell without faceuses), so the order of the outer and void shells is compared.
void put_brep(std::vector<double>& o, const SmBrep* b)
{
SmTArray<SmRegion*> regions;
b->GetRegions(regions);
o.push_back((double)b->GetNumFaces());
o.push_back((double)b->GetNumEdges());
o.push_back((double)b->GetNumVertices());
o.push_back((double)regions.GetSize());
o.push_back(flg(b->IsManifoldSolid() != FALSE));
for (ULONG r = 0; r < regions.GetSize(); r++)
{
SmTArray<SmShell*> shells;
regions[r]->GetShells(shells);
o.push_back((double)regions[r]->IsVoid());
o.push_back(flg(regions[r]->IsInfiniteRegion() != FALSE));
o.push_back((double)shells.GetSize());
for (ULONG s = 0; s < shells.GetSize(); s++)
{
SmTArray<SmFaceuse*> fus;
shells[s]->GetFaceuses(fus);
o.push_back((double)fus.GetSize());
double mate = -1;
SmFaceuse* fu = shells[s]->GetFirstFaceuse();
if (fu != NULL && fu->GetMate() != NULL)
{
SmRegion* mr = fu->GetMate()->GetShell()->GetRegion();
for (ULONG k = 0; k < regions.GetSize(); k++)
if (regions[k] == mr)
mate = (double)k;
}
o.push_back(mate);
}
}
}
// Accuracies of the OP_MEASURED queries: SmApiBrepComputeVolume clamps to [1e-8, 1e-1],
// SmApiFaceComputeArea to [1e-4, 1e-1].
const double VOLUME_ACCURACY = 1e-6;
const double AREA_ACCURACY = 1e-4;
// OP_MEASURED's tail: `volume_status volume faces` then the face areas in ascending order
// (a face whose SmApiFaceComputeArea fails counts as minus its status), then the number of
// those values, so the reader can split them from the integer record before them.
void put_measures(std::vector<double>& o, SmBrep* b)
{
size_t start = o.size();
double volume = 0.0;
SmApiStatus vstat = SmApiBrepComputeVolume(b, VOLUME_ACCURACY, volume);
o.push_back(st(vstat));
o.push_back(vstat == SM_SUCCESS ? volume : 0.0);
SmTArray<SmFace*> faces;
b->GetFaces(faces);
o.push_back((double)faces.GetSize());
std::vector<double> areas;
for (ULONG i = 0; i < faces.GetSize(); i++)
{
double area = 0.0;
SmApiStatus astat = SmApiFaceComputeArea(faces[i], AREA_ACCURACY, area);
areas.push_back(astat == SM_SUCCESS ? area : -(double)st(astat));
}
std::sort(areas.begin(), areas.end());
o.insert(o.end(), areas.begin(), areas.end());
o.push_back((double)(o.size() - start));
}
// The tail of a successful OP_BOOLEAN_2D (with a result) or sweep: `faces` then the face
// areas in ascending order (a failed face as minus its status), then the number of those
// values. No volume: these results are sheets or non-manifold.
void put_areas(std::vector<double>& o, SmBrep* b)
{
size_t start = o.size();
SmTArray<SmFace*> faces;
b->GetFaces(faces);
o.push_back((double)faces.GetSize());
std::vector<double> areas;
for (ULONG i = 0; i < faces.GetSize(); i++)
{
double area = 0.0;
SmApiStatus astat = SmApiFaceComputeArea(faces[i], AREA_ACCURACY, area);
areas.push_back(astat == SM_SUCCESS ? area : -(double)st(astat));
}
std::sort(areas.begin(), areas.end());
o.insert(o.end(), areas.begin(), areas.end());
o.push_back((double)(o.size() - start));
}
// Whether upstream keeps the second operand of a successful two-B-rep boolean
// (source/SM_API/inc/SmApiBrep.h).
bool keeps_other(int32_t op, bool keep_option)
{
return op == SM_BO_PARTIAL_MERGE || op == SM_BO_IMPRINT_CLASSIFY || keep_option;
}
void run_two(Rd& rd, int32_t api, std::vector<double>& o)
{
BrepSpec a = read_spec(rd);
BrepSpec b = read_spec(rd);
int32_t op = SM_BO_UNKNOWN;
bool flags[5] = {false, false, true, false, false};
switch (api)
{
case OP_BOOLEAN: op = (int32_t)rd.idx(0, 10); break;
case OP_UNION: op = SM_BO_UNION; break;
case OP_DIFFERENCE: op = SM_BO_DIFFERENCE; break;
case OP_INTERSECTION: op = SM_BO_INTERSECTION; break;
case OP_MERGE: op = SM_BO_MERGE; break;
case OP_WITH_OPTIONS:
op = (int32_t)rd.idx(0, 10);
for (int i = 0; i < 5; i++)
flags[i] = rd.flag();
break;
case OP_MEASURED:
// SmApiBooleanWithOptions with only bBooleanPostProcess chosen
op = (int32_t)rd.idx(0, 10);
flags[2] = rd.flag();
break;
default: throw Bad();
}
if (rd.at != rd.n)
throw Bad();
std::vector<BrepSpec> specs;
specs.push_back(a);
specs.push_back(b);
std::vector<SmBrep*> bs = build_all(specs);
SmBrep* b1 = bs[0];
SmBrep* b2 = bs[1];
SmBrep* res = b1; // stale output: every entry point must clear it
SmBooleanOperationType eop = (SmBooleanOperationType)op;
SmApiStatus stat = SM_ERR;
switch (api)
{
case OP_BOOLEAN: stat = SmApiBoolean(b1, b2, eop, res); break;
case OP_UNION: stat = SmApiBooleanUnion(b1, b2, res); break;
case OP_DIFFERENCE: stat = SmApiBooleanDifference(b1, b2, res); break;
case OP_INTERSECTION: stat = SmApiBooleanIntersection(b1, b2, res); break;
case OP_MERGE: stat = SmApiBooleanMerge(b1, b2, res); break;
case OP_WITH_OPTIONS:
case OP_MEASURED:
stat = SmApiBooleanWithOptions(b1, b2, eop, flags[0], flags[1], flags[2], flags[3],
flags[4], res);
break;
}
o.push_back(st(stat));
if (stat != SM_SUCCESS)
{
// Both operands remain allocated on failure.
if (res != NULL && res != b1 && res != b2)
delete res;
delete b1;
delete b2;
return;
}
bool kept = keeps_other(op, api == OP_WITH_OPTIONS && flags[4]);
o.push_back(flg(res != NULL));
if (res == NULL)
return;
o.push_back(flg(res == b1));
o.push_back(flg(kept));
put_brep(o, res);
if (kept && b2 != res)
{
put_brep(o, b2);
delete b2;
}
if (api == OP_MEASURED)
put_measures(o, res);
delete res; // b1 itself on the documented path; any other b1 is left (ownership unknown)
}
// OP_ATTRIBUTED: the first application attribute id, and the accuracy of the edge lengths
// (SmApiEdgeComputeLength's absolute accuracy).
const ULONG ATTRIBUTE_BASE = 10007;
const double LENGTH_ACCURACY = 1e-9;
// Gives every face (GetFaces order) and edge (GetEdges order) of operand `operand` its own
// SmLongAttribute: id ATTRIBUTE_BASE + 1000 operand + 500 kind + index (kind 0 face, 1
// edge), value the id, behaviour `behavior`: one attribute id per origin, as a history
// caller tags its operands.
void tag_operand(SmBrep* b, ULONG operand, SmAttributeBehaviorType behavior)
{
const SmContext& ctx = *SmApiGetOrCreateContext();
SmTArray<SmFace*> faces;
b->GetFaces(faces);
for (ULONG i = 0; i < faces.GetSize(); i++)
{
ULONG id = ATTRIBUTE_BASE + 1000 * operand + i;
faces[i]->AddAttribute(new (ctx) SmLongAttribute(id, (long)id, behavior));
}
SmTArray<SmEdge*> edges;
b->GetEdges(edges);
for (ULONG i = 0; i < edges.GetSize(); i++)
{
ULONG id = ATTRIBUTE_BASE + 1000 * operand + 500 + i;
edges[i]->AddAttribute(new (ctx) SmLongAttribute(id, (long)id, behavior));
}
}
// One object's row: `measure n` then per attribute `id behavior value users` (value: an
// SmLongAttribute's GetValue, else 0; users: the GetUsers that are SmTopology objects), the attributes in ascending
// order: which of two merged coplanar faces survives (and so whose attributes come first
// in its list) depends on the C++ process's earlier heap use, the set does not.
std::vector<double> attribute_row(const SmAObject* obj, double measure)
{
SmTArray<SmAttribute*> attrs;
obj->GetAttributes(attrs);
std::vector<std::vector<double>> entries;
for (ULONG i = 0; i < attrs.GetSize(); i++)
{
SmAttribute* a = attrs[i];
SmLongAttribute* l = dynamic_cast<SmLongAttribute*>(a);
SmTArray<SmAObject*> users;
a->GetUsers(users);
std::vector<double> e;
e.push_back((double)a->GetAttributeID());
e.push_back((double)a->GetBehavior());
e.push_back(l != NULL ? (double)l->GetValue() : 0.0);
// the topology users: the port keeps no attributes on geometry, whose
// objects (an edge's curve) also use an edge's reference attribute here
ULONG topology_users = 0;
for (ULONG u = 0; u < users.GetSize(); u++)
if (users[u] != NULL && users[u]->IsKindOf(SmTopology_TYPE))
topology_users++;
e.push_back((double)topology_users);
entries.push_back(e);
}
std::sort(entries.begin(), entries.end());
std::vector<double> row;
row.push_back(measure);
row.push_back((double)entries.size());
for (size_t i = 0; i < entries.size(); i++)
row.insert(row.end(), entries[i].begin(), entries[i].end());
return row;
}
// Rows sorted by their attribute part (from index 1), then by measure, so the order of
// the result's lists does not matter.
bool row_less(const std::vector<double>& x, const std::vector<double>& y)
{
if (std::lexicographical_compare(x.begin() + 1, x.end(), y.begin() + 1, y.end()))
return true;
if (std::lexicographical_compare(y.begin() + 1, y.end(), x.begin() + 1, x.end()))
return false;
return x[0] < y[0];
}
void put_rows(std::vector<double>& o, std::vector<std::vector<double>>& rows)
{
std::sort(rows.begin(), rows.end(), row_less);
o.push_back((double)rows.size());
for (size_t i = 0; i < rows.size(); i++)
o.insert(o.end(), rows[i].begin(), rows[i].end());
}
// `faces rows... edges rows...` of a B-rep: each face's row with its area
// (SmApiFaceComputeArea at AREA_ACCURACY; a failure as minus its status) and each edge's
// with its length (SmApiEdgeComputeLength at LENGTH_ACCURACY, likewise).
void put_attributes(std::vector<double>& o, SmBrep* b)
{
std::vector<std::vector<double>> rows;
SmTArray<SmFace*> faces;
b->GetFaces(faces);
for (ULONG i = 0; i < faces.GetSize(); i++)
{
double area = 0.0;
SmApiStatus astat = SmApiFaceComputeArea(faces[i], AREA_ACCURACY, area);
rows.push_back(attribute_row(faces[i], astat == SM_SUCCESS ? area : -(double)st(astat)));
}
put_rows(o, rows);
rows.clear();
SmTArray<SmEdge*> edges;
b->GetEdges(edges);
for (ULONG i = 0; i < edges.GetSize(); i++)
{
double length = 0.0;
SmApiStatus lstat = SmApiEdgeComputeLength(edges[i], LENGTH_ACCURACY, length);
rows.push_back(attribute_row(edges[i], lstat == SM_SUCCESS ? length : -(double)st(lstat)));
}
put_rows(o, rows);
}
// OP_ATTRIBUTED `brep brep operation behavior`: tags both operands (tag_operand, operand 0
// and 1), runs SmApiBoolean, and records `status` then on success the two-B-rep record of
// run_two followed by the attribute rows of the result and of a kept second operand
// (put_attributes) and the number of those values last (as OP_MEASURED's tail).
void run_attributed(Rd& rd, std::vector<double>& o)
{
BrepSpec a = read_spec(rd);
BrepSpec b = read_spec(rd);
int32_t op = (int32_t)rd.idx(0, 10);
SmAttributeBehaviorType behavior = (SmAttributeBehaviorType)rd.idx(0, 4);
if (rd.at != rd.n)
throw Bad();
std::vector<BrepSpec> specs;
specs.push_back(a);
specs.push_back(b);
std::vector<SmBrep*> bs = build_all(specs);
SmBrep* b1 = bs[0];
SmBrep* b2 = bs[1];
tag_operand(b1, 0, behavior);
tag_operand(b2, 1, behavior);
SmBrep* res = b1;
SmApiStatus stat = SmApiBoolean(b1, b2, (SmBooleanOperationType)op, res);
o.push_back(st(stat));
if (stat != SM_SUCCESS)
{
if (res != NULL && res != b1 && res != b2)
delete res;
delete b1;
delete b2;
return;
}
bool kept = keeps_other(op, false);
o.push_back(flg(res != NULL));
if (res == NULL)
return;
o.push_back(flg(res == b1));
o.push_back(flg(kept));
put_brep(o, res);
if (kept && b2 != res)
put_brep(o, b2);
size_t start = o.size();
put_attributes(o, res);
if (kept && b2 != res)
{
put_attributes(o, b2);
delete b2;
}
o.push_back((double)(o.size() - start));
delete res;
}
void run_merge_breps(Rd& rd, std::vector<double>& o)
{
std::vector<BrepSpec> specs = read_specs(rd, 16);
int32_t op = (int32_t)rd.idx(0, 10);
if (rd.at != rd.n)
throw Bad();
std::vector<SmBrep*> bs = build_all(specs);
SmTArray<SmBrep*> arr;
for (size_t i = 0; i < bs.size(); i++)
arr.Add(bs[i]);
SmBrep* res = bs.empty() ? NULL : bs[0]; // stale output
SmApiStatus stat = SmApiMergeBreps(arr, (SmBooleanOperationType)op, res);
o.push_back(st(stat));
if (stat == SM_ERR_INVALID_INPUT)
{
// Rejected before anything is consumed.
o.push_back(flg(res == NULL));
o.push_back((double)arr.GetSize());
for (size_t i = 0; i < bs.size(); i++)
delete bs[i];
return;
}
if (stat != SM_SUCCESS)
return; // consumed in part; leak
o.push_back((double)arr.GetSize());
o.push_back(flg(res != NULL));
if (res == NULL)
return;
put_brep(o, res);
delete res;
}
void run_lists(Rd& rd, std::vector<double>& o)
{
std::vector<BrepSpec> s1 = read_specs(rd, 8);
std::vector<BrepSpec> s2 = read_specs(rd, 8);
int64_t op = rd.idx(-(int64_t)1 << 31, ((int64_t)1 << 31) - 1);
if (rd.at != rd.n)
throw Bad();
std::vector<BrepSpec> all(s1);
all.insert(all.end(), s2.begin(), s2.end());
std::vector<SmBrep*> bs = build_all(all);
SmTArray<SmBrep*> b1, b2;
for (size_t i = 0; i < bs.size(); i++)
(i < s1.size() ? b1 : b2).Add(bs[i]);
SmTArray<SmSurface*> srf1, srf2;
SmTArray<SmBrep*> rb;
SmTArray<SmSurface*> rs;
SmApiStatus stat = SmApiBooleanLists(b1, srf1, b2, srf2, (int)op, rb, rs);
o.push_back(st(stat));
if (stat == SM_SUCCESS)
{
o.push_back((double)rb.GetSize());
for (ULONG i = 0; i < rb.GetSize(); i++)
{
o.push_back(flg(rb[i] != NULL));
if (rb[i] != NULL)
put_brep(o, rb[i]);
}
o.push_back((double)rs.GetSize());
}
// The inputs are never modified; outputs are new objects.
for (ULONG i = 0; i < rb.GetSize(); i++)
delete rb[i];
for (ULONG i = 0; i < rs.GetSize(); i++)
delete rs[i];
for (size_t i = 0; i < bs.size(); i++)
delete bs[i];
}
void run_trees(Rd& rd, std::vector<double>& o)
{
std::vector<BrepSpec> specs = read_specs(rd, 16);
int64_t t = rd.idx(0, 64);
SmTArray<long> tree;
for (int64_t i = 0; i < t; i++)
tree.Add((long)rd.idx(-64, 64));
if (rd.at != rd.n)
throw Bad();
std::vector<SmBrep*> bs = build_all(specs);
SmTArray<SmBrep*> arr;
for (size_t i = 0; i < bs.size(); i++)
arr.Add(bs[i]);
SmApiStatus stat = SmApiBooleanTrees(arr, tree);
o.push_back(st(stat));
if (stat != SM_SUCCESS)
return; // consumed in part; leak
o.push_back((double)arr.GetSize());
for (ULONG i = 0; i < arr.GetSize(); i++)
{
o.push_back(flg(arr[i] != NULL));
if (arr[i] != NULL)
put_brep(o, arr[i]);
}
for (ULONG i = 0; i < arr.GetSize(); i++)
delete arr[i];
}
// The index of `e` in `b`'s GetEdges list, -1 when `b` no longer has it (compared by
// address only: an edge the merge deleted is never dereferenced).
double edge_index(const SmBrep* b, const SmEdge* e)
{
SmTArray<SmEdge*> edges;
b->GetEdges(edges);
for (ULONG i = 0; i < edges.GetSize(); i++)
if (edges[i] == e)
return (double)i;
return -1.0;
}
// The two-B-rep entry points added with SmApiBooleanWithCurves: OP_WITH_CURVES and
// OP_NON_MANIFOLD (`brep brep operation`, the two-B-rep record; OP_WITH_CURVES then
// `n` and each intersection edge's index in the result's GetEdges, -1 when gone),
// OP_BOOLEAN_2D (`brep brep 2d_operation`: `assigned`, then the result and its areas
// (put_areas), or both operands when an empty operand leaves rpResult unassigned) and OP_PIECEWISE
// (`brep brep create_new`: `has is_first`, the result, then the second operand, which
// upstream imprints but keeps).
void run_more_two(Rd& rd, int32_t api, std::vector<double>& o)
{
BrepSpec a = read_spec(rd);
BrepSpec b = read_spec(rd);
int32_t op = SM_BO_UNKNOWN;
bool create_new = false;
switch (api)
{
case OP_WITH_CURVES:
case OP_NON_MANIFOLD: op = (int32_t)rd.idx(0, 10); break;
case OP_BOOLEAN_2D: op = (int32_t)rd.idx(0, 4); break;
case OP_PIECEWISE: create_new = rd.flag(); break;
default: throw Bad();
}
if (rd.at != rd.n)
throw Bad();
std::vector<BrepSpec> specs;
specs.push_back(a);
specs.push_back(b);
std::vector<SmBrep*> bs = build_all(specs);
SmBrep* b1 = bs[0];
SmBrep* b2 = bs[1];
SmBrep* res = NULL;
SmTArray<SmEdge*> edges;
SmApiStatus stat = SM_ERR;
switch (api)
{
case OP_WITH_CURVES:
stat = SmApiBooleanWithCurves(b1, b2, (SmBooleanOperationType)op, res, edges);
break;
case OP_NON_MANIFOLD:
stat = SmApiNonManifoldBoolean(b1, b2, (SmBooleanOperationType)op, res);
break;
case OP_BOOLEAN_2D:
stat = SmApiBoolean2d(b1, b2, (Sm2DBooleanOperationType)op, res);
break;
case OP_PIECEWISE:
stat = SmApiPiecewiseMerge(b1, b2, create_new ? TRUE : FALSE, res);
break;
}
o.push_back(st(stat));
if (stat != SM_SUCCESS)
{
// Both operands remain allocated on failure.
if (res != NULL && res != b1 && res != b2)
delete res;
delete b1;
delete b2;
return;
}
if (api == OP_BOOLEAN_2D)
{
// Assigned: rpResult aliases pBrep1 and pBrep2 was deleted; otherwise (an operand
// without faces) both are unchanged.
o.push_back(flg(res != NULL));
if (res == NULL)
{
put_brep(o, b1);
put_brep(o, b2);
delete b1;
delete b2;
return;
}
put_brep(o, res);
put_areas(o, res);
delete res;
return;
}
if (api == OP_PIECEWISE)
{
o.push_back(flg(res != NULL));
if (res == NULL)
return; // ownership unknown; leak
o.push_back(flg(res == b1));
put_brep(o, res);
if (b2 != res)
{
put_brep(o, b2);
delete b2;
}
delete res;
return;
}
bool kept = keeps_other(op, false);
o.push_back(flg(res != NULL));
if (res == NULL)
return;
o.push_back(flg(res == b1));
o.push_back(flg(kept));
put_brep(o, res);
if (kept && b2 != res)
{
put_brep(o, b2);
delete b2;
}
if (api == OP_WITH_CURVES)
{
o.push_back((double)edges.GetSize());
for (ULONG i = 0; i < edges.GetSize(); i++)
o.push_back(edge_index(res, edges[i]));
}
delete res;
}
// One selection array of a non-manifold sweep: `count` (-1 for a NULL array) then the
// entries, each an index into the B-rep's list or -1 for a NULL entry.
template <class T>
bool read_selection(Rd& rd, const SmTArray<T*>& all, SmTArray<T*>& out)
{
int64_t n = rd.idx(-1, MAX_SELECTION);
for (int64_t i = 0; i < n; i++)
{
int64_t at = rd.idx(-1, (int64_t)all.GetSize() - 1);
out.Add(at < 0 ? (T*)NULL : all[(ULONG)at]);
}
return n >= 0;
}
// OP_NM_SWEEP (`brep dir[3] dist stitch merge region` then the selections) and
// OP_NM_ROTATIONAL_SWEEP (`brep base[3] axis[3] angle stitch merge region` then the
// selections): the status, then the swept B-rep and its areas (put_areas) on success, the
// untouched B-rep on SM_ERR_INVALID_INPUT. `region` is pRegionBrep: 0 NULL, 1 the swept
// B-rep, 2 another B-rep (SmApiCreateBox(origin, 1, 1, 1)). With `behavior` (>= 0, the
// OP_ATTRIBUTED_* ops) every face and edge of the B-rep is tagged first (tag_operand,
// operand 0), and a successful sweep's record ends with its attribute rows
// (put_attributes) and the number of those values, after the areas.
void run_sweep(Rd& rd, int32_t api, std::vector<double>& o, int64_t behavior = -1)
{
BrepSpec spec = read_spec(rd);
double v[7] = {0, 0, 0, 0, 0, 0, 0};
int nv = api == OP_NM_SWEEP ? 4 : 7;
for (int i = 0; i < nv; i++)
v[i] = rd.num();
bool stitch = rd.flag();
bool merge = rd.flag();
int64_t region = rd.idx(0, 2);
// The selections index the operand's lists, so it is built first; the rest of the
// program is still read before upstream is called.
std::vector<BrepSpec> specs;
specs.push_back(spec);
std::vector<SmBrep*> bs = build_all(specs);
SmBrep* brep = bs[0];
SmTArray<SmFace*> faces, sel_faces;
SmTArray<SmEdge*> edges, sel_edges;
SmTArray<SmVertex*> vertices, sel_vertices;
brep->GetFaces(faces);
brep->GetEdges(edges);
brep->GetVertices(vertices);
bool has_faces = false, has_edges = false, has_vertices = false;
try
{
has_faces = read_selection(rd, faces, sel_faces);
has_edges = read_selection(rd, edges, sel_edges);
has_vertices = read_selection(rd, vertices, sel_vertices);
if (rd.at != rd.n)
throw Bad();
}
catch (...)
{
delete brep;
throw;
}
SmBrep* other = NULL;
if (region == 2 && (SmApiCreateBox(SmVector3d(0, 0, 0), 1, 1, 1, other) != SM_SUCCESS || !other))
{
delete other;
delete brep;
throw Bad();
}
if (behavior >= 0)
tag_operand(brep, 0, (SmAttributeBehaviorType)behavior);
SmBrep* region_brep = region == 0 ? NULL : region == 1 ? brep : other;
SmTArray<SmFace*>* pf = has_faces ? &sel_faces : NULL;
SmTArray<SmEdge*>* pe = has_edges ? &sel_edges : NULL;
SmTArray<SmVertex*>* pv = has_vertices ? &sel_vertices : NULL;
SmApiStatus stat;
if (api == OP_NM_SWEEP)
stat = SmApiNonManifoldSweep(brep, region_brep, SmVector3d(v[0], v[1], v[2]), v[3],
stitch ? TRUE : FALSE, merge ? TRUE : FALSE, pf, pe, pv);
else
stat = SmApiNonManifoldRotationalSweep(brep, region_brep, SmPoint3d(v[0], v[1], v[2]),
SmVector3d(v[3], v[4], v[5]), v[6],
stitch ? TRUE : FALSE, merge ? TRUE : FALSE,
pf, pe, pv);
o.push_back(st(stat));
if (stat == SM_SUCCESS || stat == SM_ERR_INVALID_INPUT)
put_brep(o, brep);
if (stat == SM_SUCCESS)
put_areas(o, brep);
if (stat == SM_SUCCESS && behavior >= 0)
{
size_t start = o.size();
put_attributes(o, brep);
o.push_back((double)(o.size() - start));
}
delete brep;
delete other;
}
// SmApiTessellateBoundaries of an operand whose `GetEdges` edges at the listed indices
// first lose their curve (SetCurve(NULL, TRUE, FALSE, FALSE), as upstream's test): the
// status, then (outputs are kept on SM_ERR) the edges as `GetEdges` indices, the vertex
// counts and the points.
void run_tessellate_boundaries(Rd& rd, std::vector<double>& o)
{
BrepSpec spec = read_spec(rd);
double tol = rd.num();
int64_t nd = rd.idx(0, 64);
std::vector<int64_t> drop;
for (int64_t i = 0; i < nd; i++)
drop.push_back(rd.idx(0, 4095));
if (rd.at != rd.n)
throw Bad();
std::vector<BrepSpec> specs;
specs.push_back(spec);
std::vector<SmBrep*> bs = build_all(specs);
SmBrep* b = bs[0];
SmTArray<SmEdge*> all;
b->GetEdges(all);
for (int64_t d : drop)
{
if (d >= (int64_t)all.GetSize())
{
delete b;
throw Bad();
}
all[(ULONG)d]->SetCurve(NULL, TRUE, FALSE, FALSE);
}
SmTArray<SmPoint3d> points;
SmTArray<ULONG> counts;
SmTArray<SmEdge*> edges;
SmApiStatus stat = SmApiTessellateBoundaries(b, tol, points, counts, edges);
o.push_back(st(stat));
o.push_back((double)edges.GetSize());
for (ULONG i = 0; i < edges.GetSize(); i++)
{
double at = -1.0;
for (ULONG j = 0; j < all.GetSize(); j++)
if (all[j] == edges[i])
{
at = (double)j;
break;
}
o.push_back(at);
}
o.push_back((double)counts.GetSize());
for (ULONG i = 0; i < counts.GetSize(); i++)
o.push_back((double)counts[i]);
o.push_back((double)points.GetSize());
for (ULONG i = 0; i < points.GetSize(); i++)
{
o.push_back(points[i].x);
o.push_back(points[i].y);
o.push_back(points[i].z);
}
delete b;
}
void run(Rd& rd, std::vector<double>& o)
{
int32_t api = (int32_t)rd.idx(1, 20);
switch (api)
{
case OP_BOOLEAN:
case OP_UNION:
case OP_DIFFERENCE:
case OP_INTERSECTION:
case OP_MERGE:
case OP_WITH_OPTIONS:
case OP_MEASURED: run_two(rd, api, o); break;
case OP_MERGE_BREPS: run_merge_breps(rd, o); break;
case OP_LISTS: run_lists(rd, o); break;
case OP_TREES: run_trees(rd, o); break;
case OP_WITH_CURVES:
case OP_BOOLEAN_2D:
case OP_NON_MANIFOLD:
case OP_PIECEWISE: run_more_two(rd, api, o); break;
case OP_NM_SWEEP:
case OP_NM_ROTATIONAL_SWEEP: run_sweep(rd, api, o); break;
case OP_ATTRIBUTED: run_attributed(rd, o); break;
case OP_TESSELLATE_BOUNDARIES: run_tessellate_boundaries(rd, o); break;
case OP_ATTRIBUTED_NM_SWEEP:
case OP_ATTRIBUTED_NM_ROTATIONAL_SWEEP:
{
int64_t behavior = rd.idx(0, 4);
run_sweep(rd, api - 4, o, behavior);
break;
}
default: throw Bad();
}
}
} // namespace
SC_EXPORT int32_t sc_boolean_api_brep_run(const double* in, int64_t n_in, double* out,
int64_t cap, int64_t* out_len)
{
SC_TRY
SmApiGetOrCreateContext();
Rd rd{in, n_in, 0};
std::vector<double> o;
try
{
run(rd, o);
}
catch (const Bad&)
{
return SC_ERR_ARG;
}
catch (const TorusFailed& t)
{
o.clear();
o.push_back(t.status);
o.push_back(2.0);
}
return sc_copy_out(o.data(), (int64_t)o.size(), out, cap, out_len);
SC_CATCH
}