mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-08 13:01:52 +00:00
a64ea7d08b
Shape::new took (BrepId, id) keys but nothing to resolve them, so its B-rep and face boxes were DISPATCH GAPs and its edge and vertex lists stale UNPORTED stubs. It now takes a BrepStore ((BrepId, &Brep) for one B-rep, NoBreps for none) and runs the whole constructor: the box union of the listed objects, then each face, edge and vertex loaded into its tree once (the constructor's mark as sets of loaded keys, since a shape may span B-reps), statuses ignored as upstream. SmShell::RayFire's shape, FindFacesToDelete and SmApiFaceClosestPoint pass their B-rep. The geom.object shim built its box with SmApiCreateBox and numbered entities by the B-rep lists, which the Rust side could not import. Both sides now build the shared fixture's SmApiCreateBox solid; the Rust import gets upstream's GetFaces/GetEdges/GetVertices orders (sc_geom_object_list_orders), which order a whole B-rep's members. The shim rejects the inputs SmShape dereferences (a face without surface, an edge without curve, a lone B-rep without infinite region). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
429 lines
15 KiB
C++
429 lines
15 KiB
C++
// SPDX-License-Identifier: Apache-2.0
|
|
// difftest shim for unit `geom.object`. Wraps upstream SmObject (type system, Notify,
|
|
// AssertValid), SmTemporaryChangeValue<TYPE> (source/SMLib/inc/SmObject.h,
|
|
// source/SMLib/src/SmObject.cpp) and SmShape (source/SMLib/inc/SmShape.h,
|
|
// source/SMLib/src/SmShape.cpp). B-reps for shapes come from the shared B-rep fixture
|
|
// (support/topology_fixture.h), whose walk numbers their entities as the Rust import does.
|
|
#include "sc_shim.h"
|
|
#include "support/topology_fixture.h"
|
|
#include <SmBrep.h>
|
|
#include <SmContext.h>
|
|
#include <SmEdge.h>
|
|
#include <SmFace.h>
|
|
#include <SmRegion.h>
|
|
#include <SmVertex.h>
|
|
#include <SmObject.h>
|
|
#include <SmShape.h>
|
|
#include <SmTree.h>
|
|
|
|
#include <cstring>
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
namespace
|
|
{
|
|
|
|
const int64_t kNumObjects = 6;
|
|
|
|
// A notify callback recording (tag, op, caller, data1, data2, data3, caller type).
|
|
struct ScRecorder : public SmNotifyCallback
|
|
{
|
|
int64_t m_lTag = 0;
|
|
std::vector<int64_t>* m_pOut = nullptr;
|
|
std::vector<SmObject*>* m_pObjs = nullptr;
|
|
|
|
int64_t Index(SmObject* p) const
|
|
{
|
|
if (p == nullptr)
|
|
return -1;
|
|
for (size_t i = 0; i < m_pObjs->size(); ++i)
|
|
if ((*m_pObjs)[i] == p)
|
|
return (int64_t)i;
|
|
return -2;
|
|
}
|
|
|
|
SmStatus Execute(SmNotifyOperation eOp, SmObject* pObj, SmObject* p1, SmObject* p2,
|
|
SmObject* p3) override
|
|
{
|
|
m_pOut->push_back(m_lTag);
|
|
m_pOut->push_back((int64_t)eOp);
|
|
m_pOut->push_back(Index(pObj));
|
|
m_pOut->push_back(pObj ? (int64_t)pObj->GetType() : 0);
|
|
m_pOut->push_back(Index(p1));
|
|
m_pOut->push_back(Index(p2));
|
|
m_pOut->push_back(Index(p3));
|
|
return SM_ERR; // ignored by Notify
|
|
}
|
|
};
|
|
|
|
enum
|
|
{
|
|
TCV_OPEN = 0, // push SmTemporaryChangeValue(var, x)
|
|
TCV_OPEN_SET = 1, // push SmTemporaryChangeValue(); then Set(var, x)
|
|
TCV_CLOSE = 2, // pop (destructor)
|
|
TCV_CLEAR = 3,
|
|
TCV_USE_ORIG = 4,
|
|
TCV_USE_TEMP = 5,
|
|
TCV_SET_NEW = 6, // SetNewValue(x)
|
|
TCV_WRITE = 7, // var = x
|
|
};
|
|
|
|
template <typename T>
|
|
int32_t run_tcv(T init, const int64_t* ops, const double* xs, int64_t n_ops,
|
|
std::vector<double>& res)
|
|
{
|
|
T var = init;
|
|
std::vector<std::unique_ptr<SmTemporaryChangeValue<T>>> stack;
|
|
res.push_back((double)var);
|
|
for (int64_t i = 0; i < n_ops; ++i)
|
|
{
|
|
T x = (T)xs[i];
|
|
switch (ops[i])
|
|
{
|
|
case TCV_OPEN: stack.emplace_back(new SmTemporaryChangeValue<T>(var, x)); break;
|
|
case TCV_OPEN_SET:
|
|
stack.emplace_back(new SmTemporaryChangeValue<T>());
|
|
stack.back()->Set(var, x);
|
|
break;
|
|
case TCV_CLOSE: stack.pop_back(); break;
|
|
case TCV_CLEAR: stack.back()->Clear(); break;
|
|
case TCV_USE_ORIG: stack.back()->UseOrigValue(); break;
|
|
case TCV_USE_TEMP: stack.back()->UseTempValue(); break;
|
|
case TCV_SET_NEW: stack.back()->SetNewValue(x); break;
|
|
case TCV_WRITE: var = x; break;
|
|
default: return SC_ERR_ARG;
|
|
}
|
|
res.push_back((double)var);
|
|
}
|
|
while (!stack.empty())
|
|
{
|
|
stack.pop_back();
|
|
res.push_back((double)var);
|
|
}
|
|
return SC_OK;
|
|
}
|
|
|
|
void put_box(std::vector<double>& res, const SmExtent3d& b)
|
|
{
|
|
const SmPoint3d& lo = b.GetMin();
|
|
const SmPoint3d& hi = b.GetMax();
|
|
res.push_back(lo.x);
|
|
res.push_back(lo.y);
|
|
res.push_back(lo.z);
|
|
res.push_back(hi.x);
|
|
res.push_back(hi.y);
|
|
res.push_back(hi.z);
|
|
}
|
|
|
|
void put_tree(std::vector<double>& res, const SmTree* pTree)
|
|
{
|
|
res.push_back((double)pTree->GetNodeCount());
|
|
SmTArray<SmObjectList*> sList;
|
|
pTree->GetObjectList(sList);
|
|
res.push_back((double)sList.GetSize());
|
|
SmTreeNode* pTop = pTree->GetTopNode();
|
|
res.push_back(pTop ? 1.0 : 0.0);
|
|
if (pTop)
|
|
put_box(res, pTop->GetBoundingBox());
|
|
}
|
|
|
|
template <typename P>
|
|
int64_t index_of(const std::vector<P*>& all, P* p)
|
|
{
|
|
for (size_t i = 0; i < all.size(); ++i)
|
|
if (all[i] == p)
|
|
return (int64_t)i;
|
|
return -1;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// Type system: per t in ts, (SmObject::IsKindOf(t), SmShape::IsKindOf(t)); then
|
|
// SmObject::GetType, SmShape::GetType, SmObject::AssertValid, and the two type
|
|
// strings as (length, chars...).
|
|
SC_EXPORT int32_t sc_geom_object_types(const int64_t* ts, int64_t n, int64_t* out, int64_t cap,
|
|
int64_t* out_len)
|
|
{
|
|
if (n < 0)
|
|
return SC_ERR_ARG;
|
|
SC_TRY
|
|
SmObject sObj;
|
|
SmTArray<SmBrep*> sB;
|
|
SmTArray<SmFace*> sF;
|
|
SmTArray<SmEdge*> sE;
|
|
SmTArray<SmVertex*> sV;
|
|
SmShape sShape(sB, sF, sE, sV);
|
|
std::vector<int64_t> res;
|
|
for (int64_t i = 0; i < n; ++i)
|
|
{
|
|
res.push_back(sObj.IsKindOf((SM_TYPE)ts[i]) ? 1 : 0);
|
|
res.push_back(sShape.IsKindOf((SM_TYPE)ts[i]) ? 1 : 0);
|
|
}
|
|
res.push_back((int64_t)sObj.GetType());
|
|
res.push_back((int64_t)sShape.GetType());
|
|
res.push_back(sObj.AssertValid() ? 1 : 0);
|
|
for (const TCHAR* s : {sObj.GetTypeString(), sShape.GetTypeString()})
|
|
{
|
|
size_t len = strlen(s);
|
|
res.push_back((int64_t)len);
|
|
for (size_t k = 0; k < len; ++k)
|
|
res.push_back((int64_t)(unsigned char)s[k]);
|
|
}
|
|
return sc_copy_out(res.data(), (int64_t)res.size(), out, cap, out_len);
|
|
SC_CATCH
|
|
}
|
|
|
|
// Notify script. ops[8*n_ops] per op: (has_ctx, user_cb, sys_cb, op, caller, d1, d2, d3);
|
|
// object indices in [0, 6), data -1 = NULL. The caller's context is the shared
|
|
// SmContext (with the given callbacks installed) or NULL. Output: per op the count of
|
|
// Execute records, then each record (tag 1=user 2=sys, op, caller, caller type, d1, d2, d3).
|
|
SC_EXPORT int32_t sc_geom_object_notify(const int64_t* ops, int64_t n_ops, int64_t* out,
|
|
int64_t cap, int64_t* out_len)
|
|
{
|
|
if (n_ops < 0)
|
|
return SC_ERR_ARG;
|
|
for (int64_t i = 0; i < n_ops; ++i)
|
|
{
|
|
const int64_t* o = ops + 8 * i;
|
|
for (int k = 0; k < 3; ++k)
|
|
if (o[k] < 0 || o[k] > 1)
|
|
return SC_ERR_ARG;
|
|
if (o[3] < 0 || o[3] > (int64_t)SM_NO_UNKNOWN)
|
|
return SC_ERR_ARG;
|
|
if (o[4] < 0 || o[4] >= kNumObjects)
|
|
return SC_ERR_ARG;
|
|
for (int k = 5; k < 8; ++k)
|
|
if (o[k] < -1 || o[k] >= kNumObjects)
|
|
return SC_ERR_ARG;
|
|
}
|
|
SC_TRY
|
|
SmContext sCtx;
|
|
std::vector<SmObject> vObjs((size_t)kNumObjects);
|
|
std::vector<SmObject*> vPtrs;
|
|
for (SmObject& r : vObjs)
|
|
vPtrs.push_back(&r);
|
|
std::vector<int64_t> res;
|
|
std::vector<int64_t> rec;
|
|
ScRecorder sUser, sSys;
|
|
sUser.m_lTag = 1;
|
|
sSys.m_lTag = 2;
|
|
sUser.m_pOut = sSys.m_pOut = &rec;
|
|
sUser.m_pObjs = sSys.m_pObjs = &vPtrs;
|
|
for (int64_t i = 0; i < n_ops; ++i)
|
|
{
|
|
const int64_t* o = ops + 8 * i;
|
|
sCtx.SetUserNotifyCallback(o[1] ? &sUser : nullptr);
|
|
sCtx.SetSysNotifyCallback(nullptr);
|
|
sCtx.SetSysNotifyCallback(o[2] ? &sSys : nullptr);
|
|
SmObject* pCaller = vPtrs[(size_t)o[4]];
|
|
pCaller->SetContext(o[0] ? &sCtx : nullptr);
|
|
auto data = [&](int64_t k) { return k < 0 ? (SmObject*)nullptr : vPtrs[(size_t)k]; };
|
|
rec.clear();
|
|
pCaller->Notify((SmNotifyOperation)o[3], data(o[5]), data(o[6]), data(o[7]));
|
|
res.push_back((int64_t)(rec.size() / 7));
|
|
res.insert(res.end(), rec.begin(), rec.end());
|
|
}
|
|
sCtx.SetUserNotifyCallback(nullptr);
|
|
sCtx.SetSysNotifyCallback(nullptr);
|
|
return sc_copy_out(res.data(), (int64_t)res.size(), out, cap, out_len);
|
|
SC_CATCH
|
|
}
|
|
|
|
// SmTemporaryChangeValue script on one variable. type 0 = double, 1 = SmBoolean (int),
|
|
// 2 = ULONG. ops[n_ops] and xs[n_ops] (operand, converted to the type). Output: the
|
|
// variable after construction, after every op, and after each remaining guard is
|
|
// destroyed (innermost first).
|
|
SC_EXPORT int32_t sc_geom_object_tcv(int32_t type, double init, const int64_t* ops,
|
|
const double* xs, int64_t n_ops, double* out, int64_t cap,
|
|
int64_t* out_len)
|
|
{
|
|
if (type < 0 || type > 2 || n_ops < 0)
|
|
return SC_ERR_ARG;
|
|
int64_t depth = 0;
|
|
for (int64_t i = 0; i < n_ops; ++i)
|
|
{
|
|
int64_t op = ops[i];
|
|
if (op < TCV_OPEN || op > TCV_WRITE)
|
|
return SC_ERR_ARG;
|
|
if (type != 0 && !(xs[i] >= 0.0 && xs[i] <= 1e9 && xs[i] == (double)(int64_t)xs[i]))
|
|
return SC_ERR_ARG;
|
|
if (op == TCV_OPEN || op == TCV_OPEN_SET)
|
|
++depth;
|
|
else if (op != TCV_WRITE && depth == 0)
|
|
return SC_ERR_ARG;
|
|
if (op == TCV_CLOSE)
|
|
--depth;
|
|
}
|
|
if (type != 0 && !(init >= 0.0 && init <= 1e9 && init == (double)(int64_t)init))
|
|
return SC_ERR_ARG;
|
|
SC_TRY
|
|
std::vector<double> res;
|
|
int32_t st = type == 0 ? run_tcv<double>(init, ops, xs, n_ops, res)
|
|
: type == 1 ? run_tcv<SmBoolean>((SmBoolean)init, ops, xs, n_ops, res)
|
|
: run_tcv<ULONG>((ULONG)init, ops, xs, n_ops, res);
|
|
if (st != SC_OK)
|
|
return st;
|
|
return sc_copy_out(res.data(), (int64_t)res.size(), out, cap, out_len);
|
|
SC_CATCH
|
|
}
|
|
|
|
namespace
|
|
{
|
|
// The shape of `sel` over `pBrep` (null: no B-rep) whose faces, edges and vertices
|
|
// are numbered by `vAllF` / `vAllE` / `vAllV`; see sc_geom_object_shape.
|
|
int32_t shape_of(SmBrep* pBrep, const std::vector<SmFace*>& vAllF,
|
|
const std::vector<SmEdge*>& vAllE, const std::vector<SmVertex*>& vAllV,
|
|
const int64_t* sel, int64_t nf, int64_t ne, int64_t nv,
|
|
std::vector<double>& res)
|
|
{
|
|
SmTArray<SmBrep*> sB;
|
|
SmTArray<SmFace*> sF;
|
|
SmTArray<SmEdge*> sE;
|
|
SmTArray<SmVertex*> sV;
|
|
if (sel[0])
|
|
sB.Add(pBrep);
|
|
const int64_t* idx = sel + 4;
|
|
for (int64_t i = 0; i < nf; ++i, ++idx)
|
|
{
|
|
if (*idx < 0 || *idx >= (int64_t)vAllF.size())
|
|
return SC_ERR_ARG;
|
|
sF.Add(vAllF[(size_t)*idx]);
|
|
}
|
|
for (int64_t i = 0; i < ne; ++i, ++idx)
|
|
{
|
|
if (*idx < 0 || *idx >= (int64_t)vAllE.size())
|
|
return SC_ERR_ARG;
|
|
sE.Add(vAllE[(size_t)*idx]);
|
|
}
|
|
for (int64_t i = 0; i < nv; ++i, ++idx)
|
|
{
|
|
if (*idx < 0 || *idx >= (int64_t)vAllV.size())
|
|
return SC_ERR_ARG;
|
|
sV.Add(vAllV[(size_t)*idx]);
|
|
}
|
|
// SmShape dereferences a face's surface and an edge's curve (a listed face's edges
|
|
// included)
|
|
for (ULONG i = 0; i < sF.GetSize(); ++i)
|
|
{
|
|
if (sF[i]->GetSurface() == nullptr)
|
|
return SC_ERR_ARG;
|
|
SmTArray<SmEdge*> vFaceE;
|
|
sF[i]->GetEdges(vFaceE);
|
|
for (ULONG j = 0; j < vFaceE.GetSize(); ++j)
|
|
if (vFaceE[j]->GetCurve() == nullptr)
|
|
return SC_ERR_ARG;
|
|
}
|
|
for (ULONG i = 0; i < sE.GetSize(); ++i)
|
|
if (sE[i]->GetCurve() == nullptr)
|
|
return SC_ERR_ARG;
|
|
if (sel[0])
|
|
{
|
|
for (SmFace* f : vAllF)
|
|
if (f->GetSurface() == nullptr)
|
|
return SC_ERR_ARG;
|
|
for (SmEdge* e : vAllE)
|
|
if (e->GetCurve() == nullptr)
|
|
return SC_ERR_ARG;
|
|
// the last listed object gives the trees their context
|
|
if (nf + ne + nv == 0 && pBrep->GetInfiniteRegion() == nullptr)
|
|
return SC_ERR_ARG;
|
|
}
|
|
SmShape sShape(sB, sF, sE, sV);
|
|
SmTArray<SmFace*> vF;
|
|
SmTArray<SmEdge*> vE;
|
|
SmTArray<SmVertex*> vV;
|
|
sShape.GetFaces(vF);
|
|
sShape.GetEdges(vE);
|
|
sShape.GetVertices(vV);
|
|
res.push_back((double)vF.GetSize());
|
|
res.push_back((double)vE.GetSize());
|
|
res.push_back((double)vV.GetSize());
|
|
for (ULONG i = 0; i < vF.GetSize(); ++i)
|
|
res.push_back((double)index_of(vAllF, vF[i]));
|
|
for (ULONG i = 0; i < vE.GetSize(); ++i)
|
|
res.push_back((double)index_of(vAllE, vE[i]));
|
|
for (ULONG i = 0; i < vV.GetSize(); ++i)
|
|
res.push_back((double)index_of(vAllV, vV[i]));
|
|
put_tree(res, sShape.GetVertexTree());
|
|
put_tree(res, sShape.GetCurveTree());
|
|
put_tree(res, sShape.GetSurfaceTree());
|
|
for (SmFace* f : vAllF)
|
|
res.push_back(sShape.IsInShape(f) ? 1.0 : 0.0);
|
|
for (SmEdge* e : vAllE)
|
|
res.push_back(sShape.IsInShape(e) ? 1.0 : 0.0);
|
|
for (SmVertex* v : vAllV)
|
|
res.push_back(sShape.IsInShape(v) ? 1.0 : 0.0);
|
|
return 0;
|
|
}
|
|
} // namespace
|
|
|
|
// SmShape over the B-rep of the shared fixture `path` / `block` (`n_block == 0`: no
|
|
// B-rep, all lists empty). sel = (whole_brep, n_f, n_e, n_v, face indices..., edge
|
|
// indices..., vertex indices...) into the fixture walk's face / edge / vertex lists.
|
|
// Output: (#faces, #edges, #vertices of the shape, their walk indices in shape order),
|
|
// each tree (node count, object count, has top, top box), then IsInShape of every
|
|
// face, edge and vertex of the B-rep.
|
|
SC_EXPORT int32_t sc_geom_object_shape(const char* path, const double* block,
|
|
int64_t n_block, const int64_t* sel, int64_t n_sel,
|
|
double* out, int64_t cap, int64_t* out_len)
|
|
{
|
|
if (n_block < 0 || (n_block > 0 && (path == nullptr || block == nullptr)) || sel == nullptr ||
|
|
n_sel < 4 || out_len == nullptr)
|
|
return SC_ERR_ARG;
|
|
int64_t nf = sel[1], ne = sel[2], nv = sel[3];
|
|
if (sel[0] < 0 || sel[0] > 1 || nf < 0 || ne < 0 || nv < 0 || 4 + nf + ne + nv != n_sel)
|
|
return SC_ERR_ARG;
|
|
if (n_block == 0 && (sel[0] != 0 || nf + ne + nv != 0))
|
|
return SC_ERR_ARG;
|
|
SC_TRY
|
|
std::vector<double> res;
|
|
int32_t rc = 0;
|
|
if (n_block == 0)
|
|
rc = shape_of(nullptr, {}, {}, {}, sel, nf, ne, nv, res);
|
|
else
|
|
rc = sc_topology_fixture::apply(path, block, n_block,
|
|
[&](const sc_topology_fixture::View& g) -> int32_t {
|
|
return shape_of(&g.brep, g.faces, g.edges,
|
|
g.vertices, sel, nf, ne, nv, res);
|
|
});
|
|
if (rc != 0)
|
|
return rc;
|
|
return sc_copy_out(res.data(), (int64_t)res.size(), out, cap, out_len);
|
|
SC_CATCH
|
|
}
|
|
|
|
// The fixture B-rep's GetFaces, GetEdges and GetVertices orders as fixture walk indices:
|
|
// (n, indices...) per kind (the Rust import's list orders).
|
|
SC_EXPORT int32_t sc_geom_object_list_orders(const char* path, const double* block,
|
|
int64_t n_block, double* out, int64_t cap,
|
|
int64_t* out_len)
|
|
{
|
|
if (path == nullptr || block == nullptr || n_block < 1 || out_len == nullptr)
|
|
return SC_ERR_ARG;
|
|
SC_TRY
|
|
std::vector<double> res;
|
|
int32_t rc = sc_topology_fixture::apply(
|
|
path, block, n_block, [&](const sc_topology_fixture::View& g) -> int32_t {
|
|
SmTArray<SmFace*> vF;
|
|
SmTArray<SmEdge*> vE;
|
|
SmTArray<SmVertex*> vV;
|
|
g.brep.GetFaces(vF);
|
|
g.brep.GetEdges(vE);
|
|
g.brep.GetVertices(vV);
|
|
res.push_back((double)vF.GetSize());
|
|
for (ULONG i = 0; i < vF.GetSize(); ++i)
|
|
res.push_back((double)index_of(g.faces, vF[i]));
|
|
res.push_back((double)vE.GetSize());
|
|
for (ULONG i = 0; i < vE.GetSize(); ++i)
|
|
res.push_back((double)index_of(g.edges, vE[i]));
|
|
res.push_back((double)vV.GetSize());
|
|
for (ULONG i = 0; i < vV.GetSize(); ++i)
|
|
res.push_back((double)index_of(g.vertices, vV[i]));
|
|
return 0;
|
|
});
|
|
if (rc != 0)
|
|
return rc;
|
|
return sc_copy_out(res.data(), (int64_t)res.size(), out, cap, out_len);
|
|
SC_CATCH
|
|
}
|