// SPDX-License-Identifier: Apache-2.0 // difftest shim for unit `topology.loopuse`: the SmLoop and SmLoopuse members owned by // the unit (source/SMLib/src/SmLoop.cpp, source/SMLib/inc/SmLoop.h, // source/SMLib/src/SmLoopuse.cpp, source/SMLib/inc/SmLoopuse.h) on real SmBrep objects. // // One entry point, sc_topology_loopuse_eval(op, path, in, n_in, out, cap, out_len). Every // op starts with the B-rep block of unit topology.edgeuse_p1 (built and exported by the // shared sc_edgeuse_graph.h; the export itself is topology.edgeuse_p1's Export op). // Layouts are documented in crates/solidcore-ffi/src/units/topology_loopuse.rs, which // validates every input. Outputs start with `0`, or `-1 status` when the B-rep could not // be built. In an output slot `-2` is "upstream would dereference null (or read memory // of the wrong kind)" and `-3` "not run": the op would reach a callee that is unported // on the Rust side (whose cases are deferred), so the shim does not call it. // // The guards below (ok_*, has_prim, ok_sector) mirror what upstream dereferences without // checking; the sector guard is that of the topology.vertexuse shim. #include "sc_shim.h" #include "StdAfx.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sc_edgeuse_graph.h" namespace { enum OpLu : int32_t { OP_LU_QUERY = 1, OP_LU_LISTS = 2, OP_LU_CONNECTED = 3, OP_LOOP_QUERY = 4, OP_LOOP_CONNECTED = 5, OP_CLOSED3D = 6, OP_MISSING_SEAM = 7, OP_UV_CORNERS = 8, OP_FLIP = 9, OP_COLLECT = 10, OP_SET_EDGEUSE = 11, OP_SET_ORIENT = 12, OP_SET_LOOP = 13, OP_FACE_FU = 14, }; const double ST_NULL_DEREF = -2.0; const double ST_NOT_RUN = -3.0; // The attribute id the LuLists op attaches to its selected objects. const ULONG ATTR_ID = 777; const SM_TYPE KIND_TYPES[] = {SmLoopuse_TYPE, SmLoop_TYPE, SmTopology_TYPE, SmEdgeuse_TYPE, SmOwningTopology_TYPE, SmAObject_TYPE, SmObject_TYPE, SmUnknown_TYPE, 0}; void put_kinds(std::vector& o, const SmObject* p) { for (const SM_TYPE* t = KIND_TYPES; *t != 0; t++) o.push_back(flg(p->IsKindOf(*t))); } // A string as its length and character codes. void put_str(std::vector& o, const TCHAR* s) { std::vector c; for (const TCHAR* q = s; q && *q; q++) c.push_back((double)*q); o.push_back((double)c.size()); o.insert(o.end(), c.begin(), c.end()); } struct FcPeek : SmFace { static SmFaceuse* SmFace::*fu() { return &FcPeek::m_pFU; } }; struct EdTolPeek : SmEdge { static SmZoneTol3d SmEdge::*tol() { return &EdTolPeek::m_sZoneTol3d; } }; struct EuTypePeek : SmEdgeuse { static SM_TYPE SmEdgeuse::*type() { return &EuTypePeek::m_tEdgeuseType; } }; // The links the mutating ops change, restored before the B-rep is deleted (its // destructor walks them; a loop use left pointing at another loop, say, is freed twice). struct Snapshot { struct Lu { SM_TYPE type; SmTopology* start; SmOrientType orient; SmLoop* loop; }; std::vector lus; std::vector> eus; std::vector vus; void take(Graph& g) { for (SmLoopuse* p : g.loopuses) lus.push_back({p->*LuPeek::type(), p->*LuPeek::start(), p->*LuPeek::orient(), p->*LuPeek::loop()}); for (SmEdgeuse* p : g.edgeuses) eus.push_back({p->*EuTypePeek::type(), p->*EuPeek::sorlu()}); for (SmVertexuse* p : g.vertexuses) vus.push_back(p->*VuPeek::owner()); } void restore(Graph& g) { for (size_t i = 0; i < lus.size(); i++) { SmLoopuse* p = g.loopuses[i]; p->*LuPeek::type() = lus[i].type; p->*LuPeek::start() = lus[i].start; p->*LuPeek::orient() = lus[i].orient; p->*LuPeek::loop() = lus[i].loop; } for (size_t i = 0; i < eus.size(); i++) { g.edgeuses[i]->*EuTypePeek::type() = eus[i].first; g.edgeuses[i]->*EuPeek::sorlu() = eus[i].second; } for (size_t i = 0; i < vus.size(); i++) g.vertexuses[i]->*VuPeek::owner() = vus[i]; } }; SmTopology* start_of(SmLoopuse* p) { return p->*LuPeek::start(); } SM_TYPE type_of(SmLoopuse* p) { return p->*LuPeek::type(); } // The CCW walk of an edge loop use (the walk of a vertex loop use is empty); `closes` // false when it neither reaches NULL nor returns to its start within the B-rep's edge // use count (upstream loops forever). std::vector ccw_walk(SmLoopuse* p, size_t n_eu, bool& closes) { std::vector v; closes = true; if (type_of(p) == SmVertexuse_TYPE) return v; SmEdgeuse* start = SM_CAST_PTR(SmEdgeuse, start_of(p)); SmEdgeuse* q = start; while (q != nullptr) { if (v.size() > n_eu) { closes = false; return v; } v.push_back(q); q = q->GetCCWEdgeuse(); if (q == start) break; } return v; } // GetMate needs the edge's primary edge use (loop edge uses). bool has_prim(SmEdgeuse* e) { return e->GetEdgeuseType() == SmShell_TYPE || (e->GetEdge() && e->GetEdge()->GetPrimaryEdgeuse()); } // ---- vertex use guards (copy of the topology.vertexuse shim's) ------------------------- SmTopology* owner_of(SmVertexuse* p) { return p->*VuPeek::owner(); } // Every pointer a loop edgeuse's UV trim curve creation dereferences: the edge with its // curve, the faceuse, face and surface, the vertexuse, the primary edgeuse with its mate and // both their vertexuses (SetUVTrimCurve), the edge's two vertices (as in topology.edgeuse_p3). bool loop_safe(SmEdgeuse* p) { if (p == nullptr || !p->IsLoopEdgeuse() || !p->GetEdge() || !p->GetEdge()->GetCurve() || !p->GetFaceuse() || !p->GetFace() || !p->GetFace()->GetSurface() || !p->GetVertexuse() || !has_prim(p)) return false; SmEdgeuse* prim = p->GetEdge()->GetPrimaryEdgeuse(); SmEdgeuse* pm = prim->GetMate(); if (pm == nullptr || prim->GetVertexuse() == nullptr || pm->GetVertexuse() == nullptr) return false; SmEdgeuse* mate = p->GetMate(); if (mate && mate->GetVertexuse() == nullptr) return false; return p->GetEdge()->GetStartVertex() != nullptr && p->GetEdge()->GetEndVertex() != nullptr; } bool ok_faceuse(SmVertexuse* p) { return !p->IsLoopVertexuse() || owner_of(p) != nullptr; } bool ok_loopuse(SmVertexuse* p) { return !p->IsEdgeVertexuse() || owner_of(p) != nullptr; } bool ok_shell(SmVertexuse* p) { if (!p->IsLoopVertexuse() || owner_of(p) == nullptr) return true; return ((SmLoopuse*)owner_of(p))->GetFaceuse() != nullptr; } bool ok_sector(SmVertexuse* p) { if (p->IsShellVertexuse()) return true; if (!ok_loopuse(p) || !ok_faceuse(p)) return false; SmVertex* v = p->GetVertex(); SmFaceuse* fu = p->GetFaceuse(); SmFace* f = fu ? fu->GetFace() : nullptr; if (v == nullptr || f == nullptr || f->GetSurface() == nullptr) return true; if (!p->IsEdgeVertexuse()) return true; SmEdgeuse* beg = p->GetEdgeuse(); if (beg->IsLoopEdgeuse()) { if (beg->GetVertexuse() == nullptr || !has_prim(beg)) return false; if (beg->GetVertexuse()->GetVertex() != v && (beg->GetMate() == nullptr || beg->GetMate()->GetVertexuse() == nullptr)) return false; } SmEdgeuse* end = beg->GetCornerMateEdgeuse(v); if (end == nullptr || end->GetVertexuse() == nullptr) return false; SmVertexuse* evu = end->GetVertexuse(); if (evu->GetVertex() != v) { if (!has_prim(end) || end->GetMate() == nullptr || end->GetMate()->GetVertexuse() == nullptr) return false; evu = end->GetMate()->GetVertexuse(); } if (evu->GetVertex() == nullptr || !ok_faceuse(evu)) return false; return p->GetLoopuse() != nullptr || fu->GetOrientation() == SM_OT_OPPOSITE; } // SmLoopuse::GetVertices, reached by SmVertex::IsConnectedTo with a loop or loop use // target: a vertex-use loop use reads its start, an edge loop use the vertex use of // every edge use of its CCW chain (null = no loop use, dereferenced through SmLoop). const int64_t MAX_LU_WALK = 100000; bool ok_lu_vertices(SmLoopuse* lu) { if (lu == nullptr) return false; SM_TYPE ty = lu->*LuPeek::type(); SmTopology* start = lu->*LuPeek::start(); if (ty == SmVertexuse_TYPE) return start != nullptr; if (ty != SmEdgeuse_TYPE) return start == nullptr; SmEdgeuse* eu = (SmEdgeuse*)start; for (int64_t k = 0; eu != nullptr; k++) { if (k > MAX_LU_WALK || eu->GetVertexuse() == nullptr) return false; eu = eu->GetCCWEdgeuse(); if (eu == (SmEdgeuse*)start) return true; } return true; } // SmVertex::IsConnectedTo / SmEdge::IsConnectedTo reached from the loop use `lu` (of // `self`, a loop or the loop use): the vertex walks the loop use's vertices, the edge // its edges (through SmLoop::m_pLU for a loop). bool ok_ve_connected(SmLoopuse* lu, int64_t ty) { if (ty == SmVertex_TYPE) return ok_lu_vertices(lu); return lu != nullptr; } // SmVertexuse::IsConnectedTo of `vu` against a loop or loop use target. double vu_connected(SmVertexuse* vu, SmTopology* t) { return ok_loopuse(vu) ? flg(vu->IsConnectedTo(t)) : ST_NULL_DEREF; } // SmEdgeuse::IsConnectedTo of `eu`: a face target dereferences a loop edge use's face use. bool ok_eu_connected(SmEdgeuse* eu, int64_t ty) { if (ty != SmFace_TYPE || !eu->IsLoopEdgeuse()) return true; SmLoopuse* lu = eu->GetLoopuse(); return lu == nullptr || lu->GetFaceuse() != nullptr; } // ---- outputs ------------------------------------------------------------------------------ // The loop/edge/vertex use links the mutating ops change, in the pre-op traversal order: // per loop use `type start orientation loop faceuse`, per edge use `owner_type owner`, // per vertex use `owner_type owner`. void put_use_state(std::vector& o, Graph& g) { for (SmLoopuse* p : g.loopuses) { SM_TYPE ty = type_of(p); o.push_back((double)ty); SmTopology* s = start_of(p); o.push_back((double)(ty == SmEdgeuse_TYPE ? g.find(6, s) : ty == SmVertexuse_TYPE ? g.find(8, s) : -1)); o.push_back((double)(p->*LuPeek::orient())); o.push_back((double)g.find(5, p->*LuPeek::loop())); o.push_back((double)g.find(2, p->GetOwner())); } for (SmEdgeuse* p : g.edgeuses) { SM_TYPE ty = p->GetEdgeuseType(); o.push_back((double)ty); SmTopology* s = p->*EuPeek::sorlu(); o.push_back((double)(ty == SmLoopuse_TYPE ? g.find(4, s) : ty == SmShell_TYPE ? g.find(1, s) : -1)); } for (SmVertexuse* p : g.vertexuses) { SM_TYPE ty = p->GetVertexuseType(); o.push_back((double)ty); SmTopology* s = owner_of(p); o.push_back((double)(ty == SmEdgeuse_TYPE ? g.find(6, s) : ty == SmLoopuse_TYPE ? g.find(4, s) : ty == SmShell_TYPE ? g.find(1, s) : -1)); } } // The four member lists of a loop use (`GetEdges`, `GetEdgeuses`, `GetVertexuses`, // `GetVertices`), each `-2` where upstream would dereference null or loop forever. void put_lists(std::vector& o, Graph& g, SmLoopuse* p, const ULONG* attr, SmTopology* conn, int64_t conn_ty) { bool closes = true; std::vector walk = ccw_walk(p, g.edgeuses.size(), closes); bool vertex = type_of(p) == SmVertexuse_TYPE; // GetEdges: a null edge is only dereferenced to test the attribute bool ok = closes; for (SmEdgeuse* q : walk) if (attr && q->GetEdge() == nullptr) ok = false; if (ok) { SmTArray v; p->GetEdges(v, (ULONG*)attr); std::vector ix; for (ULONG k = 0; k < v.GetSize(); k++) ix.push_back(g.find(7, v[k])); put_list(o, ix); } else o.push_back(ST_NULL_DEREF); // GetEdgeuses ok = closes; for (SmEdgeuse* q : walk) if (conn && !ok_eu_connected(q, conn_ty)) ok = false; if (ok) { SmTArray v; p->GetEdgeuses(v, (ULONG*)attr, conn); std::vector ix; for (ULONG k = 0; k < v.GetSize(); k++) ix.push_back(g.find(6, v[k])); put_list(o, ix); } else o.push_back(ST_NULL_DEREF); // GetVertexuses: a null vertex use is only dereferenced to test the attribute ok = closes; if (vertex) ok = !(attr && start_of(p) == nullptr); for (SmEdgeuse* q : walk) if (attr && q->GetVertexuse() == nullptr) ok = false; if (ok) { SmTArray v; p->GetVertexuses(v, (ULONG*)attr); std::vector ix; for (ULONG k = 0; k < v.GetSize(); k++) ix.push_back(g.find(8, v[k])); put_list(o, ix); } else o.push_back(ST_NULL_DEREF); // GetVertices: the vertex use is always dereferenced, a null vertex only to test // the attribute ok = closes; if (vertex) { SmVertexuse* vu = (SmVertexuse*)start_of(p); ok = vu != nullptr && !(attr && vu->GetVertex() == nullptr); } for (SmEdgeuse* q : walk) if (q->GetVertexuse() == nullptr || (attr && q->GetVertexuse()->GetVertex() == nullptr)) ok = false; if (ok) { SmTArray v; p->GetVertices(v, (ULONG*)attr); std::vector ix; for (ULONG k = 0; k < v.GetSize(); k++) ix.push_back(g.find(9, v[k])); put_list(o, ix); } else o.push_back(ST_NULL_DEREF); } // IsClosed3d's loop with only the checks: whether upstream dereferences a null edge or // curve before it finishes (`check_all` FALSE stops at the first gap over tolerance). bool closed3d_safe(SmLoopuse* p, size_t n_eu, bool check_all) { if (type_of(p) == SmVertexuse_TYPE) return true; bool closes = true; std::vector eus = ccw_walk(p, n_eu, closes); if (!closes) return false; size_t n = eus.size(); for (size_t i = 0; i < n; i++) { SmEdgeuse* a = eus[i]; SmEdgeuse* b = eus[(i + 1) % n]; if (a->GetEdge() == nullptr || a->GetEdge()->GetCurve() == nullptr || b->GetEdge() == nullptr || b->GetEdge()->GetCurve() == nullptr) return false; if (check_all) continue; SmPoint3d pa(SM_UNDEF_DOUBLE, SM_UNDEF_DOUBLE, SM_UNDEF_DOUBLE), pb = pa; SmExtent1d ia = a->GetEdge()->GetInterval(), ib = b->GetEdge()->GetInterval(); a->GetEdge()->GetCurve()->EvaluatePoint(a->GetOrientation() == SM_OT_SAME ? ia.GetMax() : ia.GetMin(), pa); b->GetEdge()->GetCurve()->EvaluatePoint(b->GetOrientation() == SM_OT_SAME ? ib.GetMin() : ib.GetMax(), pb); // the same tolerance upstream tests against (SmLoopuse.cpp:860) SmXSectTol3d tol = SmTol::GetXSectTol3d(SmTol::GetZoneTol3d(a), SmTol::GetZoneTol3d(b)); if (pa.DistanceBetweenSquared(pb) > tol * tol) return true; } return true; } void put_closed(std::vector& o, Graph& g, SmBoolean r, SmEdgeuse* eu, double gap, double tol) { o.push_back(flg(r)); o.push_back(eu == (SmEdgeuse*)0x1 ? -5.0 : (double)g.find(6, eu)); o.push_back(gap); o.push_back(tol); } // SmLoop::IsConnectedTo for shell/region targets: what it dereferences, in its order. double loop_connected_shell(SmLoop* l, SmTopology* t, bool region) { SmLoopuse* lu = l->GetLoopuse(); if (lu == nullptr || lu->GetFaceuse() == nullptr || lu->GetFaceuse()->GetFace() == nullptr) return ST_NULL_DEREF; SmFaceuse *f1, *f2; lu->GetFaceuse()->GetFace()->GetFaceuses(f1, f2); if (f1 == nullptr || (region && f1->GetShell() == nullptr)) return ST_NULL_DEREF; bool first = region ? f1->GetShell()->GetRegion() == (SmRegion*)t : f1->GetShell() == (SmShell*)t; if (!first && (f2 == nullptr || (region && f2->GetShell() == nullptr))) return ST_NULL_DEREF; return flg(l->IsConnectedTo(t)); } SmTopology* read_target(Rd& rd, Graph& g, int64_t& ty) { ty = rd.idx(); int64_t i = rd.idx(); return target_of(g, ty, i); } void run_graph_op(int32_t op, Rd& rd, Graph& g, size_t n_eu, std::vector& o); void run_op(int32_t op, Rd& rd, const char* path, std::vector& o) { SmStatus bst; Built b; b.brep = build(rd, path, bst); if (b.brep == nullptr) { o.push_back(-1.0); o.push_back(st(bst)); return; } o.push_back(0.0); Graph g; walk(b.brep, g); size_t n_eu = g.edgeuses.size(); Snapshot snap; snap.take(g); run_graph_op(op, rd, g, n_eu, o); snap.restore(g); } void run_graph_op(int32_t op, Rd& rd, Graph& g, size_t n_eu, std::vector& o) { switch (op) { case OP_LU_QUERY: { SmLoopuse* p = g.loopuses[(size_t)rd.idx()]; o.push_back((double)g.find(8, p->GetVertexuse())); o.push_back((double)g.find(2, p->GetFaceuse())); o.push_back((double)g.find(5, p->GetLoop())); o.push_back((double)p->GetOrientation()); o.push_back((double)g.find(4, p->GetOtherLoopuse())); SmTopology* s = p->GetEUorVU(); SM_TYPE ty = p->GetLoopuseType(); o.push_back(s == nullptr ? -1.0 : (double)ty); o.push_back(s == nullptr ? -1.0 : (double)g.typed(ty, s)); o.push_back((double)ty); o.push_back(flg(p->IsVertexLoopuse())); o.push_back(flg(p->IsEdgeLoopuse())); o.push_back((double)g.find(1, p->GetShell())); bool closes = true; ccw_walk(p, n_eu, closes); o.push_back(closes ? flg(p->IsClosedPtrs()) : ST_NULL_DEREF); o.push_back((double)p->GetType()); put_kinds(o, p); put_str(o, p->GetTypeString()); put_str(o, p->GetClassString()); put_lists(o, g, p, nullptr, nullptr, 0); break; } case OP_LU_LISTS: { SmLoopuse* p = g.loopuses[(size_t)rd.idx()]; bool use_attr = rd.flag(); int64_t n = rd.idx(); for (int64_t k = 0; k < n; k++) { int64_t ty = rd.idx(); int64_t i = rd.idx(); SmTopology* t = target_of(g, ty, i); if (t != nullptr && t->FindAttribute(ATTR_ID) == nullptr) t->AddAttribute(new (the_context()) SmAttribute(ATTR_ID, SM_AB_STANDALONE_COPY)); } int64_t cty = 0; SmTopology* conn = read_target(rd, g, cty); ULONG id = ATTR_ID; put_lists(o, g, p, use_attr ? &id : nullptr, conn, cty); break; } case OP_LU_CONNECTED: { SmLoopuse* p = g.loopuses[(size_t)rd.idx()]; int64_t ty = 0; SmTopology* t = read_target(rd, g, ty); if (t == nullptr) o.push_back(flg(p->IsConnectedTo(t))); else if (ty == SmVertex_TYPE || ty == SmEdge_TYPE) o.push_back(ok_ve_connected(p, ty) ? flg(p->IsConnectedTo(t)) : ST_NULL_DEREF); else if (ty == SmVertexuse_TYPE) o.push_back(vu_connected((SmVertexuse*)t, p)); else if (ty == SmEdgeuse_TYPE) o.push_back(flg(p->IsConnectedTo(t))); else if (ty == SmFace_TYPE && p->GetFaceuse() == nullptr) o.push_back(ST_NULL_DEREF); else o.push_back(flg(p->IsConnectedTo(t))); break; } case OP_LOOP_QUERY: { SmLoop* l = g.loops[(size_t)rd.idx()]; SmLoopuse* lu = l->GetLoopuse(); o.push_back((double)g.find(4, lu)); bool face_ok = lu != nullptr && lu->GetFaceuse() != nullptr; o.push_back(face_ok ? (double)g.find(3, l->GetFace()) : ST_NULL_DEREF); if (lu != nullptr) { SmLoopuse *a, *c; l->GetLoopuses(a, c); o.push_back((double)g.find(4, a)); o.push_back((double)g.find(4, c)); } else { o.push_back(ST_NULL_DEREF); o.push_back(ST_NULL_DEREF); } bool outer_ok = face_ok; if (outer_ok) { SmTArray lus; lu->GetFaceuse()->GetLoopuses(lus); outer_ok = lus.GetSize() > 0; } o.push_back(outer_ok ? flg(l->IsOuterLoop()) : ST_NULL_DEREF); bool closes = true; if (lu != nullptr) ccw_walk(lu, n_eu, closes); o.push_back(closes ? flg(l->IsClosedPtrs()) : ST_NULL_DEREF); o.push_back((double)l->GetType()); o.push_back((double)SmLoop::GetClassType()); put_kinds(o, l); put_str(o, l->GetTypeString()); put_str(o, l->GetClassString()); if (lu == nullptr) { for (int k = 0; k < 3; k++) o.push_back(ST_NULL_DEREF); break; } // SmLoop::GetVertices / GetEdges / GetEdgeuses (guards of the loop use lists) std::vector lists; put_lists(lists, g, lu, nullptr, nullptr, 0); // put_lists' order is edges, edge uses, vertex uses, vertices: run the loop's // own calls where the loop use's are defined size_t at = 0; auto skip = [&lists, &at]() -> bool { if (lists[at] == ST_NULL_DEREF) { at += 1; return false; } at += 1 + (size_t)lists[at]; return true; }; bool e_ok = skip(); bool eu_ok = skip(); skip(); bool v_ok = skip(); if (v_ok) { SmTArray v; l->GetVertices(v); std::vector ix; for (ULONG k = 0; k < v.GetSize(); k++) ix.push_back(g.find(9, v[k])); put_list(o, ix); } else o.push_back(ST_NULL_DEREF); if (e_ok) { SmTArray v; l->GetEdges(v); std::vector ix; for (ULONG k = 0; k < v.GetSize(); k++) ix.push_back(g.find(7, v[k])); put_list(o, ix); } else o.push_back(ST_NULL_DEREF); if (eu_ok) { SmTArray v; l->GetEdgeuses(v); std::vector ix; for (ULONG k = 0; k < v.GetSize(); k++) ix.push_back(g.find(6, v[k])); put_list(o, ix); } else o.push_back(ST_NULL_DEREF); break; } case OP_LOOP_CONNECTED: { SmLoop* l = g.loops[(size_t)rd.idx()]; int64_t ty = 0; SmTopology* t = read_target(rd, g, ty); SmLoopuse* lu = l->GetLoopuse(); bool face_ok = lu != nullptr && lu->GetFaceuse() != nullptr; if (t == nullptr) o.push_back(flg(l->IsConnectedTo(t))); else if (ty == SmVertex_TYPE || ty == SmEdge_TYPE) o.push_back(ok_ve_connected(lu, ty) ? flg(l->IsConnectedTo(t)) : ST_NULL_DEREF); else if (ty == SmVertexuse_TYPE) o.push_back(vu_connected((SmVertexuse*)t, l)); else if ((ty == SmFace_TYPE || ty == SmFaceuse_TYPE) && !face_ok) o.push_back(ST_NULL_DEREF); else if (ty == SmShell_TYPE || ty == SmRegion_TYPE) o.push_back(loop_connected_shell(l, t, ty == SmRegion_TYPE)); else o.push_back(flg(l->IsConnectedTo(t))); break; } case OP_CLOSED3D: { bool of_loop = rd.flag(); int64_t i = rd.idx(); SmBoolean all = rd.bflag(); bool want_eu = rd.flag(); bool want_gap = rd.flag(); bool want_tol = rd.flag(); bool set_tol = rd.flag(); double edge_tol = rd.num(); // tolerance-limit cases: every edge's stored ZoneTol3d (`operator=` keeps fabs) if (set_tol) for (SmEdge* e : g.edges) e->*EdTolPeek::tol() = edge_tol; SmLoopuse* p = of_loop ? g.loops[(size_t)i]->GetLoopuse() : g.loopuses[(size_t)i]; if (p != nullptr && !closed3d_safe(p, n_eu, all != FALSE)) { o.push_back(ST_NULL_DEREF); break; } // the out-params start at sentinels (-5 edge use, -7 gap, 777 tolerance), so an // unwritten one is visible SmEdgeuse* eu = (SmEdgeuse*)0x1; double gap = -7.0; SmXSectTol3d tol = 777.0; // a tolerance is stored as fabs SmBoolean r = of_loop ? g.loops[(size_t)i]->IsClosed3d(all, want_eu ? &eu : NULL, want_gap ? &gap : NULL, want_tol ? &tol : NULL) : p->IsClosed3d(all, want_eu ? &eu : NULL, want_gap ? &gap : NULL, want_tol ? &tol : NULL); put_closed(o, g, r, eu, gap, (double)tol); break; } case OP_MISSING_SEAM: { SmLoopuse* p = g.loopuses[(size_t)rd.idx()]; SmFaceuse* fu = p->GetFaceuse(); SmSurface* s = fu && fu->GetFace() ? fu->GetFace()->GetSurface() : nullptr; bool closes = true; std::vector walk_v = ccw_walk(p, n_eu, closes); if (s != nullptr && (s->IsClosed(s->GetNaturalUVDomain(), SM_SP_U) || s->IsClosed(s->GetNaturalUVDomain(), SM_SP_V))) { if (!closes) { o.push_back(ST_NULL_DEREF); break; } // the sectors upstream computes, in its order, until one is missing a seam std::vector vus; if (type_of(p) == SmVertexuse_TYPE) vus.push_back((SmVertexuse*)start_of(p)); for (SmEdgeuse* q : walk_v) vus.push_back(q->GetVertexuse()); bool safe = true; for (SmVertexuse* vu : vus) { if (vu == nullptr) continue; if (!ok_sector(vu)) { safe = false; break; } SmUVSectorIO io; vu->ComputeUVSector(io, TRUE); if (io.m_bSectorMissingSeamU || io.m_bSectorMissingSeamV) break; } if (!safe) { o.push_back(ST_NULL_DEREF); break; } } o.push_back(flg(p->IsMissingSeamAtVertices())); break; } case OP_UV_CORNERS: { SmLoop* l = g.loops[(size_t)rd.idx()]; SmPoint2d a = rd.uv(), c = rd.uv(); double tol = rd.num(); SmLoopuse* lu = l->GetLoopuse(); if (lu != nullptr) { bool closes = true; std::vector w = ccw_walk(lu, n_eu, closes); if (!closes) { o.push_back(ST_NULL_DEREF); break; } // GetOrCreateUVTrimCurve on every edge use: it builds a missing UV trim curve, which // dereferences what loop_safe lists (the edge use's face, surface, vertex uses, ...) bool deref_ok = true; for (SmEdgeuse* x : w) { if (!has_prim(x)) { deref_ok = false; break; } if (x->GetUVTrimCurve() == nullptr && x->GetFaceuse() != nullptr && !loop_safe(x)) { deref_ok = false; break; } } if (!deref_ok) { o.push_back(ST_NULL_DEREF); break; } } SmExtent2d dom(a.x, a.y, c.x, c.y); SmTArray edges; SmTArray bnd; SmTArray prm; ULONG r = l->GetUVCornerBoundaries(dom, tol, edges, bnd, prm); o.push_back((double)r); std::vector ix; for (ULONG k = 0; k < edges.GetSize(); k++) ix.push_back(g.find(7, edges[k])); put_list(o, ix); ix.clear(); for (ULONG k = 0; k < bnd.GetSize(); k++) ix.push_back((int64_t)bnd[k]); put_list(o, ix); o.push_back((double)prm.GetSize()); for (ULONG k = 0; k < prm.GetSize(); k++) o.push_back(prm[k]); break; } case OP_FLIP: { SmLoop* l = g.loops[(size_t)rd.idx()]; SmLoopuse* l1 = l->GetLoopuse(); if (l1 == nullptr) { o.push_back(ST_NULL_DEREF); break; } SmLoopuse* l2 = l1->GetOtherLoopuse(); if (l2 != nullptr) { // what the swap reads as the kind of the loop use's own start (the mate's // start of the other kind is memory of the wrong class upstream) SM_TYPE t1 = type_of(l1), t2 = type_of(l2); bool bad = false; if (t1 == SmVertexuse_TYPE) { SmVertexuse* v1 = (SmVertexuse*)start_of(l1); SmTopology* s2 = start_of(l2); if (v1 != nullptr && s2 != nullptr) { if (t2 != SmVertexuse_TYPE) bad = true; else if (!v1->IsLoopVertexuse() || !((SmVertexuse*)s2)->IsLoopVertexuse()) bad = true; } } else if (start_of(l1) != nullptr && start_of(l2) != nullptr && t2 == SmVertexuse_TYPE) bad = true; if (bad) { o.push_back(ST_NULL_DEREF); break; } } o.push_back(st(l->FlipLoopOrientation())); put_use_state(o, g); break; } case OP_COLLECT: { SmLoopuse* p = g.loopuses[(size_t)rd.idx()]; int64_t e = rd.idx(); SmEdgeuse* eu = e >= 0 ? g.edgeuses[(size_t)e] : nullptr; o.push_back(st(p->CollectEdgeuses(eu))); put_use_state(o, g); break; } case OP_SET_EDGEUSE: { SmLoopuse* p = g.loopuses[(size_t)rd.idx()]; int64_t e = rd.idx(); SmEdgeuse* eu = e >= 0 ? g.edgeuses[(size_t)e] : nullptr; o.push_back(st(p->SetEdgeuse(eu))); put_use_state(o, g); break; } case OP_SET_ORIENT: { SmLoopuse* p = g.loopuses[(size_t)rd.idx()]; p->SetOrientation((SmOrientType)rd.idx()); o.push_back((double)p->GetOrientation()); put_use_state(o, g); break; } case OP_SET_LOOP: { SmLoopuse* p = g.loopuses[(size_t)rd.idx()]; int64_t i = rd.idx(); p->SetLoop(i >= 0 ? g.loops[(size_t)i] : nullptr); o.push_back((double)g.find(5, p->GetLoop())); put_use_state(o, g); break; } case OP_FACE_FU: { // the export lacks each face's m_pFU (SmFace::GetFaceuses reads it) std::vector ix; for (SmFace* f : g.faces) ix.push_back(g.find(2, f->*FcPeek::fu())); put_list(o, ix); break; } default: break; } } } // namespace SC_EXPORT int32_t sc_topology_loopuse_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 o; run_op(op, rd, path, o); return sc_copy_out(o.data(), (int64_t)o.size(), out, cap, out_len); SC_CATCH }