mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-08 13:01:52 +00:00
86e36540dd
Shim mode 7 builds one CreateFaceFromSurface face on an owning SmOffsetSurface of a real SmSurfOfExtrusion (lazy, no NURB), SmPlane or SmBSplineSurface, runs ReplaceImplicitOffsets and records the status, the result surface type, domains and sampled points. 12 fixtures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MNXLXj2sJf943y2dDF36Vy
1217 lines
56 KiB
C++
1217 lines
56 KiB
C++
// SPDX-License-Identifier: Apache-2.0
|
|
// Native adapter for offset.offset_executive_p1 (PR #175).
|
|
// Upstream: source/SMLib/inc/SmOffsetExecutive.h, src/SmOffsetExecutive.cpp.
|
|
//
|
|
// Rust API/protocol: crates/solidcore-ffi/src/units/offset_offset_executive_p1.rs.
|
|
// config -> default/custom geometry ctor views, native executive ctor/dtor,
|
|
// state setters/getters, SmRelation/GetOffsetEdgeuse.
|
|
// Config input begins distance, approx_tol, tangency_tol_radians, operation.
|
|
// Its constructor-state and final-state snapshots both use the supplied geometry;
|
|
// other modes retain the native default 2-degree tangency constructor behavior.
|
|
// synchronize -> SyncronizeOffsetEdgeuses, which calls all four owned static helpers.
|
|
// extend -> GetCreateExtendedSurface with real SmPlane/SmBSplineSurface geometry.
|
|
// Extension takes an internal nonnegative magnitude, not a public signed offset:
|
|
// SmOffsetExecutive.cpp:114-120,206-223 pass fabs(distance) to the geometry creator
|
|
// and preserve the sign via SetInset; SmBrep.cpp:12004 uses bounding-sphere radius/2.
|
|
// Reject negative extension distances before geometry construction as SC_ERR_ARG;
|
|
// both signed zeros remain valid. Config only observes state, so stays sign-agnostic.
|
|
// probe -> independent safe topology paths; not a replacement for arithmetic tests.
|
|
// face tail -> BuildOffsetOfFaceuse's contained-domain branch on a MakeFaceWithCurves
|
|
// plane face, including MapEdgeuses and self-intersection merging.
|
|
// map twin -> MapEdgeuses alone, between two MakeFaceWithCurves rectangle faces.
|
|
// replace -> ReplaceImplicitOffsets on one CreateFaceFromSurface face whose surface is
|
|
// an owning SmOffsetSurface(d, base) of a real SmPlane, SmBSplineSurface or
|
|
// SmSurfOfExtrusion (lazy: no NURB until GetOrCreateGwNurbPointer).
|
|
// Deviations: bounded plain-C fixture protocol, traversal IDs instead of pointers;
|
|
// extension-only nonnegative-distance validation. Native array ordering is unchanged.
|
|
// No implementation file is included; no native numerical/helper code is copied.
|
|
#include "sc_shim.h"
|
|
#include "StdAfx.h"
|
|
#include <SmAxis2Placement.h>
|
|
#include <SmBSplineCurve.h>
|
|
#include <SmBSplineSurface.h>
|
|
#include <SmBrep.h>
|
|
#include <SmContext.h>
|
|
#include <SmEdge.h>
|
|
#include <SmEdgeuse.h>
|
|
#include <SmFace.h>
|
|
#include <SmFaceuse.h>
|
|
#include <SmLine.h>
|
|
#include <SmLoop.h>
|
|
#include <SmNurbsCrv.h>
|
|
#include <SmNurbsSrf.h>
|
|
#include <SmOffsetExecutive.h>
|
|
#include <SmOffsetGeometryCreation.h>
|
|
#include <SmOffsetSurface.h>
|
|
#include <SmPlane.h>
|
|
#include <SmRegion.h>
|
|
#include <SmShell.h>
|
|
#include <SmSurfOfExtrusion.h>
|
|
#include <SmSSIData.h>
|
|
#include <SmVertex.h>
|
|
#include <SmVertexuse.h>
|
|
#include <nurbs.h>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <memory>
|
|
#include <new>
|
|
#include <vector>
|
|
|
|
namespace {
|
|
constexpr int64_t max_values = 1048576;
|
|
constexpr int64_t max_items = 4096;
|
|
constexpr int64_t max_points = 65536;
|
|
constexpr int64_t max_degree = 17; // NL_MAXDEG, not an arbitrary evaluation degree.
|
|
struct Argument {};
|
|
struct Rejected { SmStatus status; };
|
|
|
|
// A second bounds check here makes malformed direct ABI calls fail as arguments,
|
|
// not read beyond the caller's buffer. Geometric invariants are checked in Rust.
|
|
struct Reader {
|
|
const double* data;
|
|
int64_t size;
|
|
int64_t pos = 0;
|
|
double number() {
|
|
if (pos >= size) throw Argument{};
|
|
double v = data[pos++];
|
|
if (!std::isfinite(v)) throw Argument{};
|
|
return v;
|
|
}
|
|
int64_t index(int64_t limit = max_items) {
|
|
double v = number();
|
|
if (v < 0 || v > (double)limit || std::floor(v) != v) throw Argument{};
|
|
return (int64_t)v;
|
|
}
|
|
bool flag() { return index(1) != 0; }
|
|
SmPoint3d point() {
|
|
double x = number(), y = number(), z = number();
|
|
return SmPoint3d(x, y, z);
|
|
}
|
|
SmPoint2d uv() {
|
|
double x = number(), y = number();
|
|
return SmPoint2d(x, y);
|
|
}
|
|
SmExtent1d interval() {
|
|
double a = number(), b = number();
|
|
if (a > b || !std::isfinite(b-a)) throw Argument{};
|
|
return SmExtent1d(a, b);
|
|
}
|
|
SmExtent2d domain() {
|
|
SmPoint2d a = uv(), b = uv();
|
|
if (!(a.x < b.x && a.y < b.y)) throw Argument{};
|
|
return SmExtent2d(a, b);
|
|
}
|
|
void done() const { if (pos != size) throw Argument{}; }
|
|
};
|
|
|
|
void constructed(SmStatus status) {
|
|
if (status != SM_SUCCESS) throw Rejected{status};
|
|
}
|
|
void put_point(std::vector<double>& out, const SmPoint3d& p) {
|
|
out.insert(out.end(), {p.x, p.y, p.z});
|
|
}
|
|
void put_domain(std::vector<double>& out, const SmExtent2d& d) {
|
|
out.insert(out.end(), {d.GetMin().x, d.GetMin().y, d.GetMax().x, d.GetMax().y});
|
|
}
|
|
template<class T> double id(const SmTArray<T*>& list, const T* p) {
|
|
if (!p) return -1;
|
|
for (ULONG i=0; i<list.GetSize(); ++i) if (list[i] == p) return (double)i;
|
|
return -2;
|
|
}
|
|
void put_ids(std::vector<double>& out, const SmTArray<SmEdgeuse*>& values,
|
|
const SmTArray<SmEdgeuse*>& traversal) {
|
|
out.push_back(values.GetSize());
|
|
for (ULONG i=0; i<values.GetSize(); ++i) out.push_back(id(traversal, values[i]));
|
|
}
|
|
std::unique_ptr<SmBrep> brep(SmContext& ctx) {
|
|
return std::unique_ptr<SmBrep>(new (ctx) SmBrep(SM_USE_DEFAULT, SM_USE_DEFAULT, &ctx));
|
|
}
|
|
|
|
// Config-only geometry-subclass fixture. Preserve base initialization and the
|
|
// inherited GetTangencyTolDegrees calculation; only the protected scalar changes.
|
|
class ConfigGeometry : public SmOffsetGeometryCreation {
|
|
public:
|
|
ConfigGeometry(double distance, double tol, double tangency_tol_radians)
|
|
: SmOffsetGeometryCreation(distance, tol) {
|
|
m_dTangencyTolRadians = tangency_tol_radians;
|
|
}
|
|
};
|
|
|
|
// Only fixture injection/observation: all behavior under test remains native.
|
|
class Executive : public SmOffsetExecutive {
|
|
public:
|
|
Executive(SmContext& ctx, SmOffsetOperationType op, SmOffsetGeometryCreation& geometry,
|
|
SmBrep* original) : SmOffsetExecutive(ctx, op, geometry, original) {}
|
|
void install(std::unique_ptr<SmBrep> offset) { m_pOffsetBrep = offset.release(); }
|
|
SmStatus relate(SmEdgeuse* a, SmEdgeuse* b) { return m_vEUToEU.RelatePair(a, b); }
|
|
void seconds(SmEdgeuse* a, SmTArray<SmEdgeuse*>& b) { m_vEUToEU.GetSeconds(a, b); }
|
|
SmStatus relate_face(SmFaceuse* a, SmFace* b) { return m_vFUToF.RelatePair(a, b); }
|
|
bool merge() const { return m_bMergeResults != FALSE; }
|
|
bool solid() const { return m_bCreateOffsetSolid != FALSE; }
|
|
SmSurface* cached(SmSurface* base) { return m_vSurfExtSurf.At(base); }
|
|
void forget(SmSurface* base) { m_vSurfExtSurf.Remove(base); }
|
|
void clear() { m_vSurfExtSurf.RemoveAll(); }
|
|
ULONG cache_count() const { return m_vSurfExtSurf.Count(); }
|
|
void faces_of(SmFaceuse* use, SmTArray<SmFace*>& faces) { m_vFUToF.GetSeconds(use, faces); }
|
|
};
|
|
|
|
std::unique_ptr<SmCurve> read_curve(Reader& rd, SmContext& ctx) {
|
|
int64_t kind = rd.index(1);
|
|
if (kind == 0) {
|
|
SmPoint3d start = rd.point(), end = rd.point();
|
|
SmExtent1d domain = rd.interval();
|
|
SmLine* line = nullptr;
|
|
SmStatus status = SmLine::CreateLineSegment(ctx, 3, start, end, line, &domain);
|
|
std::unique_ptr<SmCurve> result(line);
|
|
constructed(status);
|
|
if (!result) throw std::bad_alloc();
|
|
return result;
|
|
}
|
|
auto degree = (gw_DEGREE)rd.index(max_degree);
|
|
auto npoints = (gw_INDEX)rd.index(max_points);
|
|
if (degree < 1 || npoints <= degree) throw Argument{};
|
|
gw_INDEX n = npoints - 1, m = npoints + degree;
|
|
std::unique_ptr<gw_CURVE, decltype(&sm_FreeNurbCurve)> raw(
|
|
sm_AllocateNurbCurve(n, degree, m), &sm_FreeNurbCurve);
|
|
gw_CURVE* curve = raw.get();
|
|
if (!curve) throw std::bad_alloc();
|
|
// The native constructor tunes knots, so initialize storage BEFORE invoking it.
|
|
for (gw_INDEX i=0; i<npoints; ++i) {
|
|
curve->pol->Pw[i].x = rd.number(); curve->pol->Pw[i].y = rd.number();
|
|
curve->pol->Pw[i].z = rd.number(); curve->pol->Pw[i].w = rd.number();
|
|
}
|
|
for (gw_INDEX i=0; i<=m; ++i) curve->knt->U[i] = rd.number();
|
|
std::unique_ptr<SmCurve> result(new (ctx) SmBSplineCurve(curve, 3, FALSE, &ctx));
|
|
raw.release();
|
|
return result;
|
|
}
|
|
|
|
SmEdge* make_wire(SmContext& ctx, SmBrep* target, std::unique_ptr<SmCurve> curve,
|
|
const SmExtent1d& interval) {
|
|
SmPoint3d a(0,0,0), b(0,0,0);
|
|
constructed(curve->EvaluatePoint(interval.GetMin(), a));
|
|
constructed(curve->EvaluatePoint(interval.GetMax(), b));
|
|
SmShell* shell = nullptr;
|
|
SmVertex *v1 = nullptr, *v2 = nullptr;
|
|
constructed(target->MakeShellVertex(nullptr, a, shell, v1));
|
|
constructed(target->MakeShellVertex(nullptr, b, shell, v2));
|
|
SmEdge* edge = nullptr;
|
|
// MakeWireEdge can fail after assigning the curve to a newly made edge.
|
|
// The guard must not delete a curve already owned by that topology.
|
|
struct Pending {
|
|
SmCurve* p;
|
|
~Pending() { if (p && !p->GetOwner()) delete p; }
|
|
} pending{curve.release()};
|
|
constructed(target->MakeWireEdge(nullptr, v1, v2, pending.p, interval, SM_OT_SAME, edge));
|
|
if (!edge || !edge->GetPrimaryEdgeuse()) throw Argument{};
|
|
(void)ctx;
|
|
return edge;
|
|
}
|
|
|
|
SmEdge* unit_wire(SmContext& ctx, SmBrep* target, double y) {
|
|
SmLine* curve = nullptr;
|
|
SmExtent1d interval(0,1);
|
|
constructed(SmLine::CreateLineSegment(ctx, 3, SmPoint3d(0,y,0), SmPoint3d(1,y,0),
|
|
curve, &interval));
|
|
return make_wire(ctx, target, std::unique_ptr<SmCurve>(curve), interval);
|
|
}
|
|
|
|
SmFace* plane_face(SmContext& ctx, SmBrep* target, double z) {
|
|
SmExtent2d domain(SmPoint2d(0,0), SmPoint2d(1,1));
|
|
SmPlane* plane = new (ctx) SmPlane(SmPoint3d(0,0,z), SmVector3d(1,0,0),
|
|
SmVector3d(0,1,0), SmVector2d(1,1), domain, &ctx);
|
|
struct Pending {
|
|
SmSurface* p;
|
|
~Pending() { if (p && !p->GetOwner()) delete p; }
|
|
} pending{plane};
|
|
SmFace* face = nullptr;
|
|
constructed(target->CreateFaceFromSurface(plane, domain, face));
|
|
if (!face) throw Argument{};
|
|
return face;
|
|
}
|
|
|
|
std::unique_ptr<SmSurface> read_surface(Reader& rd, SmContext& ctx) {
|
|
int64_t kind = rd.index(1);
|
|
if (kind == 0) {
|
|
SmPoint3d origin = rd.point();
|
|
SmVector3d x = rd.point(), y = rd.point();
|
|
SmVector2d scale = rd.uv();
|
|
SmExtent2d domain = rd.domain();
|
|
return std::unique_ptr<SmSurface>(new (ctx) SmPlane(origin, x, y, scale, domain, &ctx));
|
|
}
|
|
auto p = (gw_DEGREE)rd.index(max_degree), q = (gw_DEGREE)rd.index(max_degree);
|
|
auto nu = (gw_INDEX)rd.index(max_points), nv = (gw_INDEX)rd.index(max_points);
|
|
if (p < 1 || q < 1 || nu <= p || nv <= q || (int64_t)nu*nv > max_points)
|
|
throw Argument{};
|
|
gw_INDEX r = nu+p, s = nv+q;
|
|
auto free_surface = [](gw_SURFACE* p) { smos_Free(p); };
|
|
std::unique_ptr<gw_SURFACE, decltype(free_surface)> raw(
|
|
sm_AllocateNurbSurface(nu-1, nv-1, p, q, r, s), free_surface);
|
|
gw_SURFACE* surface = raw.get();
|
|
if (!surface) throw std::bad_alloc();
|
|
for (gw_INDEX i=0; i<nu; ++i) for (gw_INDEX j=0; j<nv; ++j) {
|
|
surface->net->Pw[i][j].x = rd.number(); surface->net->Pw[i][j].y = rd.number();
|
|
surface->net->Pw[i][j].z = rd.number(); surface->net->Pw[i][j].w = rd.number();
|
|
}
|
|
for (gw_INDEX i=0; i<=r; ++i) surface->knu->U[i] = rd.number();
|
|
for (gw_INDEX i=0; i<=s; ++i) surface->knv->U[i] = rd.number();
|
|
std::unique_ptr<SmBSplineSurface> result(new (ctx) SmBSplineSurface(surface, FALSE, &ctx));
|
|
raw.release();
|
|
result->SetBSplineSurfaceForm(SM_SF_UNSPECIFIED);
|
|
return std::unique_ptr<SmSurface>(result.release());
|
|
}
|
|
|
|
void state(std::vector<double>& out, Executive& executive, SmOffsetGeometryCreation& geometry) {
|
|
SmTArray<SmFace*> faces;
|
|
executive.GetShellFaces(faces);
|
|
out.insert(out.end(), {
|
|
geometry.GetOffsetDistance(), geometry.GetThisApproxTol3d(),
|
|
(double)executive.GetOffsetOperation(), (double)executive.GetInset(),
|
|
(double)executive.GetExtendConvexEdges(), (double)executive.merge(),
|
|
(double)executive.solid(), executive.GetTangencyTolDegrees(),
|
|
executive.GetOffsetBrep() ? 1.0 : 0.0, (double)faces.GetSize()
|
|
});
|
|
}
|
|
|
|
void config(Reader& rd, std::vector<double>& out) {
|
|
double distance = rd.number(), tol = rd.number(), tangency_tol_radians = rd.number();
|
|
auto operation = (SmOffsetOperationType)rd.index(4);
|
|
bool inset = rd.flag(), extend = rd.flag(), merge = rd.flag(), solid = rd.flag();
|
|
bool install = rd.flag();
|
|
int64_t norig = rd.index(), noffset = rd.index(), nfaces = rd.index();
|
|
SmContext ctx;
|
|
auto original = brep(ctx), offset = brep(ctx), face_owner = brep(ctx);
|
|
for (int64_t i=0; i<norig; ++i) unit_wire(ctx, original.get(), (double)i);
|
|
for (int64_t i=0; i<noffset; ++i) unit_wire(ctx, offset.get(), (double)i);
|
|
for (int64_t i=0; i<nfaces; ++i) plane_face(ctx, face_owner.get(), (double)i);
|
|
SmTArray<SmEdgeuse*> originals, offsets;
|
|
SmTArray<SmFace*> faces;
|
|
original->GetEdgeuses(originals); offset->GetEdgeuses(offsets); face_owner->GetFaces(faces);
|
|
ConfigGeometry geometry(distance, tol, tangency_tol_radians);
|
|
Executive executive(ctx, operation, geometry, original.get());
|
|
out.push_back(0);
|
|
state(out, executive, geometry);
|
|
executive.SetInset(inset ? TRUE : FALSE);
|
|
executive.SetExtendConvexEdges(extend ? TRUE : FALSE);
|
|
executive.SetMergeResults(merge ? TRUE : FALSE);
|
|
executive.SetCreateOffsetSolid(solid ? TRUE : FALSE);
|
|
if (install) executive.install(std::move(offset)); // only executive deletes this B-rep.
|
|
std::vector<double> shell_records;
|
|
int64_t nsets = rd.index();
|
|
shell_records.push_back((double)nsets);
|
|
for (int64_t i=0; i<nsets; ++i) {
|
|
SmTArray<SmFace*> selectors, result;
|
|
int64_t n = rd.index();
|
|
for (int64_t j=0; j<n; ++j) {
|
|
int64_t index = rd.index();
|
|
if (index >= faces.GetSize()) throw Argument{};
|
|
selectors.Add(faces[(ULONG)index]);
|
|
}
|
|
executive.SetShellFaces(selectors);
|
|
// Mutating the caller's array checks SetShellFaces' copy, and prefilling
|
|
// the destination checks that GetShellFaces resets, rather than appends.
|
|
selectors.ReSet();
|
|
if (faces.GetSize()) result.Add(faces[0]);
|
|
executive.GetShellFaces(result);
|
|
shell_records.push_back(result.GetSize());
|
|
for (ULONG j=0; j<result.GetSize(); ++j) shell_records.push_back(id(faces, result[j]));
|
|
}
|
|
state(out, executive, geometry);
|
|
out.insert(out.end(), shell_records.begin(), shell_records.end());
|
|
int64_t npairs = rd.index();
|
|
std::vector<double> statuses;
|
|
for (int64_t i=0; i<npairs; ++i) {
|
|
int64_t a = rd.index(max_items*2), b = rd.index(max_items*2);
|
|
if (a >= originals.GetSize() || b >= offsets.GetSize()) throw Argument{};
|
|
statuses.push_back(executive.relate(originals[(ULONG)a], offsets[(ULONG)b]));
|
|
}
|
|
rd.done();
|
|
out.push_back(originals.GetSize());
|
|
for (ULONG i=0; i<originals.GetSize(); ++i) {
|
|
SmTArray<SmEdgeuse*> seconds;
|
|
out.push_back(id(offsets, executive.GetOffsetEdgeuse(originals[i])));
|
|
executive.seconds(originals[i], seconds);
|
|
put_ids(out, seconds, offsets);
|
|
}
|
|
out.push_back((double)statuses.size());
|
|
out.insert(out.end(), statuses.begin(), statuses.end());
|
|
}
|
|
|
|
// A closing segment requires distinct endpoints (SmBSplineCurve.cpp's SAME_PNT_ER).
|
|
// For exactly coincident endpoints, use a nonzero closed cubic instead; only this
|
|
// unmeasured support changes kind. Its fixed unit weights and clamped [0,1] knots
|
|
// are valid native constructor data, not a replacement for any measured geometry.
|
|
std::unique_ptr<SmCurve> closing_curve(SmContext& ctx, const SmPoint3d& start,
|
|
const SmPoint3d& end) {
|
|
if (start.x == end.x && start.y == end.y && start.z == end.z) {
|
|
std::unique_ptr<gw_CURVE, decltype(&sm_FreeNurbCurve)> raw(
|
|
sm_AllocateNurbCurve(3, 3, 7), &sm_FreeNurbCurve);
|
|
if (!raw) throw std::bad_alloc();
|
|
const SmPoint3d points[] = {start, SmPoint3d(1,0,0), SmPoint3d(0,1,0), end};
|
|
for (gw_INDEX i=0; i<4; ++i) {
|
|
raw->pol->Pw[i].x = points[i].x; raw->pol->Pw[i].y = points[i].y;
|
|
raw->pol->Pw[i].z = points[i].z; raw->pol->Pw[i].w = 1;
|
|
}
|
|
for (gw_INDEX i=0; i<8; ++i) raw->knt->U[i] = i < 4 ? 0 : 1;
|
|
std::unique_ptr<SmCurve> result(new (ctx) SmBSplineCurve(raw.get(), 3, FALSE, &ctx));
|
|
raw.release();
|
|
return result;
|
|
}
|
|
SmBSplineCurve* closing = nullptr;
|
|
SmStatus status = SmBSplineCurve::CreateLineSegment(ctx, 3, start, end, closing);
|
|
std::unique_ptr<SmCurve> result(closing);
|
|
constructed(status);
|
|
if (!result) throw std::bad_alloc();
|
|
return result;
|
|
}
|
|
|
|
// Give each mapped wire a genuine two-edge loop. The second edge closes the
|
|
// loop and is fixture scaffolding only. No UV trims are requested: synchronization
|
|
// concerns the 3D edge ranges and must also accept nonplanar B-spline curves.
|
|
SmEdge* loop_fixture(SmContext& ctx, SmBrep* target, std::unique_ptr<SmCurve> curve,
|
|
const SmExtent1d& interval, std::vector<SmEdge*>& supports) {
|
|
SmEdge* edge = make_wire(ctx, target, std::move(curve), interval);
|
|
SmVertex* a = edge->GetStartVertex();
|
|
SmVertex* b = edge->GetOtherVertex(a);
|
|
auto closing_owner = closing_curve(ctx, b->GetPoint(), a->GetPoint());
|
|
SmExtent1d unit = closing_owner->GetNaturalInterval();
|
|
struct PendingCurve {
|
|
SmCurve* p;
|
|
~PendingCurve() { if (p && !p->GetOwner()) delete p; }
|
|
} pending_curve{closing_owner.release()};
|
|
SmEdge* support = nullptr;
|
|
constructed(target->MakeWireEdge(nullptr, b, a, pending_curve.p,
|
|
unit, SM_OT_SAME, support));
|
|
if (!support) throw Argument{};
|
|
supports.push_back(support);
|
|
SmTArray<ULONG> loop_sizes;
|
|
SmTArray<SmEdgeuse*> uses;
|
|
SmTArray<SmVertex*> vertices;
|
|
loop_sizes.Add(2);
|
|
uses.Add(edge->GetPrimaryEdgeuse()); uses.Add(support->GetPrimaryEdgeuse());
|
|
SmExtent2d domain(SmPoint2d(0,0), SmPoint2d(1,1));
|
|
struct PendingSurface {
|
|
SmSurface* p;
|
|
~PendingSurface() { if (p && !p->GetOwner()) delete p; }
|
|
} plane{new (ctx) SmPlane(SmPoint3d(0,0,0), SmVector3d(1,0,0), SmVector3d(0,1,0),
|
|
SmVector2d(1,1), domain, &ctx)};
|
|
SmRegion* region = nullptr;
|
|
SmShell* shell = nullptr;
|
|
SmFace* face = nullptr;
|
|
constructed(target->MakeFaceTopology(nullptr, plane.p, SM_OT_SAME, loop_sizes, uses, vertices,
|
|
region, shell, face, FALSE, FALSE));
|
|
SmEdgeuse* use = edge->GetPrimaryEdgeuse();
|
|
if (!use || !use->GetCCWEdgeuse() || !use->GetCWEdgeuse()) throw Argument{};
|
|
return edge;
|
|
}
|
|
|
|
// Native traversal restricted to the measured edges: auxiliary closing edges are
|
|
// not part of the unit's inputs or output topology. Split descendants remain here.
|
|
void measured_traversal(SmBrep* target, const std::vector<SmEdge*>& supports,
|
|
SmTArray<SmEdge*>& edges, SmTArray<SmEdgeuse*>& uses) {
|
|
edges.ReSet(); uses.ReSet();
|
|
SmTArray<SmEdge*> all;
|
|
target->GetEdges(all);
|
|
for (ULONG i=0; i<all.GetSize(); ++i) {
|
|
if (std::find(supports.begin(), supports.end(), all[i]) != supports.end()) continue;
|
|
edges.Add(all[i]);
|
|
SmTArray<SmEdgeuse*> pair;
|
|
all[i]->GetEdgeuses(pair);
|
|
uses.Append(pair);
|
|
}
|
|
}
|
|
|
|
void put_eu_records(std::vector<double>& out, const SmTArray<SmEdge*>& edges,
|
|
const SmTArray<SmEdgeuse*>& uses) {
|
|
out.push_back(uses.GetSize());
|
|
for (ULONG i=0; i<uses.GetSize(); ++i) {
|
|
SmEdge* edge = uses[i]->GetEdge();
|
|
const SmExtent1d& interval = edge->GetInterval();
|
|
SmCurve* curve = edge->GetCurve();
|
|
out.insert(out.end(), {(double)i, id(edges, edge), interval.GetMin(), interval.GetMax(),
|
|
curve->ApproximateLength(interval, 10)});
|
|
SmPoint3d point(0,0,0);
|
|
out.push_back(curve->EvaluatePoint(interval.GetMin(), point));
|
|
put_point(out, point);
|
|
point.Set(0,0,0);
|
|
out.push_back(curve->EvaluatePoint(interval.GetMax(), point));
|
|
put_point(out, point);
|
|
}
|
|
}
|
|
|
|
void synchronize(Reader& rd, std::vector<double>& out) {
|
|
bool null_original = rd.flag(), null_other = rd.flag();
|
|
SmContext ctx;
|
|
auto original = brep(ctx), offset = brep(ctx);
|
|
SmEdgeuse* a = unit_wire(ctx, original.get(), 0)->GetPrimaryEdgeuse();
|
|
SmEdgeuse* b = unit_wire(ctx, original.get(), 1)->GetPrimaryEdgeuse();
|
|
SmTArray<SmEdgeuse*> first, second;
|
|
std::vector<SmEdge*> supports;
|
|
for (int side=0; side<2; ++side) {
|
|
int64_t n = rd.index();
|
|
for (int64_t i=0; i<n; ++i) {
|
|
auto curve = read_curve(rd, ctx);
|
|
SmExtent1d interval = rd.interval();
|
|
SmEdge* edge = loop_fixture(ctx, offset.get(), std::move(curve), interval, supports);
|
|
(side == 0 ? first : second).Add(edge->GetPrimaryEdgeuse());
|
|
}
|
|
}
|
|
rd.done();
|
|
SmOffsetGeometryCreation geometry(1, 1e-6);
|
|
Executive executive(ctx, SM_OO_SOLID_OFFSET, geometry, original.get());
|
|
SmBrep* target = offset.get();
|
|
executive.install(std::move(offset));
|
|
for (ULONG i=0; i<first.GetSize(); ++i) {
|
|
SmStatus status = executive.relate(a, first[i]);
|
|
if (status != SM_SUCCESS) throw Argument{};
|
|
}
|
|
if (!null_other) for (ULONG i=0; i<second.GetSize(); ++i) {
|
|
SmStatus status = executive.relate(b, second[i]);
|
|
if (status != SM_SUCCESS) throw Argument{};
|
|
}
|
|
if (null_original) a = nullptr;
|
|
if (null_other) b = nullptr;
|
|
SmTArray<SmEdgeuse*> before, related_a, related_b;
|
|
SmTArray<SmEdge*> measured_edges;
|
|
measured_traversal(target, supports, measured_edges, before);
|
|
double lookup_a = id(before, executive.GetOffsetEdgeuse(a));
|
|
double lookup_b = id(before, executive.GetOffsetEdgeuse(b));
|
|
executive.seconds(a, related_a); executive.seconds(b, related_b);
|
|
std::vector<double> relation_records;
|
|
put_ids(relation_records, related_a, before); put_ids(relation_records, related_b, before);
|
|
SmTArray<SmEdgeuse*> matched_a, matched_b, gap_a, gap_b;
|
|
SmStatus status = executive.SyncronizeOffsetEdgeuses(a, b, matched_a, matched_b, gap_a, gap_b);
|
|
out.insert(out.end(), {0, (double)status, lookup_a, lookup_b});
|
|
SmTArray<SmEdgeuse*> after, fixtures;
|
|
measured_traversal(target, supports, measured_edges, after);
|
|
fixtures.Append(first); fixtures.Append(second);
|
|
put_ids(out, fixtures, after);
|
|
out.insert(out.end(), relation_records.begin(), relation_records.end());
|
|
put_eu_records(out, measured_edges, after);
|
|
put_ids(out, matched_a, after); put_ids(out, matched_b, after);
|
|
put_ids(out, gap_a, after); put_ids(out, gap_b, after);
|
|
}
|
|
|
|
// NL_CrvAdv.cpp's G1 extension retries a zero end tangent using Pw[2] or
|
|
// Pw[n-2], even for a two-point curve. Check the native isocurves sampled by
|
|
// NL_SrfAdv.cpp first: otherwise an accepted degenerate bilinear fixture can
|
|
// read outside its control polygon. This is a safety check, not an extension
|
|
// implementation, and does not reject short but normalizable tangents.
|
|
void guard_two_point_isocurves(SmSurface* base) {
|
|
SmBSplineSurface* spline = SM_CAST_PTR(SmBSplineSurface, base);
|
|
if (!spline) return;
|
|
gw_SURFACE* surface = spline->GetOrCreateGwNurbPointer();
|
|
if (!surface) throw Argument{};
|
|
for (int direction=0; direction<2; ++direction) {
|
|
if ((direction == 0 ? surface->net->n : surface->net->m) != 1) continue;
|
|
gw_KNOTVECTOR* cross_knots = direction == 0 ? surface->knv : surface->knu;
|
|
for (double fraction : {0.07, 0.19, 0.45, 0.73, 0.92}) {
|
|
double parameter = (1.0-fraction)*cross_knots->U[0]
|
|
+ fraction*cross_knots->U[cross_knots->m];
|
|
std::unique_ptr<gw_CURVE, decltype(&sm_FreeNurbCurve)> curve(
|
|
sm_AllocateNurbCurve(1, 1, 3), &sm_FreeNurbCurve);
|
|
if (!curve) throw std::bad_alloc();
|
|
if (N_SrfExtractIsoCrv(surface, parameter, direction == 0 ? NL_UDIR : NL_VDIR,
|
|
curve.get(), nullptr) != 0) continue;
|
|
NL_POINT a, b;
|
|
NL_VECTOR tangent, unit;
|
|
NL_REAL magnitude = 0;
|
|
N_CPtToPtEuclid(curve->pol->Pw[0], &a);
|
|
N_CPtToPtEuclid(curve->pol->Pw[1], &b);
|
|
N_VectorDir(a, b, &tangent);
|
|
if (N_VectorNormalize(tangent, &unit, &magnitude) != 0) throw Argument{};
|
|
}
|
|
}
|
|
}
|
|
|
|
void extend(Reader& rd, std::vector<double>& out) {
|
|
double distance = rd.number(), tol = rd.number();
|
|
if (distance < 0.0) throw Argument{};
|
|
SmContext ctx;
|
|
std::vector<std::unique_ptr<SmSurface>> bases, extended;
|
|
int64_t n = rd.index();
|
|
for (int64_t i=0; i<n; ++i) bases.push_back(read_surface(rd, ctx));
|
|
std::vector<SmPoint2d> samples;
|
|
int64_t nsamples = rd.index();
|
|
for (int64_t i=0; i<nsamples; ++i) samples.push_back(rd.uv());
|
|
auto original = brep(ctx);
|
|
SmOffsetGeometryCreation geometry(distance, tol);
|
|
Executive executive(ctx, SM_OO_SOLID_OFFSET, geometry, original.get());
|
|
int64_t nactions = rd.index();
|
|
out.insert(out.end(), {0, (double)nactions});
|
|
for (int64_t i=0; i<nactions; ++i) {
|
|
int64_t action = rd.index(2);
|
|
if (action == 2) {
|
|
out.insert(out.end(), {2, (double)executive.cache_count(), 0});
|
|
executive.clear();
|
|
continue;
|
|
}
|
|
double raw_key = rd.number();
|
|
if (raw_key < -1 || std::floor(raw_key) != raw_key || raw_key >= (double)bases.size()
|
|
|| (action == 1 && raw_key < 0)) throw Argument{};
|
|
SmSurface* base = raw_key == -1 ? nullptr : bases.at((size_t)raw_key).get();
|
|
bool hit = executive.cached(base) != nullptr;
|
|
if (action == 1) {
|
|
executive.forget(base);
|
|
out.insert(out.end(), {1, hit ? 1.0 : 0.0, (double)executive.cache_count()});
|
|
continue;
|
|
}
|
|
if (base && !hit) guard_two_point_isocurves(base);
|
|
SmExtent2d domain;
|
|
SmSurface* result = nullptr;
|
|
SmStatus status = executive.GetCreateExtendedSurface(base, domain, result);
|
|
// SmOffsetExecutive deletes its map, NOT the extended surfaces. Keep each
|
|
// distinct returned allocation alive through eviction/clear and clean it
|
|
// after the executive has destroyed the map. Never wrap a base alias twice.
|
|
double result_id = -1;
|
|
if (result) {
|
|
for (size_t j=0; j<extended.size(); ++j)
|
|
if (extended[j].get() == result) result_id = (double)j;
|
|
if (result_id == -1) {
|
|
if (std::any_of(bases.begin(), bases.end(),
|
|
[result](const std::unique_ptr<SmSurface>& p){ return p.get() == result; }))
|
|
throw Argument{}; // unsupported ownership contract, not a fixture rejection.
|
|
result_id = (double)extended.size();
|
|
extended.emplace_back(result);
|
|
}
|
|
}
|
|
out.insert(out.end(), {0, (double)status, hit ? 1.0 : 0.0, result_id, result ? 1.0 : 0.0});
|
|
if (status != SM_SUCCESS) continue;
|
|
if (!result) throw Argument{};
|
|
out.push_back(result->IsKindOf(SmPlane_TYPE) ? 1 :
|
|
result->IsKindOf(SmBSplineSurface_TYPE) ? 2 : 3);
|
|
put_domain(out, domain);
|
|
out.push_back((double)samples.size());
|
|
for (const SmPoint2d& fraction : samples) {
|
|
SmPoint2d uv((1.0-fraction.x)*domain.GetMin().x + fraction.x*domain.GetMax().x,
|
|
(1.0-fraction.y)*domain.GetMin().y + fraction.y*domain.GetMax().y);
|
|
SmPoint3d point(0,0,0);
|
|
SmVector3d normal(0,0,0);
|
|
out.push_back(result->EvaluatePoint(uv, point)); put_point(out, point);
|
|
out.push_back(result->EvaluateNormal(uv, TRUE, TRUE, normal)); put_point(out, normal);
|
|
}
|
|
}
|
|
rd.done();
|
|
}
|
|
|
|
// All new geometry remains locally owned even if construction/evaluation throws.
|
|
// This guard is created before BuildOffsetOfVertex so partial native outputs are
|
|
// cleaned on operation failure as well as on snapshot-allocation exceptions.
|
|
struct VertexResult {
|
|
SmTArray<SmCurve*> curves;
|
|
SmTArray<SmOrientType> orientations;
|
|
SmSurface* surface = nullptr;
|
|
~VertexResult() {
|
|
for (ULONG i=0; i<curves.GetSize(); ++i) delete curves[i];
|
|
delete surface;
|
|
}
|
|
};
|
|
|
|
SmFace* face_on_edges(SmBrep* target, std::unique_ptr<SmSurface> surface,
|
|
const SmTArray<SmEdgeuse*>& uses) {
|
|
struct Pending {
|
|
SmSurface* p;
|
|
~Pending() { if (p && !p->GetOwner()) delete p; }
|
|
} pending{surface.release()};
|
|
SmTArray<ULONG> loops;
|
|
loops.Add(uses.GetSize());
|
|
SmTArray<SmVertex*> vertices;
|
|
SmRegion* region = nullptr;
|
|
SmShell* shell = nullptr;
|
|
SmFace* face = nullptr;
|
|
constructed(target->MakeFace(nullptr, loops, uses, vertices, pending.p,
|
|
pending.p->GetNaturalUVDomain(), SM_OT_SAME,
|
|
region, shell, face, FALSE, FALSE));
|
|
if (!face) throw Argument{};
|
|
return face;
|
|
}
|
|
|
|
// A bounded plane, an interior vertex-loop, and a genuine new edge joining it
|
|
// to the outer boundary. The interior endpoint has exactly one incident edge
|
|
// and IsStrut is true. No source/result cap is preseeded.
|
|
SmVertex* source_strut(SmContext& ctx, SmBrep* target, const SmPoint3d& point,
|
|
SmFaceuse*& source_use) {
|
|
SmExtent2d domain(SmPoint2d(-2,-2), SmPoint2d(2,2));
|
|
struct Pending {
|
|
SmSurface* p;
|
|
~Pending() { if (p && !p->GetOwner()) delete p; }
|
|
} pending{new (ctx) SmPlane(point, SmVector3d(1,0,0), SmVector3d(0,1,0),
|
|
SmVector2d(1,1), domain, &ctx)};
|
|
SmFace* face = nullptr;
|
|
constructed(target->CreateFaceFromSurface(pending.p, domain, face));
|
|
if (!face) throw Argument{};
|
|
SmTArray<SmVertex*> boundary;
|
|
face->GetVertices(boundary);
|
|
if (!boundary.GetSize()) throw Argument{};
|
|
SmVertex* start = boundary[0];
|
|
if (!start) throw Argument{};
|
|
SmVertex* vertex = nullptr;
|
|
SmLoop* loop = nullptr;
|
|
target->m_bEditingEnabled = TRUE;
|
|
constructed(target->MakeVertexLoop(face, point, loop, vertex));
|
|
if (!vertex) throw Argument{};
|
|
SmBSplineCurve* curve = nullptr;
|
|
SmStatus curve_status = SmBSplineCurve::CreateLineSegment(ctx, 3, start->GetPoint(), point, curve);
|
|
struct PendingCurve {
|
|
SmCurve* p;
|
|
~PendingCurve() { if (p && !p->GetOwner()) delete p; }
|
|
} pending_curve{curve};
|
|
constructed(curve_status);
|
|
if (!curve) throw Argument{};
|
|
SmEdge* edge = nullptr;
|
|
SmFace* split_face = nullptr;
|
|
constructed(target->MakeEdgeInFace(face, start, vertex, curve, nullptr,
|
|
curve->GetNaturalInterval(), SM_OT_SAME, 0.0,
|
|
edge, loop, split_face));
|
|
if (!edge || !edge->GetPrimaryEdgeuse()) throw Argument{};
|
|
SmEdgeuse* use = edge->GetPrimaryEdgeuse();
|
|
if (!use->GetVertexuse()) throw Argument{};
|
|
if (use->GetVertexuse()->GetVertex() != vertex) use = use->GetRadial();
|
|
// Safety invariant of this constructor, not an arithmetic parity assertion.
|
|
if (!use || !use->IsStrut() || !use->GetFaceuse()) throw Argument{};
|
|
source_use = use->GetFaceuse();
|
|
return vertex;
|
|
}
|
|
|
|
SmFace* circular_disk(SmContext& ctx, SmBrep* target, const SmPoint3d& center,
|
|
double radius) {
|
|
SmAxis2Placement placement;
|
|
placement.Translate(center);
|
|
SmBSplineCurve* circle = nullptr;
|
|
SmStatus status = SmBSplineCurve::CreateCircleSegment(ctx, 3, placement, radius,
|
|
0.0, 360.0, SM_CO_QUADRATIC, circle);
|
|
std::unique_ptr<SmCurve> circle_owner(circle);
|
|
constructed(status);
|
|
if (!circle) throw Argument{};
|
|
SmPoint3d point(0,0,0);
|
|
constructed(circle->EvaluatePoint(circle->GetNaturalInterval().GetMin(), point));
|
|
SmShell* shell = nullptr;
|
|
SmVertex* vertex = nullptr;
|
|
constructed(target->MakeShellVertex(nullptr, point, shell, vertex));
|
|
struct Pending {
|
|
SmCurve* p;
|
|
~Pending() { if (p && !p->GetOwner()) delete p; }
|
|
} pending{circle_owner.release()};
|
|
SmEdge* edge = nullptr;
|
|
constructed(target->MakeWireEdge(nullptr, vertex, vertex, pending.p,
|
|
circle->GetNaturalInterval(), SM_OT_SAME, edge));
|
|
if (!edge || !edge->GetPrimaryEdgeuse()) throw Argument{};
|
|
SmTArray<SmEdgeuse*> uses;
|
|
uses.Add(edge->GetPrimaryEdgeuse());
|
|
SmExtent2d domain(SmPoint2d(-radius,-radius), SmPoint2d(radius,radius));
|
|
std::unique_ptr<SmSurface> plane(new (ctx) SmPlane(center, SmVector3d(1,0,0),
|
|
SmVector3d(0,1,0), SmVector2d(1,1),
|
|
domain, &ctx));
|
|
return face_on_edges(target, std::move(plane), uses);
|
|
}
|
|
|
|
void put_vertex_surface(std::vector<double>& out, SmSurface* surface,
|
|
const std::vector<SmPoint2d>& samples) {
|
|
out.push_back(surface->GetType());
|
|
SmExtent2d domain = surface->GetNaturalUVDomain();
|
|
put_domain(out, domain);
|
|
// GetType is compared separately: never turn an analytic Sphere into a
|
|
// B-spline merely to make the samples agree.
|
|
SmBSplineSurface* spline = SM_CAST_PTR(SmBSplineSurface, surface);
|
|
gw_SURFACE* nurb = spline ? spline->GetGwNurbPointer() : nullptr;
|
|
out.push_back(nurb ? 1.0 : 0.0);
|
|
if (nurb) {
|
|
gw_INDEX n=nurb->net->n, m=nurb->net->m, r=nurb->knu->m, s=nurb->knv->m;
|
|
out.insert(out.end(), {(double)n, (double)m, (double)nurb->p,
|
|
(double)nurb->q, (double)r, (double)s});
|
|
for (gw_INDEX i=0; i<=n; ++i) for (gw_INDEX j=0; j<=m; ++j) {
|
|
const auto& point = nurb->net->Pw[i][j];
|
|
out.insert(out.end(), {point.x,point.y,point.z,point.w});
|
|
}
|
|
for (gw_INDEX i=0; i<=r; ++i) out.push_back(nurb->knu->U[i]);
|
|
for (gw_INDEX i=0; i<=s; ++i) out.push_back(nurb->knv->U[i]);
|
|
}
|
|
out.push_back(samples.size());
|
|
for (const auto& sample : samples) {
|
|
SmPoint2d uv((1.0-sample.x)*domain.GetMin().x + sample.x*domain.GetMax().x,
|
|
(1.0-sample.y)*domain.GetMin().y + sample.y*domain.GetMax().y);
|
|
SmPoint3d point(0,0,0);
|
|
SmVector3d du(0,0,0), dv(0,0,0), duv(0,0,0), duu(0,0,0), dvv(0,0,0);
|
|
SmStatus status = surface->Evaluate2ndDerivatives(uv, TRUE, TRUE,
|
|
point, du, dv, duv, duu, dvv);
|
|
out.push_back(status);
|
|
if (status != SM_SUCCESS) {
|
|
out.insert(out.end(), 18, 0.0);
|
|
} else {
|
|
put_point(out, point); put_point(out, du); put_point(out, dv);
|
|
put_point(out, duv); put_point(out, duu); put_point(out, dvv);
|
|
}
|
|
}
|
|
}
|
|
|
|
void vertex(Reader& rd, std::vector<double>& out) {
|
|
SmPoint3d point = rd.point();
|
|
double distance=rd.number(), approx_tol=rd.number();
|
|
double original_tol=rd.number(), result_tol=rd.number();
|
|
bool has_strut=rd.flag();
|
|
double radius=0.0, height=0.0;
|
|
bool map_mate=false;
|
|
if (has_strut) {
|
|
radius=rd.number(); height=rd.number(); map_mate=rd.flag();
|
|
for (double value : {point.x-2.0, point.x+2.0, point.y-2.0, point.y+2.0,
|
|
point.z+height, point.x+radius, point.x-radius,
|
|
point.y+radius, point.y-radius})
|
|
if (!std::isfinite(value)) throw Argument{};
|
|
}
|
|
std::vector<SmPoint2d> samples;
|
|
int64_t count=rd.index();
|
|
for (int64_t i=0; i<count; ++i) {
|
|
SmPoint2d sample=rd.uv();
|
|
if (sample.x<0 || sample.x>1 || sample.y<0 || sample.y>1) throw Argument{};
|
|
samples.push_back(sample);
|
|
}
|
|
rd.done();
|
|
SmContext ctx;
|
|
auto original=brep(ctx), offset=brep(ctx);
|
|
original->SetTolerance(original_tol, FALSE, FALSE);
|
|
offset->SetTolerance(result_tol, FALSE, FALSE);
|
|
SmVertex* source=nullptr;
|
|
SmFaceuse* source_use=nullptr;
|
|
SmFace* mapped_face=nullptr;
|
|
if (has_strut) {
|
|
source=source_strut(ctx, original.get(), point, source_use);
|
|
mapped_face=circular_disk(ctx, offset.get(), SmPoint3d(point.x,point.y,point.z+height), radius);
|
|
} else {
|
|
SmShell* shell=nullptr;
|
|
constructed(original->MakeShellVertex(nullptr, point, shell, source));
|
|
}
|
|
SmOffsetGeometryCreation geometry(distance, approx_tol);
|
|
Executive executive(ctx, SM_OO_SOLID_OFFSET, geometry, original.get());
|
|
executive.install(std::move(offset));
|
|
if (has_strut) {
|
|
if (map_mate) source_use=source_use->GetMate();
|
|
if (executive.relate_face(source_use, mapped_face) != SM_SUCCESS) throw Argument{};
|
|
}
|
|
VertexResult result;
|
|
SmStatus status=executive.BuildOffsetOfVertex(source, result.curves, result.orientations,
|
|
result.surface);
|
|
out.insert(out.end(), {0, (double)status, original->GetTolerance(),
|
|
executive.GetOffsetBrep()->GetTolerance(),
|
|
(double)result.curves.GetSize(), (double)result.orientations.GetSize(),
|
|
result.surface ? 1.0 : 0.0});
|
|
for (ULONG i=0; i<result.orientations.GetSize(); ++i) out.push_back(result.orientations[i]);
|
|
for (ULONG i=0; i<result.curves.GetSize(); ++i) {
|
|
SmCurve* curve=result.curves[i];
|
|
if (!curve) throw Argument{};
|
|
SmExtent1d interval=curve->GetNaturalInterval();
|
|
out.insert(out.end(), {(double)curve->GetType(), interval.GetMin(), interval.GetMax(), 4});
|
|
for (double fraction : {0.113,0.371,0.729,0.913}) {
|
|
double parameter=(1.0-fraction)*interval.GetMin()+fraction*interval.GetMax();
|
|
SmPoint3d point(0,0,0);
|
|
SmStatus status=curve->EvaluatePoint(parameter,point);
|
|
out.push_back(status);
|
|
if (status==SM_SUCCESS) put_point(out,point);
|
|
else out.insert(out.end(),3,0.0);
|
|
}
|
|
}
|
|
if (result.surface) put_vertex_surface(out, result.surface, samples);
|
|
}
|
|
|
|
void probe(Reader& rd, std::vector<double>& out) {
|
|
int64_t operation = rd.index(12);
|
|
double tol = rd.number();
|
|
rd.done();
|
|
SmContext ctx;
|
|
auto original = brep(ctx), offset = brep(ctx);
|
|
SmOffsetGeometryCreation geometry(0.25, tol);
|
|
Executive executive(ctx, SM_OO_SOLID_OFFSET, geometry, original.get());
|
|
executive.install(std::move(offset));
|
|
SmTArray<SmCurve*> curves;
|
|
SmTArray<SmOrientType> orientations;
|
|
SmSurface* surface = nullptr;
|
|
SmBrep* result = nullptr;
|
|
SmStatus status = SM_SUCCESS;
|
|
SmTArray<SmEdgeuse*> originals, offsets;
|
|
switch (operation) {
|
|
case 0: status = SmOffsetExecutive::ReplaceImplicitOffsets(original.get(), tol); break;
|
|
case 1: status = executive.RemoveExcessFaces(executive.GetOffsetBrep()); break;
|
|
case 2:
|
|
executive.SetMergeResults(TRUE);
|
|
status = executive.RemoveExcessFaces(executive.GetOffsetBrep()); break;
|
|
case 3: status = executive.BuildSideWallOfEdgeuse(nullptr); break;
|
|
case 4: status = executive.BuildSideWallOfEdgeuse(unit_wire(ctx, original.get(), 0)->GetPrimaryEdgeuse()); break;
|
|
case 5: {
|
|
SmTArray<SmEdgeuse*> gaps;
|
|
SmTArray<SmFace*> faces;
|
|
status = executive.FillGapEdgeuses(nullptr, gaps, faces); break;
|
|
}
|
|
case 6: status = executive.BuildOffsetOfVertex(nullptr, curves, orientations, surface); break;
|
|
case 7: {
|
|
SmShell* shell = nullptr;
|
|
SmVertex* vertex = nullptr;
|
|
constructed(original->MakeShellVertex(nullptr, SmPoint3d(0,0,0), shell, vertex));
|
|
status = executive.BuildOffsetOfVertex(vertex, curves, orientations, surface); break;
|
|
}
|
|
case 8: case 9: {
|
|
SmSSIData intersections;
|
|
SmFaceuse* use = nullptr;
|
|
if (operation == 9) use = plane_face(ctx, original.get(), 0)->GetUpwardFaceuse();
|
|
status = executive.BuildOffsetOfFaceuse(use, surface, intersections); break;
|
|
}
|
|
case 10: status = executive.PiecewiseMerge(result); break;
|
|
case 11: {
|
|
SmFace* face = plane_face(ctx, original.get(), 0);
|
|
status = executive.MapEdgeuses(face->GetUpwardFaceuse(), face);
|
|
original->GetEdgeuses(originals); offsets.Append(originals);
|
|
break;
|
|
}
|
|
case 12: {
|
|
SmFace* face = plane_face(ctx, original.get(), 0);
|
|
face->GetUpwardFaceuse()->GetEdgeuses(originals);
|
|
if (!originals.GetSize()) throw Argument{};
|
|
SmEdgeuse* use = originals[0];
|
|
if (!use->GetRadial()) throw Argument{};
|
|
status = executive.BuildOffsetOfEdgeuses(use, use->GetRadial(), SM_MT_MARK);
|
|
originals.ReSet();
|
|
break;
|
|
}
|
|
}
|
|
// PiecewiseMerge transfers ownership out of m_pOffsetBrep before stitching.
|
|
std::unique_ptr<SmBrep> result_owner(result);
|
|
std::unique_ptr<SmSurface> surface_owner(surface);
|
|
out.insert(out.end(), {0, (double)status, (double)curves.GetSize(), (double)orientations.GetSize(),
|
|
surface ? 1.0 : 0.0, result ? 1.0 : 0.0,
|
|
executive.GetOffsetBrep() ? 1.0 : 0.0});
|
|
out.push_back(originals.GetSize());
|
|
for (ULONG i=0; i<originals.GetSize(); ++i)
|
|
out.push_back(id(offsets, executive.GetOffsetEdgeuse(originals[i])));
|
|
for (ULONG i=0; i<curves.GetSize(); ++i) delete curves[i];
|
|
}
|
|
// Geometry handed to native constructors that may or may not adopt it: anything
|
|
// still unowned when the scope ends is the fixture's to delete.
|
|
struct Unowned {
|
|
std::vector<SmCurve*> curves;
|
|
SmSurface* surface = nullptr;
|
|
Unowned() = default;
|
|
Unowned(const Unowned&) = delete;
|
|
Unowned& operator=(const Unowned&) = delete;
|
|
~Unowned() {
|
|
for (SmCurve* c : curves) if (c && !c->GetOwner()) delete c;
|
|
if (surface && !surface->GetOwner()) delete surface;
|
|
}
|
|
};
|
|
|
|
SmPoint3d vertex_point(SmEdgeuse* use) {
|
|
if (!use || !use->GetVertexuse() || !use->GetVertexuse()->GetVertex()) throw Argument{};
|
|
return use->GetVertexuse()->GetVertex()->GetPoint();
|
|
}
|
|
|
|
// A plane face made by MakeFaceWithCurves from explicit 3D and UV lines
|
|
// (CreateFaceFromSurface's natural trim curves have no Rust fixture twin): the
|
|
// straight-edged loop `points` at height z on the plane with axes X, Y (or Y, X
|
|
// when `flip`, so its normal points down and UV is (y, x)), the plane's domain =
|
|
// the loop's UV bounding box.
|
|
SmFace* curve_face(SmContext& ctx, SmBrep* target, const std::vector<SmPoint2d>& points, double z,
|
|
bool flip) {
|
|
const size_t n = points.size();
|
|
if (n < 3) throw Argument{};
|
|
double x0 = points[0].x, y0 = points[0].y, x1 = x0, y1 = y0;
|
|
for (const SmPoint2d& p : points) {
|
|
x0 = std::min(x0, p.x); y0 = std::min(y0, p.y); x1 = std::max(x1, p.x); y1 = std::max(y1, p.y);
|
|
}
|
|
Unowned owned;
|
|
SmTArray<SmCurve*> curves3d;
|
|
SmTArray<SmBSplineCurve*> uv;
|
|
// MakeFaceWithCurves deletes the UV curves it is given (SmObjsDelete,
|
|
// SmBrep.cpp:15743), so they leave this guard when the call is made.
|
|
struct UvGuard {
|
|
SmTArray<SmBSplineCurve*>* list;
|
|
~UvGuard() { if (list) for (ULONG i=0; i<list->GetSize(); ++i) delete (*list)[i]; }
|
|
} uv_guard{&uv};
|
|
SmTArray<SmOrientType> orients;
|
|
SmTArray<SmPoint3d> vertex_points; // MakeFaceWithCurves loop points (none)
|
|
SmTArray<ULONG> loops;
|
|
SmExtent1d unit(0,1);
|
|
auto uv_of = [flip](double x, double y) { return flip ? SmPoint3d(y, x, 0) : SmPoint3d(x, y, 0); };
|
|
for (size_t i=0; i<n; ++i) {
|
|
const SmPoint2d& pa = points[i];
|
|
const SmPoint2d& pb = points[(i+1) % n];
|
|
SmPoint3d a(pa.x, pa.y, z), b(pb.x, pb.y, z);
|
|
SmLine* line = nullptr;
|
|
SmStatus status = SmLine::CreateLineSegment(ctx, 3, a, b, line, &unit);
|
|
owned.curves.push_back(line);
|
|
constructed(status);
|
|
SmBSplineCurve* trim = nullptr;
|
|
status = SmBSplineCurve::CreateLineSegment(ctx, 2, uv_of(a.x, a.y), uv_of(b.x, b.y), trim);
|
|
uv.Add(trim); // deleted with the array until handed to MakeFaceWithCurves
|
|
constructed(status);
|
|
if (!line || !trim) throw std::bad_alloc();
|
|
curves3d.Add(line); orients.Add(SM_OT_SAME);
|
|
}
|
|
loops.Add((ULONG)n);
|
|
SmExtent2d domain = flip ? SmExtent2d(SmPoint2d(y0, x0), SmPoint2d(y1, x1))
|
|
: SmExtent2d(SmPoint2d(x0, y0), SmPoint2d(x1, y1));
|
|
owned.surface = new (ctx) SmPlane(SmPoint3d(0,0,z), flip ? SmVector3d(0,1,0) : SmVector3d(1,0,0),
|
|
flip ? SmVector3d(1,0,0) : SmVector3d(0,1,0),
|
|
SmVector2d(1,1), domain, &ctx);
|
|
SmRegion* region = nullptr;
|
|
SmShell* shell = nullptr;
|
|
SmFace* face = nullptr;
|
|
uv_guard.list = nullptr;
|
|
constructed(target->MakeFaceWithCurves(target->GetInfiniteRegion(), loops, &curves3d, &uv,
|
|
orients, vertex_points, owned.surface, domain, SM_OT_SAME,
|
|
region, shell, face));
|
|
if (!face || !face->GetUpwardFaceuse() || !face->GetUpwardFaceuse()->GetMate())
|
|
throw Argument{};
|
|
return face;
|
|
}
|
|
// The loop of a rectangle, counter-clockwise from its lower-left corner, with the
|
|
// bottom edge split at `split` when given.
|
|
std::vector<SmPoint2d> rectangle_loop(const SmExtent2d& rect, const double* split) {
|
|
const double x0 = rect.GetMin().x, y0 = rect.GetMin().y, x1 = rect.GetMax().x, y1 = rect.GetMax().y;
|
|
std::vector<SmPoint2d> points{SmPoint2d(x0, y0)};
|
|
if (split) points.push_back(SmPoint2d(*split, y0));
|
|
points.insert(points.end(), {SmPoint2d(x1, y0), SmPoint2d(x1, y1), SmPoint2d(x0, y1)});
|
|
return points;
|
|
}
|
|
SmFace* curve_face(SmContext& ctx, SmBrep* target) {
|
|
return curve_face(ctx, target, rectangle_loop(SmExtent2d(SmPoint2d(0,0), SmPoint2d(1,1)), nullptr),
|
|
0.0, false);
|
|
}
|
|
|
|
// Per original edge use of `use`, sorted by the use's own start point (GetEdgeuses
|
|
// starts a loop at a construction-dependent use): start xyz, then -1 when
|
|
// GetOffsetEdgeuse is null, else 1 and the start points of the offset use and of
|
|
// its mate.
|
|
void put_mapped_uses(std::vector<double>& out, Executive& executive, SmFaceuse* use) {
|
|
SmTArray<SmEdgeuse*> uses;
|
|
use->GetEdgeuses(uses);
|
|
std::vector<std::vector<double>> records;
|
|
for (ULONG i=0; i<uses.GetSize(); ++i) {
|
|
std::vector<double> record;
|
|
put_point(record, vertex_point(uses[i]));
|
|
SmEdgeuse* mapped = executive.GetOffsetEdgeuse(uses[i]);
|
|
if (!mapped) { record.push_back(-1); }
|
|
else {
|
|
record.push_back(1);
|
|
put_point(record, vertex_point(mapped));
|
|
put_point(record, vertex_point(mapped->GetMate()));
|
|
}
|
|
records.push_back(record);
|
|
}
|
|
std::sort(records.begin(), records.end());
|
|
out.push_back((double)records.size());
|
|
for (auto& r : records) out.insert(out.end(), r.begin(), r.end());
|
|
}
|
|
|
|
// BuildOffsetOfFaceuse with a real offset plane z = distance over the given domain.
|
|
// Input: distance, approx_tol, domain, local, extend, merge, mate, then a selector
|
|
// 0 / 1 / 2 and that many UV segments of one self-intersection: the 3D line at
|
|
// z = distance runs along the first segment; the second UV curve is the same line
|
|
// (selector 1) or the second segment (selector 2). Output: status, the related
|
|
// faces (edge count and UV domain each, sorted by domain), offset B-rep
|
|
// face/edge/vertex counts, its sorted vertex points, then per original edge use
|
|
// (sorted by its start point) GetOffsetEdgeuse's end points.
|
|
void face_tail(Reader& rd, std::vector<double>& out) {
|
|
double distance = rd.number(), tol = rd.number();
|
|
SmExtent2d domain = rd.domain();
|
|
bool local = rd.flag(), extend = rd.flag(), merge = rd.flag(), mate = rd.flag();
|
|
int64_t segments = rd.index(2);
|
|
bool has_ssi = segments > 0;
|
|
SmPoint2d s0(0,0), s1(0,0), t0(0,0), t1(0,0);
|
|
if (has_ssi) { s0 = rd.uv(); s1 = rd.uv(); t0 = s0; t1 = s1; }
|
|
if (segments == 2) { t0 = rd.uv(); t1 = rd.uv(); }
|
|
rd.done();
|
|
SmContext ctx;
|
|
auto original = brep(ctx), offset = brep(ctx);
|
|
SmFace* face = curve_face(ctx, original.get());
|
|
SmFaceuse* use = face->GetUpwardFaceuse();
|
|
if (mate) use = use->GetMate();
|
|
SmOffsetGeometryCreation geometry(distance, tol);
|
|
Executive executive(ctx, local ? SM_OO_LOCAL_OPERATION : SM_OO_SOLID_OFFSET, geometry,
|
|
original.get());
|
|
executive.install(std::move(offset));
|
|
executive.SetExtendConvexEdges(extend ? TRUE : FALSE);
|
|
executive.SetMergeResults(merge ? TRUE : FALSE);
|
|
// Declared after the executive: checks ownership before the offset B-rep dies.
|
|
Unowned owned;
|
|
SmSSIData intersections;
|
|
if (has_ssi) {
|
|
SmExtent1d unit(0,1);
|
|
SmLine* line = nullptr;
|
|
SmStatus status = SmLine::CreateLineSegment(ctx, 3, SmPoint3d(s0.x, s0.y, distance),
|
|
SmPoint3d(s1.x, s1.y, distance), line, &unit);
|
|
owned.curves.push_back(line);
|
|
constructed(status);
|
|
intersections.m_v3DCurves.Add(line);
|
|
for (int i=0; i<2; ++i) {
|
|
SmBSplineCurve* trim = nullptr;
|
|
const SmPoint2d& a = i ? t0 : s0;
|
|
const SmPoint2d& b = i ? t1 : s1;
|
|
status = SmBSplineCurve::CreateLineSegment(ctx, 2, SmPoint3d(a.x, a.y, 0),
|
|
SmPoint3d(b.x, b.y, 0), trim);
|
|
owned.curves.push_back(trim);
|
|
constructed(status);
|
|
(i ? intersections.m_vUVCurves2 : intersections.m_vUVCurves1).Add(trim);
|
|
}
|
|
}
|
|
owned.surface = new (ctx) SmPlane(SmPoint3d(0,0,distance), SmVector3d(1,0,0),
|
|
SmVector3d(0,1,0), SmVector2d(1,1), domain, &ctx);
|
|
SmSurface* surface = owned.surface;
|
|
SmStatus status = executive.BuildOffsetOfFaceuse(use, surface, intersections);
|
|
out.insert(out.end(), {0, (double)status});
|
|
SmTArray<SmFace*> related;
|
|
executive.faces_of(use, related);
|
|
std::vector<std::vector<double>> summaries;
|
|
for (ULONG i=0; i<related.GetSize(); ++i) {
|
|
SmTArray<SmEdge*> edges;
|
|
related[i]->GetEdges(edges);
|
|
SmExtent2d d = related[i]->GetUVDomain();
|
|
summaries.push_back({d.GetMin().x, d.GetMin().y, d.GetMax().x, d.GetMax().y,
|
|
(double)edges.GetSize()});
|
|
}
|
|
std::sort(summaries.begin(), summaries.end());
|
|
out.push_back((double)summaries.size());
|
|
for (auto& v : summaries) out.insert(out.end(), v.begin(), v.end());
|
|
SmBrep* result = executive.GetOffsetBrep();
|
|
if (!result) throw Argument{};
|
|
SmTArray<SmFace*> faces;
|
|
SmTArray<SmEdge*> edges;
|
|
SmTArray<SmVertex*> vertices;
|
|
result->GetFaces(faces); result->GetEdges(edges); result->GetVertices(vertices);
|
|
out.insert(out.end(), {(double)faces.GetSize(), (double)edges.GetSize(),
|
|
(double)vertices.GetSize()});
|
|
std::vector<std::vector<double>> points;
|
|
for (ULONG i=0; i<vertices.GetSize(); ++i) {
|
|
SmPoint3d p = vertices[i]->GetPoint();
|
|
points.push_back({p.x, p.y, p.z});
|
|
}
|
|
std::sort(points.begin(), points.end());
|
|
for (auto& p : points) out.insert(out.end(), p.begin(), p.end());
|
|
put_mapped_uses(out, executive, use);
|
|
}
|
|
|
|
// MapEdgeuses alone, on two MakeFaceWithCurves faces: the original rectangle at
|
|
// z = 0 and an offset rectangle at z = distance (its plane flipped when asked),
|
|
// matching the upward or downward original use. Input: distance, original rect,
|
|
// offset rect, flip, mate, then a flag and optionally the x at which the offset
|
|
// rectangle's bottom edge is split into two collinear edges. Output: status, then
|
|
// per original edge use (sorted by its start point) GetOffsetEdgeuse's end points
|
|
// as in face_tail.
|
|
void map_twin(Reader& rd, std::vector<double>& out) {
|
|
double distance = rd.number();
|
|
SmExtent2d original_rect = rd.domain(), offset_rect = rd.domain();
|
|
bool flip = rd.flag(), mate = rd.flag(), has_split = rd.flag();
|
|
double split = 0;
|
|
if (has_split) {
|
|
split = rd.number();
|
|
if (!(offset_rect.GetMin().x < split && split < offset_rect.GetMax().x)) throw Argument{};
|
|
}
|
|
rd.done();
|
|
SmContext ctx;
|
|
auto original = brep(ctx), offset = brep(ctx);
|
|
SmFace* face = curve_face(ctx, original.get(), rectangle_loop(original_rect, nullptr), 0.0, false);
|
|
SmFace* twin = curve_face(ctx, offset.get(), rectangle_loop(offset_rect, has_split ? &split : nullptr),
|
|
distance, flip);
|
|
SmFaceuse* use = face->GetUpwardFaceuse();
|
|
if (mate) use = use->GetMate();
|
|
SmOffsetGeometryCreation geometry(distance, 1e-6);
|
|
Executive executive(ctx, SM_OO_SOLID_OFFSET, geometry, original.get());
|
|
executive.install(std::move(offset));
|
|
SmStatus status = executive.MapEdgeuses(use, twin);
|
|
out.insert(out.end(), {0, (double)status});
|
|
put_mapped_uses(out, executive, use);
|
|
}
|
|
void replace(Reader& rd, std::vector<double>& out) {
|
|
SmContext ctx;
|
|
int64_t base_kind = rd.index(1);
|
|
std::unique_ptr<SmSurface> base;
|
|
if (base_kind == 0) {
|
|
base = read_surface(rd, ctx);
|
|
} else {
|
|
std::unique_ptr<SmCurve> curve = read_curve(rd, ctx);
|
|
SmBSplineCurve* generator = SM_CAST_PTR(SmBSplineCurve, curve.get());
|
|
if (!generator) throw Argument{};
|
|
SmPoint3d origin = rd.point();
|
|
SmVector3d direction = rd.point();
|
|
SmExtent2d domain = rd.domain();
|
|
bool swap = rd.flag();
|
|
// The extrusion owns its generator curve.
|
|
base.reset(new (ctx) SmSurfOfExtrusion(generator, origin, direction, domain,
|
|
swap ? TRUE : FALSE));
|
|
curve.release();
|
|
}
|
|
double distance = rd.number(), tol = rd.number();
|
|
int64_t nsamples = rd.index(64);
|
|
std::vector<SmPoint2d> samples;
|
|
for (int64_t i=0; i<nsamples; ++i) {
|
|
SmPoint2d sample = rd.uv();
|
|
if (sample.x<0 || sample.x>1 || sample.y<0 || sample.y>1) throw Argument{};
|
|
samples.push_back(sample);
|
|
}
|
|
rd.done();
|
|
auto original = brep(ctx);
|
|
struct Pending {
|
|
SmSurface* p;
|
|
~Pending() { if (p && !p->GetOwner()) delete p; }
|
|
} pending{new (ctx) SmOffsetSurface(distance, *base, TRUE)};
|
|
base.release();
|
|
SmExtent2d natural = pending.p->GetNaturalUVDomain();
|
|
SmFace* face = nullptr;
|
|
constructed(original->CreateFaceFromSurface(pending.p, natural, face));
|
|
if (!face) throw Argument{};
|
|
SmStatus status = SmOffsetExecutive::ReplaceImplicitOffsets(original.get(), tol);
|
|
SmTArray<SmFace*> faces;
|
|
original->GetFaces(faces);
|
|
out.insert(out.end(), {0, (double)status, (double)faces.GetSize()});
|
|
for (ULONG i=0; i<faces.GetSize(); ++i) {
|
|
SmSurface* surface = faces[i]->GetSurface();
|
|
out.push_back(surface ? (double)surface->GetType() : -1.0);
|
|
put_domain(out, faces[i]->GetUVDomain());
|
|
if (!surface) continue;
|
|
SmExtent2d domain = surface->GetNaturalUVDomain();
|
|
put_domain(out, domain);
|
|
out.push_back((double)samples.size());
|
|
for (const SmPoint2d& f : samples) {
|
|
SmPoint2d uv((1.0-f.x)*domain.GetMin().x + f.x*domain.GetMax().x,
|
|
(1.0-f.y)*domain.GetMin().y + f.y*domain.GetMax().y);
|
|
SmPoint3d point(0,0,0);
|
|
SmStatus eval = surface->EvaluatePoint(uv, point);
|
|
out.push_back(eval);
|
|
if (eval == SM_SUCCESS) put_point(out, point);
|
|
else out.insert(out.end(), 3, 0.0);
|
|
}
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
// Plain-C ABI, mode 0 config / 1 synchronize / 2 extend / 3 probe / 4 face tail /
|
|
// 5 map twin / 6 vertex / 7 replace.
|
|
// Caller owns every buffer; no native allocation or object address crosses it.
|
|
SC_EXPORT int32_t sc_offset_offset_executive_p1_eval(
|
|
int32_t mode, const double* input, int64_t input_len,
|
|
double* out, int64_t cap, int64_t* out_len) {
|
|
if (!out_len || cap < 0 || (cap && !out) || !input || input_len < 0 || input_len > max_values)
|
|
return SC_ERR_ARG;
|
|
try {
|
|
SC_NATIVE_LOCK;
|
|
Reader rd{input, input_len};
|
|
std::vector<double> result;
|
|
try {
|
|
switch (mode) {
|
|
case 0: config(rd, result); break;
|
|
case 1: synchronize(rd, result); break;
|
|
case 2: extend(rd, result); break;
|
|
case 3: probe(rd, result); break;
|
|
case 6: vertex(rd, result); break;
|
|
case 4: face_tail(rd, result); break;
|
|
case 5: map_twin(rd, result); break;
|
|
case 7: replace(rd, result); break;
|
|
default: throw Argument{};
|
|
}
|
|
} catch (const Rejected& rejection) {
|
|
result = {-1, (double)rejection.status};
|
|
}
|
|
return sc_copy_out(result.data(), (int64_t)result.size(), out, cap, out_len);
|
|
} catch (const Argument&) {
|
|
return SC_ERR_ARG;
|
|
} catch (...) {
|
|
return SC_ERR_EXCEPTION;
|
|
}
|
|
}
|