mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-08 13:01:52 +00:00
c19f168fb7
Compare raw native mutation state, partial wire lists and defined self-transfer behavior. All 234 transfer/query cases pass before the subsequent safe-cast getter audit correction; dedicated metadata-override cases and remaining owned operations are still pending. Preserve the draft/todo status. Co-Authored-By: OpenAI Codex <noreply@openai.com>
281 lines
14 KiB
C++
281 lines
14 KiB
C++
// SPDX-License-Identifier: Apache-2.0
|
|
// topology.shell: real native shell/region queries on shared B-rep fixtures.
|
|
// No native pointer crosses the C ABI; identities are traversal-order indices.
|
|
#include "sc_shim.h"
|
|
#include "support/topology_fixture.h"
|
|
#include "StdAfx.h"
|
|
#include <SmBrep.h>
|
|
#include <SmRegion.h>
|
|
#include <SmShell.h>
|
|
#include <SmFaceuse.h>
|
|
#include <SmTArray.h>
|
|
#include <algorithm>
|
|
|
|
namespace {
|
|
template<class T> double identity(const std::vector<T*>& values, T* p) {
|
|
if (!p) return -1.0;
|
|
auto found = std::find(values.begin(), values.end(), p);
|
|
if (found == values.end()) throw 1;
|
|
return (double)(found - values.begin());
|
|
}
|
|
template<class T> void list(std::vector<double>& out, const SmTArray<T*>& values,
|
|
const std::vector<T*>& identities) {
|
|
out.push_back((double)values.GetSize());
|
|
for (ULONG i = 0; i < values.GetSize(); ++i)
|
|
out.push_back(identity(identities, values[i]));
|
|
}
|
|
}
|
|
|
|
SC_EXPORT int32_t sc_topology_shell_query(const char* path, const double* input,
|
|
int64_t count, int32_t set_void, int32_t void_state,
|
|
double* output, int64_t cap, int64_t* output_len) {
|
|
if (!output_len || cap < 0 || (cap && !output) || (set_void != 0 && set_void != 1))
|
|
return SC_ERR_ARG;
|
|
SC_TRY
|
|
return sc_topology_fixture::apply(path, input, count,
|
|
[&](const sc_topology_fixture::View& fixture) -> int32_t {
|
|
std::vector<double> out;
|
|
out.push_back((double)fixture.shells.size());
|
|
out.push_back((double)fixture.regions.size());
|
|
for (SmShell* shell : fixture.shells) {
|
|
SmTArray<SmFaceuse*> faceuses;
|
|
shell->GetFaceuses(faceuses);
|
|
// GetMatedShells dereferences both pointers unconditionally.
|
|
for (ULONG i = 0; i < faceuses.GetSize(); ++i)
|
|
if (!faceuses[i]->GetMate() || !faceuses[i]->GetMate()->GetShell())
|
|
return SC_ERR_ARG;
|
|
SmTArray<SmShell*> mates;
|
|
if (shell->GetMatedShells(mates) != SM_SUCCESS) return SC_ERR_ARG;
|
|
out.push_back((double)shell->GetType());
|
|
out.push_back((double)shell->IsVertexShell());
|
|
out.push_back((double)shell->IsWireShell());
|
|
out.push_back((double)shell->IsFaceuseShell());
|
|
out.push_back((double)shell->IsUnknownShell());
|
|
out.push_back((double)shell->IsClosed());
|
|
out.push_back(identity(fixture.regions, shell->GetRegion()));
|
|
out.push_back(shell->GetBrep() == &fixture.brep ? 1.0 : 0.0);
|
|
out.push_back(identity(fixture.faceuses, shell->GetFirstFaceuse()));
|
|
out.push_back(identity(fixture.edges, shell->GetWireEdge()));
|
|
out.push_back(identity(fixture.vertices, shell->GetVertex()));
|
|
for (SM_TYPE type : {16013L, 16012L, 16000L, 16999L, -1L})
|
|
out.push_back((double)shell->IsKindOf(type));
|
|
list(out, faceuses, fixture.faceuses);
|
|
list(out, mates, fixture.shells);
|
|
}
|
|
for (SmRegion* region : fixture.regions) {
|
|
if (set_void) region->SetIsVoid(void_state);
|
|
out.push_back((double)region->GetType());
|
|
out.push_back((double)region->IsVoid());
|
|
out.push_back((double)region->IsInfiniteRegion());
|
|
out.push_back(region->GetBrep() == &fixture.brep ? 1.0 : 0.0);
|
|
for (SM_TYPE type : {16013L, 16012L, 16000L, 16999L, -1L})
|
|
out.push_back((double)region->IsKindOf(type));
|
|
SmTArray<SmShell*> shells;
|
|
region->GetShells(shells);
|
|
list(out, shells, fixture.shells);
|
|
}
|
|
return sc_copy_out(out.data(), (int64_t)out.size(), output, cap, output_len);
|
|
});
|
|
SC_CATCH
|
|
}
|
|
|
|
SC_EXPORT int32_t sc_topology_shell_wire_edges(const char* path, const double* input,
|
|
int64_t count, double* output, int64_t cap, int64_t* output_len) {
|
|
if (!output_len || cap < 0 || (cap && !output)) return SC_ERR_ARG;
|
|
SC_TRY
|
|
return sc_topology_fixture::apply(path, input, count,
|
|
[&](const sc_topology_fixture::View& fixture) -> int32_t {
|
|
std::vector<double> out;
|
|
out.push_back((double)fixture.shells.size());
|
|
for (SmShell* shell : fixture.shells) {
|
|
if (shell->IsWireShell() && !shell->GetWireEdge()) return SC_ERR_ARG;
|
|
SmTArray<SmEdge*> wires;
|
|
const SmStatus status = shell->GetWireEdges(wires);
|
|
out.push_back(status == SM_SUCCESS ? 0.0 : (double)status);
|
|
list(out, wires, fixture.edges);
|
|
}
|
|
return sc_copy_out(out.data(), (int64_t)out.size(), output, cap, output_len);
|
|
});
|
|
SC_CATCH
|
|
}
|
|
// Raw mutation snapshots: independent of shell operations being compared.
|
|
#include <SmContext.h>
|
|
#include <SmRegion.h>
|
|
#include <SmEdgeuse.h>
|
|
#include <SmVertexuse.h>
|
|
#include <memory>
|
|
#include <functional>
|
|
|
|
namespace transfer_detail {
|
|
struct NodePeek : SmTopology {
|
|
static SmOwningTopology* SmTopology::*owner() { return &NodePeek::m_pListOwner; }
|
|
static SmTopology* SmTopology::*next() { return &NodePeek::m_pNext; }
|
|
static SmTopology* SmTopology::*last() { return &NodePeek::m_pLast; }
|
|
};
|
|
struct OwnerPeek : SmOwningTopology {
|
|
static SmTopology* SmOwningTopology::*head() { return &OwnerPeek::m_pList; }
|
|
static ULONG SmOwningTopology::*size() { return &OwnerPeek::m_lListSize; }
|
|
};
|
|
struct ShellPeek : SmShell {
|
|
static SM_TYPE SmShell::*type() { return &ShellPeek::m_tShellType; }
|
|
};
|
|
struct EuPeek : SmEdgeuse {
|
|
static SmTopology* SmEdgeuse::*owner() { return &EuPeek::m_pSorLU; }
|
|
};
|
|
struct VuPeek : SmVertexuse {
|
|
static SmTopology* SmVertexuse::*owner() { return &VuPeek::m_pSorLUorEU; }
|
|
};
|
|
// Expose the real SmShell constructor, with its real context and region owner.
|
|
struct EmptyShell : SmShell { EmptyShell() : SmShell() {} };
|
|
|
|
struct Restore {
|
|
std::vector<std::function<void()>> actions;
|
|
Restore(const sc_topology_fixture::View& view, const std::vector<SmShell*>& shells) {
|
|
for (SmShell* p : shells) {
|
|
const auto type=p->*ShellPeek::type();
|
|
const auto head=p->*OwnerPeek::head();
|
|
const auto size=p->*OwnerPeek::size();
|
|
actions.push_back([p,type,head,size] {
|
|
p->*ShellPeek::type()=type;
|
|
p->*OwnerPeek::head()=head;
|
|
p->*OwnerPeek::size()=size;
|
|
});
|
|
}
|
|
for (SmFaceuse* p : view.faceuses) {
|
|
const auto owner=p->*NodePeek::owner();
|
|
const auto next=p->*NodePeek::next();
|
|
const auto last=p->*NodePeek::last();
|
|
actions.push_back([p,owner,next,last] {
|
|
p->*NodePeek::owner()=owner; p->*NodePeek::next()=next; p->*NodePeek::last()=last;
|
|
});
|
|
}
|
|
for (SmEdgeuse* p : view.edgeuses) {
|
|
const auto owner=p->*EuPeek::owner();
|
|
actions.push_back([p,owner] { p->*EuPeek::owner()=owner; });
|
|
}
|
|
for (SmVertexuse* p : view.vertexuses) {
|
|
const auto owner=p->*VuPeek::owner();
|
|
actions.push_back([p,owner] { p->*VuPeek::owner()=owner; });
|
|
}
|
|
}
|
|
~Restore() { for (const auto& action : actions) action(); }
|
|
};
|
|
|
|
void snapshot(std::vector<double>& out, const sc_topology_fixture::View& view,
|
|
const std::vector<SmShell*>& shells) {
|
|
out.insert(out.end(), {(double)shells.size(), (double)view.faceuses.size(),
|
|
(double)view.edgeuses.size(), (double)view.vertexuses.size(), (double)view.regions.size()});
|
|
for (SmShell* p : shells) {
|
|
SmTopology* head=p->*OwnerPeek::head();
|
|
const SM_TYPE kind=head ? head->GetType() : 0;
|
|
double id=-1;
|
|
if (kind==SmFaceuse_TYPE) id=identity(view.faceuses,(SmFaceuse*)head);
|
|
else if (kind==SmEdgeuse_TYPE) id=identity(view.edgeuses,(SmEdgeuse*)head);
|
|
else if (kind==SmVertexuse_TYPE) id=identity(view.vertexuses,(SmVertexuse*)head);
|
|
else if (head) throw 1;
|
|
out.insert(out.end(), {(double)(p->*ShellPeek::type()), (double)(p->*OwnerPeek::size()),
|
|
identity(view.regions,(SmRegion*)(p->*NodePeek::owner())), (double)kind, id});
|
|
}
|
|
for (SmFaceuse* p : view.faceuses) {
|
|
out.insert(out.end(), {identity(shells,(SmShell*)(p->*NodePeek::owner())),
|
|
identity(view.faceuses,(SmFaceuse*)(p->*NodePeek::next())),
|
|
identity(view.faceuses,(SmFaceuse*)(p->*NodePeek::last()))});
|
|
}
|
|
for (SmEdgeuse* p : view.edgeuses) {
|
|
const SM_TYPE kind=p->GetEdgeuseType();
|
|
SmTopology* owner=p->*EuPeek::owner();
|
|
const double id=kind==SmShell_TYPE ? identity(shells,(SmShell*)owner)
|
|
: kind==SmLoopuse_TYPE ? identity(view.loopuses,(SmLoopuse*)owner) : -1;
|
|
out.insert(out.end(), {(double)kind,id});
|
|
}
|
|
for (SmVertexuse* p : view.vertexuses) {
|
|
const SM_TYPE kind=p->GetVertexuseType();
|
|
SmTopology* owner=p->*VuPeek::owner();
|
|
const double id=kind==SmShell_TYPE ? identity(shells,(SmShell*)owner)
|
|
: kind==SmLoopuse_TYPE ? identity(view.loopuses,(SmLoopuse*)owner)
|
|
: kind==SmEdgeuse_TYPE ? identity(view.edgeuses,(SmEdgeuse*)owner) : -1;
|
|
out.insert(out.end(), {(double)kind,id});
|
|
}
|
|
for (SmRegion* p : view.regions) {
|
|
SmTopology* head=p->*OwnerPeek::head();
|
|
out.insert(out.end(), {(double)(p->*OwnerPeek::size()), identity(shells,(SmShell*)head)});
|
|
std::vector<double> members;
|
|
SmTopology* node=head;
|
|
while (node) {
|
|
if (members.size()>=100000 || node->GetType()!=SmShell_TYPE) throw 1;
|
|
members.push_back(identity(shells,(SmShell*)node));
|
|
node=node->*NodePeek::next();
|
|
if (node==head) break;
|
|
}
|
|
out.push_back((double)members.size());
|
|
out.insert(out.end(),members.begin(),members.end());
|
|
}
|
|
}
|
|
}
|
|
|
|
SC_EXPORT int32_t sc_topology_shell_transfer(const char* path, const double* input,
|
|
int64_t count, int32_t extra_empty, int64_t source, int64_t recipient,
|
|
int32_t has_candidates, const int64_t* candidates, int64_t candidate_count,
|
|
int32_t split_faceuse, double* output, int64_t cap, int64_t* output_len) {
|
|
if (!output_len || cap<0 || (cap && !output) || extra_empty<0 || extra_empty>64
|
|
|| source<0 || recipient<0 || (has_candidates!=0 && has_candidates!=1)
|
|
|| candidate_count<0 || candidate_count>64 || (candidate_count && !candidates)
|
|
|| (!has_candidates && candidate_count) || (split_faceuse!=0 && split_faceuse!=1)) return SC_ERR_ARG;
|
|
SC_TRY
|
|
return sc_topology_fixture::apply(path,input,count,
|
|
[&](const sc_topology_fixture::View& view)->int32_t {
|
|
std::vector<SmShell*> shells=view.shells;
|
|
SmRegion* region=view.brep.GetInfiniteRegion();
|
|
if (!region || !view.brep.GetContext()) return SC_ERR_ARG;
|
|
for (int32_t i=0;i<extra_empty;++i) {
|
|
std::unique_ptr<transfer_detail::EmptyShell> shell(
|
|
new (&view.brep) transfer_detail::EmptyShell());
|
|
shells.push_back(shell.get());
|
|
if (region->PostInsert(shell.get())!=SM_SUCCESS) return SC_ERR_ARG;
|
|
shell.release();
|
|
}
|
|
if (split_faceuse) {
|
|
if (view.shells.empty() || extra_empty<1) return SC_ERR_ARG;
|
|
SmShell* original=view.shells.front();
|
|
SmShell* extra=shells[view.shells.size()];
|
|
SmTopology* head=original->GetList();
|
|
if (!original->IsFaceuseShell() || original->GetSize()<2 || !head
|
|
|| head->GetType()!=SmFaceuse_TYPE) return SC_ERR_ARG;
|
|
// Fixture construction uses the already-existing base list
|
|
// operations, never TransferChildrenTo under test.
|
|
if (original->Remove(head)!=SM_SUCCESS) return SC_ERR_ARG;
|
|
extra->*transfer_detail::ShellPeek::type()=SmFaceuse_TYPE;
|
|
if (extra->PostInsert(head)!=SM_SUCCESS) return SC_ERR_ARG;
|
|
}
|
|
if (source>=(int64_t)shells.size() || recipient>=(int64_t)shells.size()) return SC_ERR_ARG;
|
|
SmShell* from=shells[(size_t)source];
|
|
SmShell* to=shells[(size_t)recipient];
|
|
const bool reads_candidates=!(to->GetSize()==0 && from->IsVertexShell())
|
|
&& !(to->GetSize()!=0 && (to->IsVertexShell() || from->IsVertexShell()));
|
|
SmTArray<SmEdge*> wires;
|
|
for (int64_t i=0;i<candidate_count;++i) {
|
|
const auto index=candidates[i];
|
|
if (index < -1 || index >= (int64_t)view.edges.size()) return SC_ERR_ARG;
|
|
SmEdge* edge=index==-1 ? nullptr : view.edges[(size_t)index];
|
|
if (reads_candidates && (!edge || !edge->GetPrimaryEdgeuse())) return SC_ERR_ARG;
|
|
wires.Add(edge);
|
|
}
|
|
// Snapshot the result before restoring canonical links for deletion.
|
|
// Self-transfer is compared, including its detached face-use cycle.
|
|
transfer_detail::Restore restore(view,shells);
|
|
const auto status=from->TransferChildrenTo(to,has_candidates ? &wires : nullptr);
|
|
std::vector<double> out{status==SM_SUCCESS ? 0.0 : (double)status};
|
|
transfer_detail::snapshot(out,view,shells);
|
|
// Exercise both connected-wire traversal and global ownership
|
|
// filtering on the actual post-transfer topology.
|
|
for (SmShell* shell : shells) {
|
|
SmTArray<SmEdge*> after;
|
|
const auto query_status=shell->GetWireEdges(after);
|
|
out.push_back(query_status==SM_SUCCESS ? 0.0 : (double)query_status);
|
|
list(out,after,view.edges);
|
|
}
|
|
return sc_copy_out(out.data(),(int64_t)out.size(),output,cap,output_len);
|
|
});
|
|
SC_CATCH
|
|
}
|