Skip to content

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_VERSION on break. Additive changes (new exports/PODs/trailing reserved[] 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 return NpsResult (nps_c_api.h:72-84). 0 = success for both.
  • No STL / exceptions across boundary.
  • Test exports exist only when NPS_ENABLE_TEST_HOOKS is 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 __cdecl pin matches every C# import's CallingConvention.Cdecl.

NPS_ABI_VERSION

#define NPS_ABI_VERSION 1

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# mirrors
  • engine-modules.md — the nps::Engine methods these exports delegate to
  • Full per-symbol call sites in docs/abi-inventory.md