Files
solvecraft/harness/cpp/shim/boolean_api_brep.cpp
T
Brett Lawson fc8903a2be feat: B-rep-owned attribute store carried through two-B-rep booleans (#1462 P1)
topology::Brep owns an AttributeStore keyed by the B-rep-local notify_ref
identity, with add/attach/find/entity/remove/clear attribute methods; a
clone copies it. The two-B-rep boolean entry points seed the merge engine's
store from both operands and write the surviving attributes back to the
result and a kept other B-rep, so SMLib's Split/Copy/Merge handlers carry
operand face and edge attributes to the result. The boolean cleanup's
DeleteEdge/DeleteVertex/DeleteTopologicalVertex now notify on the merge's
records, attributes and names (coplanar faces merged by the cleanup keep
both origins, as upstream). SmApiEdgeComputeLength passes the edge's zone
tolerance to the curve length.

Difftest: boolean.api_brep 'attributed' op (shim tags operands with
SmLongAttribute, both sides compare per face/edge attribute rows) and a
cylinder operand kind; 44 fixture cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MNXLXj2sJf943y2dDF36Vy
2026-10-29 20:35:07 +01:00

802 lines
26 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), 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), 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 <SmBSplineCurve.h>
#include <SmBrep.h>
#include <SmContext.h>
#include <SmEdge.h>
#include <SmFace.h>
#include <SmFaceuse.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_ATTRIBUTED = 11,
};
// 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_CYLINDER = 8, // 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, 8);
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_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_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));
}
// 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 (GetAttributes order) `id behavior value
// users` (value: an SmLongAttribute's GetValue, else 0; users: GetUsers' size).
std::vector<double> attribute_row(const SmAObject* obj, double measure)
{
std::vector<double> row;
row.push_back(measure);
SmTArray<SmAttribute*> attrs;
obj->GetAttributes(attrs);
row.push_back((double)attrs.GetSize());
for (ULONG i = 0; i < attrs.GetSize(); i++)
{
SmAttribute* a = attrs[i];
SmLongAttribute* l = dynamic_cast<SmLongAttribute*>(a);
SmTArray<SmAObject*> users;
a->GetUsers(users);
row.push_back((double)a->GetAttributeID());
row.push_back((double)a->GetBehavior());
row.push_back(l != NULL ? (double)l->GetValue() : 0.0);
row.push_back((double)users.GetSize());
}
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];
}
void run(Rd& rd, std::vector<double>& o)
{
int32_t api = (int32_t)rd.idx(1, 11);
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_ATTRIBUTED: run_attributed(rd, o); 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
}