03 — C ABI (nps_c_api.h)¶
Single host border. Source of truth: src/Engine/abi/nps_c_api.h —
the header "owns every public Nps* POD and every NPSENGINE_API
declaration" (nps_c_api.h:29). Implementations in
src/Engine/engine.cpp. Call-site inventory: docs/abi-inventory.md.
Policy: docs/adr/0001-abi-change-policy.md (cited at
nps_c_api.h:229-233).
The sole C# [DllImport] home is src/Nps.EngineHost/NpsAbi.cs —
apps never declare ABI imports themselves.
Rules¶
- POD field order + enum values stable; bump
NPS_ABI_VERSIONon break. Additive changes (new exports/PODs/trailingreserved[]growth) do not bump it. - Engine-owned pointers valid until documented next op or handle destroy.
- Load functions return
NpsLoadError(nps_c_api.h:53-65); others returnNpsResult(nps_c_api.h:72-84). 0 = success for both. - No STL / exceptions across boundary.
- Test exports exist only when
NPS_ENABLE_TEST_HOOKSis defined (Catch2 target). - Engine handle is REQUIRED for Load/Gen/Parse — null returns INVALID_ARGUMENT. Query getters are null-tolerant (0 / nullptr / safe defaults).
- Visibility macro in
src/Engine/abi/nps_export.h: Windows__declspec(dllexport) __cdecl; Linux__attribute__((visibility("default"))). The__cdeclpin matches every C# import'sCallingConvention.Cdecl.
NPS_ABI_VERSION¶
nps_c_api.h:234. Hosts call GetAbiVersion() (engine.cpp:2816,
returns the macro verbatim) at startup and assert equality.
Frozen POD sizes (static_assert)¶
Compile-time freeze at nps_c_api.h:215-222; runtime mirror-assert in
tests/NPSEngine.Tests/EngineBridgeTests.cs (AbiPODLayout_Frozen).
| POD | Size (bytes) | Decl | Purpose |
|---|---|---|---|
NpsGcodeMove |
36 | nps_c_api.h:128-137 |
x/y/z abs mm, e cumulative, f mm/min, type (0=TRAVEL, 1=EXTRUDE), layer, reserved[2] |
NpsLoadOptions |
32 | nps_c_api.h:92-96 |
loadMode (0=FULL, 1=METADATA_ONLY), maxBytes (0=engine default), reserved[4] must be zero |
NpsGcodeGenParams |
36 | nps_c_api.h:101-108 |
enableNonPlanar, layerHeight, volumetricFlowScale, feedrate, numLayers (0=engine-selected), reserved[4] |
NpsAnalysisFinding |
300 | nps_c_api.h:140-147 |
severity (0=Critical/1=Warning/2=Info), category[32], message[128], layer, moveIndex, suggestion[128]. Detail path only |
NpsAnalysisFindingSummary |
16 | nps_c_api.h:159-164 |
severity/layer/moveIndex/findingId — findingId indexes the full array, stable until next AnalyzeGcode |
NpsWarpGrid |
24 | nps_c_api.h:200-207 |
layer, gridW/H, minX/Y, gridRes — metadata for the engine-owned risk array |
NpsMaterialProfile |
108 | nps_c_api.h:175-196 |
Flat profile (type[16], thermal, motion, flow fields, chamberType[16], reserved[2]); empty type defaults "PLA" |
Notable field semantics:
NpsGcodeMove.reserved[0]= per-move accel mm/s² from M204;reserved[1]earmarked for elapsed time (nps_c_api.h:120-127).- Findings summary is stamp-coupling trim: list UI uses 16B rows;
the 300B detail arrives only on selection via
GetAnalysisFindingsDetail(nps_c_api.h:149-155,371-380).
Production exports (59)¶
Line refs: nps_c_api.h decl / engine.cpp def. Every export is a
thin null-checked delegate into nps::Engine (or nps::GcodeViewport
for the Viewport group).
Lifecycle (5)¶
| Export | Ref (h / cpp) | Role |
|---|---|---|
CreateEngine |
286 / 2789 | new nps::Engine(); caller owns, must DestroyEngine exactly once |
DestroyEngine |
287 / 2793 | delete; null-safe |
InitializeEngine |
288 / 2800 | Engine::Initialize() |
GetEngineVersion |
289 / 2806 | Build version; null handle → -1 |
GetAbiVersion |
296 / 2816 | Returns NPS_ABI_VERSION (1); no handle needed |
Load (3)¶
| Export | Ref (h / cpp) | Role |
|---|---|---|
LoadModelFromFile |
298 / 2820 | Path load, default options. Returns NpsLoadError; null handle → NPS_LOAD_INVALID_ARGUMENT |
LoadModelFromFileWithOptions |
299-300 / 2834 | + NpsLoadOptions; nullptr options → FULL + engine default limit |
GetLastErrorMessage |
301 / 2855 | Engine-owned UTF-8. Three states: nullptr = bad handle, "" = no error, "..." = message |
Mesh query (10)¶
| Export | Ref (h / cpp) | Role |
|---|---|---|
GetPlateCount |
303 / 2868 | Plates |
GetModelUnit |
304 / 2874 | Unit string; null handle → "millimeter" |
GetTotalVertexCount / GetTotalTriangleCount |
305-306 / 2880-2891 | Cached totals |
GetTriangleCountForPlate |
307 / 2892 | Per-plate |
GetVertexPositions / GetTriangleIndices / GetTriangleColorIndices |
308-312 / 2898-2926 | Engine-owned geometry streams, valid until next Load/Destroy |
GetPaletteColorCount / GetPaletteColor |
319-320 / 2930-2941 | Palette ARGB (0xAARRGGBB) |
Golden check fixture: 4-colors+Benchy+AMS+test-v2.3mf (repo root) =
1 plate / 16562 verts / 33097 tris / 4 palette colors. Production
bounds = compute from the vertex stream; GetMeshBounds is test-gated
(below).
G-code (9)¶
| Export | Ref (h / cpp) | Role |
|---|---|---|
GenerateGcode |
322 / 2947 | DEMO TOOLPATH — never real slicing; null params → defaults |
GetLastGeneratedGcode |
323 / 2957 | Gen-side buffer only — never shadowed by the parser (split buffers) |
GetLastParsedGcode |
324 / 2969 | Parse-side buffer, distinct from generated text; null handle → "No engine handle" sentinel |
GenerateGcodeToBuffer |
325-326 / 2979 | Gen + copy; the ONLY string writer reporting truncation (NPS_RESULT_TRUNCATED) + required bytes via written |
WriteGcodeToFile |
327-328 / 3018 | Gen + write; IO_ERROR on open failure |
ParseGcode / ParseBgcode |
329-330 / 3053-3064 | ASCII / binary BGCode parse; populates the shared move array + parsed-text buffer |
GetLastGcodeMoveCount / GetLastGcodeMoves |
331-332 / 3031-3042 | Shared move array after EITHER gen or parse; engine-owned const ptr |
Naming caution: the C# facade EngineSession.GenerateDemoToolpath
wraps GenerateGcode — it is a DEMO toolpath; slice/ is planned,
not implemented.
Viewport (18)¶
All forward to nps::GcodeViewport; all null-vp tolerant.
| Export | Ref (h / cpp) | Role |
|---|---|---|
CreateGcodeViewport / DestroyGcodeViewport |
334-335 / 5266-5272 | Separate opaque handle |
ViewportSetGcodeMoves |
336 / 5274 | Feed moves — exact name (not "ViewportSetMoves"). Viewport COPIES the array, surviving later engine Load/Parse |
ViewportSetLayerVisibility |
337 / 5280 | Max layer |
ViewportSetCamera |
338-339 / 5286 | rotX/rotZ in radians + pan + zoom |
ViewportBuildBuffers |
340 / 5293 | Build layer extrude/travel verts |
ViewportGetLayerCount |
341 / 5299 | Layers |
ViewportGetLayerExtrudeVerts / ViewportGetLayerTravelVerts |
342-345 / 5303-5323 | Per-layer float streams |
ViewportComputeMVP |
346 / 5325 | 16-float column-major matrix; null vp → identity |
ViewportSetSimulationState |
347 / 5334 | Playback index |
ViewportSetOverlayMode |
348 / 5339 | Mode ids; "motion-type" is what the "None" radio maps to, not a no-op |
ViewportSetWarpParams |
349 / 5344 | Warp deform |
ViewportSetOverlayValues / ViewportSetOverlayRange |
350-351 / 5349-5358 | Overlay pieces |
ViewportApplyOverlay |
357-358 / 5363 | One-shot aggregate packing OverlayState{} — folds the three setters above |
ViewportGetHeatmapColor |
359-360 / 5381 | Scalar → RGB; static pure function, no handle |
ProjectOrbitXY |
361-362 / 5400 | Stateless project helper; bit-exact port of deleted UI math |
Analysis (14)¶
| Export | Ref (h / cpp) | Role |
|---|---|---|
AnalyzeGcode |
364 / 5421 | Flat NpsMaterialProfile → grouped copy → run analyzers; nullptr → engine defaults/inferred |
GetInferredMaterialType / GetInferredSlicerType / GetInferredPrinter |
365-367 / 5615-5653 | Caller-owned string outs (string-out contract below) |
GetAnalysisFindingsCount / GetAnalysisFindings |
368-369 / 5460-5491 | Full 300B findings; null dst → returns available count (size probe) |
GetAnalysisFindingsSummaryCount / GetAnalysisFindingsSummary |
381-383 / 5498-5525 | Preferred list path: 16B summary rows; null dst → count |
GetAnalysisFindingsDetail |
384-385 / 5530 | One 300B finding by findingId. INVALID_ARGUMENT / NO_DATA / NOT_FOUND / SUCCESS |
GetOverlayValue |
386-387 / 5560 | Per-move overlay scalar; 0.0f on null/no data |
GetSupportedOverlayCount / GetSupportedOverlayIds |
388-389 / 5566-5586 | Overlay keyspace — ids is currently a fixed string "motion-type,thermal,warp-risk,bond-strength,actual-speed" (cpp:5579), not manager-derived |
GetWarpGridInfo / GetWarpGridRisk |
390-391 / 5594-5608 | Warp grid header + engine-owned row-major risk floats; NO_DATA when absent |
Test-gated exports (9) — NPS_ENABLE_TEST_HOOKS¶
Declarations and implementations both gated (nps_c_api.h:247-274,
398-411; impls engine.cpp:2615-2780, 5115-5257). The production
.so exports none of them; apps/EngineHost must not import them
(rationale engine.cpp:2606-2614).
| Export | Ref (h / cpp) | Role |
|---|---|---|
TestParseModelXmlForHarness |
248-251 / 2617 | ParseModelXml direct; outs = vert/tri counts + first vertex XYZ |
TestZipReaderExtractModelAndMetadata |
253-255 / 2647 | ZIP open + extract model + metadata; returns byte sizes |
TestZipReaderDirectError |
257-260 / 2699 | ZIP error paths; outErrorCode = LoadError + optional message |
TestGetPlateThumbnailSize |
262-263 / 2749 | Thumbnail PNG byte size; 0 for invalid plate |
GetMeshBounds |
272-273 / 2765 | Catch2 bounds helper only — NOT production C# (orphan-gated, comment cpp:2927-2929). INVALID_ARGUMENT (outs zeroed first) / NOT_FOUND / SUCCESS. Production callers compute bounds from GetVertexPositions/GetTriangleIndices |
TestGcodeParseForHarness |
399-401 / 5118 | Flat move dump (7 floats per move) |
TestThermalGridForHarness |
402-404 / 5154 | Thermal math via production TestComputeThermalGrid |
TestStructuralScoreForHarness |
405-408 / 5197 | Structural math via TestComputeStructuralScore |
TestAnalysisManagerThermal |
409-410 / 5234 | Full AnalysisManager::analyze smoke with default profile |
Gate: scripts/nps-n1m-12-production-gate.sh / ctest
nps_n1m_12_abi_header_gates_test_decls.
String-out contract¶
nps_c_api.h:276-283. Caller-owned buffers: null/zero-cap is a no-op
for the void writers (GetInferredMaterialType/SlicerType/Printer,
GetSupportedOverlayIds); when capacity is provided, write at most
cap-1 bytes + trailing NUL. GenerateGcodeToBuffer is the only
string writer that reports truncation (NPS_RESULT_TRUNCATED) and the
required byte count via written.
See also¶
engine-host.md— C# mirrorsengine-modules.md— thenps::Enginemethods these exports delegate to- Full per-symbol call sites in
docs/abi-inventory.md