Driving the Web Service over HTTP
The HiNC web service exposes everything its browser client does as a REST API, so a script or an agent can build, save and run a project with no browser open. This page is what such a caller needs around its requests: where the contract is and where it is wrong, how to sign in and read a reply, how files and projects are addressed, the key-based pattern most setup routes share, and how to replay a mission and color its steps by condition without disturbing anyone else. The order in which a project is assembled is Project Construction.
Contract and Sign-In
The service publishes its contract as two OpenAPI documents, both served in every environment:
- The Swagger document, opened from the Swagger UI at
/swaggerand served at/swagger/<contract version>/swagger.json— currently/swagger/v3.1/swagger.json. The UI always loads the current one, so a guessed/swagger/v1/swagger.jsonis a 404, not a missing contract. The UI and its document answer ahead of the sign-in stage and are readable without a session. - The framework document at
/openapi/v1.json. It is an ordinary routed endpoint, so while the login gate is on it needs the session cookie like any other route.
Routes and query parameters agree between the two. Request bodies do not. The framework document names every schema by its simple type name, so request types that share a name in different controllers collapse into one schema, and several routes advertise another route's body. Take body shapes from the Swagger document, which names schemas by full type name. Where only the framework document is at hand, these are the routes it gets wrong:
| Route | Shown as | Actually reads |
|---|---|---|
POST /api/mech/machine-tool/load |
{ xmlText, loadResources } |
{ rootName, relFile }, with rootName either ProjectDirectory or ResourceDir |
POST /api/Workpiece/workpiece-material/load |
{ xmlText, loadResources } |
{ rootName, relFile } |
POST /api/Workpiece/cutting-parameter/load |
{ xmlText, loadResources } |
{ rootName, relFile } |
POST /api/mech/spindle-capability/load |
{ xmlText, loadResources } |
{ xmlText, baseDirectory, relFile }; loading by path is POST /api/mech/spindle-capability/load-file with { rootName, relFile } |
POST /api/file-explorer/rename |
{ name } |
{ rootName, relativePath, newRelativePath } |
JSON property names are matched case-insensitively, so RootName and rootName are the same field,
and enumerations travel as their names. Values are not relaxed the same way: root names and geometry
or transformer kind names are compared exactly as written on this page.
Check the login gate before anything else. The shipped configuration leaves it off, so an
installed copy answers every route with no session, on its own computer only: its address is
localhost, and while the gate is off a request addressed to any host name but localhost,
127.0.0.1 or [::1] is answered 400 Bad Request unless the service's AllowedHosts lists that
name, so widening the address alone does not let a script on another computer in. A deployment can
turn the gate on, which lifts that host-name restriction unless it sets AllowedHosts, and while the
gate is on every route outside /api/auth answers 401 with an empty body until the caller has
signed in — which reads exactly like a service that is not running. GET /api/auth/status is always
anonymous and reports whether the gate is enabled, whether this caller is signed in, and the service
version. Sign in with POST /api/auth/login and the JSON body { username, password }. A good pair
answers { authenticated: true, … } and sets a cookie every later request must carry, so use an HTTP
client with a cookie jar; a wrong pair answers 401 with the code InvalidCredentials. With the gate
off, the login answers success without reading the body, so a script may sign in unconditionally. A
script in a browser page works only from a page the service served itself: the service sends no CORS
headers, so no other page may read its answers, and while the gate is off it answers 403 to another
page's requests — one on another port of the same computer included. A script outside a browser
sends neither Origin nor Sec-Fetch-Site and is not affected. Some routes answer with data but
are POSTs, because they also register the object they return for later editing, write files as they
serialize, or open a path you name: the Tool House holder and beam reads (CylindroidHolder/Get,
FreeformHolder/Get, Cutter/{id}/inner-beam, Cutter/{id}/upper-beam), ObjectManagement/GetXml,
mech/machine-tool/xml, NcProgram/file-lines and StlFile/GetFileInfo. A GET to one of them
answers 404, as a route that does not exist does (Reading a Reply). A few GETs still fill in a controller
parameter table's missing default as they read it, which a later project save writes out.
Then read GET /api/Project/status once. It reports whether a project is open, its path, and the
absolute admin and project folders on the server. Confirm the admin folder is the tree meant to be
written into before writing anything: a service started from a different configuration comes up just
as healthy, pointed at a different tree.
Reading a Reply
Routes take their input in one of three shapes, and the Swagger document says which: a JSON object
body such as { rootName, relFile }; query parameters, which is how most key-based edit routes take
both their key and their values — a route that takes a list, such as
POST /api/Cylindroid/UpdateAllPairs, takes the key as a query parameter and the list as a JSON body
(see Editing Objects by Key); or a bare JSON
value as the whole body — PUT /api/Workpiece/init-resolution takes the number itself, 0.25, not an
object.
An HTTP 200 does not by itself mean a request did what it asked. The service keeps the convention its
browser client relies on: a refusal that is not a malformed request comes back as 200 with
success: false and a message, and some partial successes come back as success: true with a
warning. For example:
POST /api/mech/machine-tool/loadnaming a file that does not exist, or called with no project open, answers 200 withsuccess: false; so do the workpiece-material and cutting-parameter loaders for a missing file or one that does not deserialize;POST /api/StlFile/UpdateSourcewhose file cannot be read answers 200 withsuccess: trueand awarning— the reference is stored anyway; a new StlFile then has no mesh, while one that already held a mesh keeps the old one, whichGetInfogoes on reporting and the next save writes under the new name, so comparetriangleCountwith the file, not onlyloaded;POST /api/Mission/list-command/entrieswith a command kind the catalog does not know answers 200 withsuccess: false;POST /api/Workpiece/workpiece-material/loadand the cutter'sflute-material/load,shank-material/loadandcoating-layers/{index}/material/loadinstall a material whose thermal constants are implausible as written, or not given — an element the file lacks takes no default, and the thermal model is skipped for the tool (for every tool when it is the workpiece material) — and carry the warning inmessages(Material-ThermalConstants--NotGiven,Material-ThermalConstants--NotFinitePositive,Material-ThermalConstants--OutOfRange; the flute and coating-layer loads alsoMaterial-WearCurves--NotGiven, a cutter material whose file lacks itsTemperatureVsHardnessCurveorTemperatureVsWearCoefficientCurve: installed, named, the native thermal model does not run for the tool), not inwarning.
Malformed requests answer 400 — an unknown root name, a path that leaves its root, an empty required
field. An object-store key that does not exist answers 404, and a key holding the wrong kind of object
answers 409. Most routes that create an object in the store answer with its key as a bare string —
plain text, unless the request's Accept header asks for JSON, which yields a quoted JSON string; a
few, such as POST /api/Cutter/{id}/upper-beam/create, answer a JSON object carrying the key.
A route that does not exist answers 404. A GET or HEAD to a path under /api that no route
serves — a misspelt route, or one that takes only POST — answers 404 with an
application/problem+json body whose detail names the method and the path; any other method
answers 405. No route answers HEAD, so a HEAD answers 404 even where a GET would answer. A path
outside /api belongs to the application's page, so a GET that leaves out the
/api prefix answers 200 with HTML (text/html). Check that the reply's Content-Type is the one
the route answers with — application/json for a JSON reply, text/plain for a bare key, a file
type for a download — before reading anything from it.
A reliable caller therefore checks the status code, then success, then warning and messages, and follows each
write with the matching read before building on it: GET /api/mech/machine-tool after a chain load,
GET /api/StlFile/GetInfo (loaded, triangleCount) after pointing an STL at a file,
GET /api/Workpiece/Status once the workpiece is set up.
One Service, One Project
A service instance holds exactly one open project and one object store, shared by every browser and
every script connected to it. Signing in decides who may connect, not whose project it is.
POST /api/Project/new or POST /api/Project/load replaces the project everyone on that instance is
working on, and the run controls act on whatever run is current: POST /api/Execution/stop takes no
run identifier and stops the run in progress, whoever started it.
Before driving an instance other people may be using:
- Read
GET /api/Project/statusandGET /api/Execution/status. Do not take over while another person's project is open or a run isRunningorPaused; a dedicated instance is the safe choice for a scripted build. - Give every object-store key a prefix no other caller uses (see Editing Objects by Key).
- Identify a run by the
runIdthatPOST /api/Execution/startreturns and theinstanceIdbeside it — the service process therunIdcounts in — and never stop a run from a timeout (see Running a Replay). - Do not edit the tool house, or the machine tool's and the fixture's solids, while a run is live.
While the player is locked (
RunningorPaused) every non-GET route of/api/ToolHouse,/api/Cutter,/api/CylindroidHolderand/api/FreeformHolderanswers409 { success: false, code: "ToolEditRefusedWhileRunning" }and changes nothing, except the index-only POSTs the tool-house page issues on every open (Initialize,SelectTool,{id}/inner-beam,{id}/upper-beam, the holders'GetandIndexCurrentGeometry); stop or reset first. While it is locked, and while a play a host request drives directly on the session shell is in flight (POST /api/SessionShell/PlayNcFileand its siblings, whichexecutionStatusdoes not show), the generic object-store routes (/api/Box3d,/api/Cylindroid,/api/StlFile,/api/TransformationGeom,/api/StaticTranslation, …) answer409 { success: false, code: "SolidEditRefusedWhileRunning" }to a write whose key names an object under the machine tool's or the fixture's solids, which the runtime copy shares with the setup instead of copying them (their index-only POSTs, such asTransformationGeom/IndexGeom, pass;TransformationGeom/IndexTransformeron a wrapper there that has no transformer yet is refused, since it would write aNoTransformon the null field), and the six solid routes of/api/mech/equipment-topology—enable-geom,disable-geom,create-geom,set-geom-transform,refresh-geom-cacheandPOST …/rgb— answer the same, as does itsindex-geomon an anchor that has no solid yet, since it would hang one. For a tool the generic routes are not gated by the player: a script must not send them against a tool's keys during a run either — the cutter they edit is the one the run cuts with, and only the resync that follows would be refused. - Do not teleport, optimize or convert over HTTP while a run is live. While the player is locked
(
RunningorPaused),POST /api/SessionShell/PlayToolingTeleportandPlayClTeleportplay nothing, andOptimizeToFilesandConvertClToNcFileswrite nothing: each answers 409 withsuccess: falseand one entry inmessageswhoseidnames the reason —Play--BusyAct,OptimizeToFiles--Busy,ConvertClToNc--Busy— and the same message is in theshelllist ofGET /api/Execution/messages. A teleport is refused the same way withPlay--BusyActAnotherThreadwhile another request plays an NC program on the session, and withPlay--Reviewingwhile a play result is under review. The program plays (PlayNcFile,PlayNc, …) answer 409 the same way:Play--Busywhile the player is locked,Play--BusyAnotherThreadwhile another request plays,Play--BusyResetwhile the player is being reset. Make these calls after the run. - Do not call the session or the player while the project is being changed or the runtime reset.
While a project New, Load, Reload or Close runs, or another request's
POST /api/SessionShell/ResetRuntimeorEndSessionis resetting the runtime or ending the session, the plays answer 409Play--BusyResetand the teleportsPlay--BusyActReset,BeginSession,ResetRuntime,EndSessionandResetanswerBeginSession--BusyReset,ResetRuntime--BusyReset,EndSession--BusyResetandReset--BusyReset, andPOST /api/Execution/start,run-lineandrun-stepanswer409 { success: false, code: "Start--BusyReset", message }, asPOST /api/Execution/resetanswersReset--BusyReset. While another request plays a teleport,ResetRuntime,EndSessionand the two resets answerResetRuntime--BusyAct,EndSession--BusyActandReset--BusyAct, and a project New, Load, Reload or Close answers 409 with a message naming the play, the act or the reset it waits for. Wait for it to end, then call again.
Files and Named Roots
Every file path the API takes is relative to one of three named roots, passed by name:
| Root name | Resolves to |
|---|---|
AdminDirectory |
the service's working area; the project paths given to /api/Project/* are relative to it |
ProjectDirectory |
the folder holding the open project file — unavailable, and refused with 400, while no project is open |
ResourceDir |
the shipped library, the Resource folder under the admin area |
GET /api/file-explorer/roots lists the roots available at the moment. A path that leaves its root
is refused with 400. The paths a mission command stores take no root name: a Program File's program
and a Post-Execution template are relative to the project folder, the one ProjectDirectory names,
and an absolute template is used as it is.
Files reach the server through the routes under /api/file-explorer:
| Route | Arguments |
|---|---|
POST upload |
query rootName and relativePath; the file as the multipart form field file. Missing parent folders are created and an existing file is overwritten. |
POST write-text |
body { rootName, relativePath, content }; written as UTF-8 without a byte-order mark, parent folders created |
POST create-directory |
body { rootName, relativePath }; a no-op when the folder exists |
GET list, GET download, DELETE delete |
query rootName and relativePath; delete removes a folder recursively |
There is no cross-root copy: copy duplicates beside the source and rename refuses to move between
roots. Because ProjectDirectory exists only once a project is open, inputs staged before the
project is created go to AdminDirectory, under the folder the project file will sit in —
<project folder>/NC/…, <project folder>/Geom/… — and are addressed through ProjectDirectory
afterwards.
What a loader does with a file depends on where it came from:
- Pre-prepared resources — machine-tool chain, spindle capability (
load-file), workpiece material, cutting parameters, cutter material — are loaded by{ rootName, relFile }. A file already inside the project is referenced where it is. A file picked underResourceDiris written into the project at the same sub-path, for exampleWorkpieceMaterial/<name>, and referenced there, so the saved project never points into the shared library; a file picked elsewhere underAdminDirectoryis written into the project root under its own name. Uploading the file into the project first and loading it fromProjectDirectorycomes to the same thing, and it is the form to use whenever the copy is not there: after a pick from the library, list the project's folder (GET /api/file-explorer/list?rootName=ProjectDirectory&relativePath=WorkpieceMaterial, and so on) and check the file landed — an instance whose loaders do not copy stores a reference that resolves to nothing, and the next save or close fails withCould not find file. The machine-tool loader accepts onlyProjectDirectoryandResourceDir, and a chain picked from the library comes with its side files. - An STL set with
POST /api/StlFile/UpdateSourceand{ stlFileKey, rootName, relFile }is recorded project-relative; one picked outside the project is recorded asGeom/<file name>, and its mesh is written there at the latest on the next save — but only when no file already sits at that path: an existingGeom/<file name>is kept and the reference then names it. Give meshes from different folders distinct names, or upload them into the project first. - NC programs are recorded as the plain path a mission command names. Nothing copies them: upload them.
Project Lifecycle
The project routes work on the one open project, addressed by a path relative to AdminDirectory:
| Route | Body | Effect |
|---|---|---|
GET /api/Project/status |
— | hasProject, projectPath, and the absolute admin and project folders |
POST /api/Project/new |
{ relativePath } |
creates the folder if needed, writes the new project file at once and opens it; refused with 400 when a file already exists at that path |
POST /api/Project/load |
{ relativePath } |
opens a project; the reply's messages lists load-time holes, such as a missing STL, a referenced side file that cannot be read (XmlSource-Read--Failed) or an older project's unreadable script file (ScriptFile-Read--Failed), none of which fails the load; a project file that itself cannot be read — missing, locked, malformed — is refused with 400, the message naming the file under its root, and no project is open afterwards (the open project is released before the file is read; the status hub reports it) |
POST /api/Project/reload |
— | re-reads the open project from its file, with the same messages; a file deleted, locked or damaged since is refused with 400 and no project is open afterwards: the in-memory project is released before the file is read |
POST /api/Project/save, close |
— | act on the open project |
POST /api/Project/saveas |
{ relativePath } |
writes to a new path, which becomes the open project; a file already there is overwritten without a prompt |
save, reload, close and saveas answer 400 when no project is open. The six project-file
operations take turns: one requested while another is running is refused with 409 rather than queued.
A new project is not empty. It carries a fixture in its default shape with an empty solid on its
geometry anchor, a workpiece with no geometry, an empty tool house and no machine chain, and a mission seeded
with eight root entries: Machining Resolution, Machining Motion Resolution, Collision Detection, Pause
on Failure, Physics, two Program File entries naming the placeholders NC/NC-File-Name-1.nc and
NC/NC-File-Name-2.nc, and a Post-Execution. The placeholder programs do not exist, and each one left
enabled raises the error RunNcFile--NotFound on every play and is skipped; the play goes on. An entry added through
POST /api/Mission/list-command/entries without an insertIndex lands at the end, after the seeded
Post-Execution. A Post-Execution writes only what has played above it, so nothing a program added
there plays reaches its outputs, and when nothing above it played either, its writers end on
StepFile-Write--NoFile and ShotFile-Write--NoFile (for the outputs switched on) instead of
finishing. Add a program with "insertIndex" set to the Post-Execution's current index — 5 once
the two placeholders are deleted — or move the Post-Execution to the end afterwards with
POST /api/Mission/list-command/entries/{path}/move and
{"direction": "to", "targetIndex": <last index>}. Edit the seeded entries in place, or delete them with
DELETE /api/Mission/list-command/entries/{path}, highest index first, because the entries after a
deleted one renumber; then read the tree back with GET /api/Mission/list-command/entries.
Rebuilding at the same path is close, delete, new: POST /api/Project/close, then
DELETE /api/file-explorer/delete?rootName=AdminDirectory&relativePath=<project path>, then
POST /api/Project/new. The other files in the folder — uploaded programs and resources — are left as
they are.
Finish every build with save, reload and a read-back. Reload re-reads the file, so the settings read after it are the ones the file actually holds; a value that does not survive the round trip was never saved.
Editing Objects by Key
Geometry, transformers, cylindroid profiles and similar setup values are edited through an object store rather than through one route per field. A caller works in three moves:
- Create or index an object and get its key. A create route builds an object, installs it where
it belongs, stores it under a key and returns that key. The key is passed as a query argument
(
geometryKeyon the workpiece's create routes,keyon the per-kindNewandNewWithValue, …); when it is left out, a generated one is used and returned. An index route publishes an object that already exists — the geometry a workpiece already holds, the transformer inside a transformation geometry — and returns its key. - Edit through the key. Per-kind routes take the key as a query parameter and the new values as
query parameters too —
POST /api/StaticTranslation/UpdateTrans?staticTranslationKey=…&x=…&y=…&z=…— or a list as a JSON body. - Read back through the key —
GET /api/StaticTranslation/Get?staticTranslationKey=…,GET /api/StlFile/GetInfo?stlFileKey=…, orGET /api/Index/GetType?key=…for any key.
A vector is never an object of its own. The translation of a static translation, the axis and
pivot of a rotation and the two corners of a box are values of the object that owns them, and are
written through that owner's route with the owner's key and the three axes as query parameters:
StaticTranslation/UpdateTrans, StaticRotation/UpdateAxis and UpdatePivot,
DynamicRotation/UpdateAxis and UpdatePivot, DynamicTranslation/UpdateAxis, and Box3d/Update
with both corners. The owner's Get reads the value back, and the two rotations and the dynamic
translation also answer NormalizeAxis. No route hands out a key to a vector.
Container routes derive the keys of what they create from the container's own key, so a chain of calls can be scripted without parsing replies:
| Call | Key returned |
|---|---|
POST /api/Workpiece/CreateRawGeometry?geometryKey=K&geometryType=TransformationGeom |
K |
POST /api/TransformationGeom/CreateGeom?transformationGeomKey=K&geometryType=StlFile |
K-Geom |
POST /api/TransformationGeom/CreateTransformer?transformationGeomKey=K&transformerType=StaticTranslation |
K-Transformer |
POST /api/Workpiece/SetRawGeometryTransform?geometryKey=K&transformerType=StaticTranslation |
K — the geometry already in the slot, wrapped in place; K-Geom and K-Transformer are kept in step, and transformerType=None unwraps it again |
POST /api/Workpiece/Initialize?sessionKey=S |
a JSON object naming S-Workpiece, S-RawGeom and S-IdealGeom; a geometry key is indexed only when its slot holds something (hasRawGeom, hasIdealGeom) |
POST /api/Fixture/Initialize?sessionKey=S |
a JSON object naming S-Fixture |
POST /api/mech/equipment-topology/{owner}/branch/{guid}/index-transformer |
equipment-topology.<owner>.branch.<guid>.transformer — the transformer on that branch of the fixture, workpiece or machine tool; a branch carrying none is first given a pass-through one |
Placing a mesh as the stock, translated into position, therefore runs as below, here with the
caller's own prefix job7-stock and a mesh already uploaded to Geom/stock.stl:
POST /api/Workpiece/CreateRawGeometry?geometryKey=job7-stock&geometryType=TransformationGeom
POST /api/TransformationGeom/CreateGeom?transformationGeomKey=job7-stock&geometryType=StlFile
POST /api/StlFile/UpdateSource { "stlFileKey": "job7-stock-Geom", "rootName": "ProjectDirectory", "relFile": "Geom/stock.stl" }
GET /api/StlFile/GetInfo?stlFileKey=job7-stock-Geom -> loaded: true, triangleCount > 0
POST /api/TransformationGeom/CreateTransformer?transformationGeomKey=job7-stock&transformerType=StaticTranslation
POST /api/StaticTranslation/UpdateTrans?staticTranslationKey=job7-stock-Transformer&x=-45&y=-30&z=-12
POST /api/Workpiece/UpdateRawGeometry?geometryKey=job7-stock (re-install, next section)
POST /api/Workpiece/ClearRawGeomCache
A create route replaces whatever the slot held; nothing carries over from the object it
displaced. Wrapping what is already there is a separate family, the owners' Set…Transform routes —
Workpiece/SetRawGeometryTransform, Workpiece/SetIdealGeometryTransform,
mech/equipment-topology/{owner}/anchor/{id}/set-geom-transform,
FreeformHolder/SetGeometryTransform, GeomCombination/SetItemTransformAt,
TransformationGeom/SetGeomTransform and Cutter/{id}/upper-beam/set-transform — each taking
transformerType, a transformer kind or None: a bare geometry is wrapped in a TransformationGeom
around that very object, a wrapped one gets a fresh transformer of that kind, and None hands the
inner geometry back to the slot; the route answers the slot's key. These are what the web client's
Transform picker posts, and the client never names TransformationGeom as a kind; the sequence
above stays valid for a script and builds the same wrapper.
Anchors and branches of the equipment
The transformers that place the fixture on the table and the workpiece on the fixture are not slots
with routes of their own. Each is the transformer of a branch in the topology of the part that
owns it, and the routes under /api/mech/equipment-topology address a part's anchors and branches
by guid, with the owner — machine-tool, fixture or workpiece — in the path:
GET /api/mech/equipment-topology/graphlists every anchor (guid,owner,roles) and every branch (guid,owner,fletchAnchorGuid,arrowAnchorGuid,transformerType,isAttachment,isErrorTerm). Find a branch by the role of the anchor it arrives at, never by its name: the workpiece branch whosearrowAnchorGuidis the workpiece anchor whoserolesincludeProgramZeroAnchoris the one that places program zero; the one arriving atFixtureMountplaces the workpiece on the fixture; the fixture branch arriving atTableMountplaces the fixture on the table. Skip the attachments (isAttachment: true, ownerequipment) — the equipment builds those itself from the roles.- Build a transformer under a key of the caller's own —
POST /api/StaticTranslation/NewWithValue?key=K&x=…&y=…&z=…— and install it withPOST /api/mech/equipment-topology/{owner}/branch/{guid}/update-transformer?transformerKey=K. The answer is the canonical key the transformer is re-indexed under,equipment-topology.<owner>.branch.<guid>.transformer, whichindex-transformeron the same branch also answers; editing through that key is the nested-edit case of the next section. - A role is bound with
POST /api/mech/equipment-topology/roles/{owner}/{role}and the body{ "guid": "…" }— an anchor guid forTableMount,ToolMount,WorkpieceMount,GeomAnchor,FixtureMountandProgramZeroAnchor, a branch guid forAxisX…AxisCandSpindle; an empty guid unbinds.GET roles/{owner}lists each role with its candidates and the checks that fail (Unbound,NotOwn,Duplicate,NotReachable,AxisTransformerMismatch,AxisOffChain,SpindleOffToolAxis, and for marks where none belongsErrorTermRole,ErrorTermDynamic,ErrorTermOnToolAxis,ErrorTermNotOnRole), and gives each axis and the spindle the guid of its error term,errorTermBranchGuid, empty when it has none. - An error term belongs to an axis or the spindle of the machine tool. It is added with
POST /api/mech/equipment-topology/roles/machine-tool/{role}/error-term,{role}beingAxisX…AxisCorSpindle, with no body: in one edit a new anchor and the marked error term — a Static Rotation of 0° about (0, 0, 1) until a value is written — go in just before the role's branch, and that branch is rebuilt from the new anchor under a new guid. The answer carries therole, the newanchor, theerrorTermand the role'sbranch.DELETEon the same route takes the error term and its anchor out and answers the role's branch, under a new guid again; take the guid from the answer, or read the roles again, before addressing that branch. The error's value is the error term's transformer, set withindex-transformerandupdate-transformerlike any branch's, static kinds only. The mark by itself is set and cleared withPOST /api/mech/equipment-topology/{owner}/branch/{guid}/error-term: the body{ "value": false }clears it, and{ "value": true }sets it again only on a branch that stands where an error term stands, so a script can compare a play with the marks cleared, in which the controller knows the errors, against one with them set. Posting the value the branch already holds changes nothing. The project's error-term switch isGET/PUT /api/mech/equipment-topology/error-terms, a bare JSONtrueorfalseas the whole body. Where an error term stands is in Error-Term Branches; a request the editor does not accept answers 409 with acodenaming the reason, and the HTTP Surface table lists those codes with every other route of this controller.
Every topology edit under this controller, an error term added or removed and a mark set or cleared
included, rewires the equipment, and the attachment branches are given new guids each time, so read
the graph again before addressing one; writing the error-term switch rewires nothing. The worktable
attachment — from the machine's table end to the fixture's TableMount, or to the workpiece's
FixtureMount when there is no fixture — is the one attachment with an editable transformer,
addressed with the owner equipment (…/equipment/branch/{guid}/index-transformer and
update-transformer; its key is equipment-topology.equipment.table-to-comp); any other attachment
answers 409 AttachmentFixed.
The refusals carry a code: 400 UnknownOwner, 404 MechanismNotFound when the part is not
installed, 409 MechanismNotEditable for a cutter-location device, 409 RoleElementTaken when the
element already plays another role of the same part, 409 AxisTransformerMismatch when an axis is
given, or a bound axis branch is set to, a transformer of the wrong kind, 409 TransformerInUse
when the object under transformerKey already sits on another branch (build one transformer per
branch; re-posting the one a branch already carries is never refused), and 409 SolidsNotAllowed
for a geometry route on the workpiece — its geometry is the raw and target slots above, not an
anchor solid.
The store is shared by every caller of the service and its keys are plain strings. Writing a key that
already exists silently repoints it, so every caller needs a key prefix no other caller uses. Keys stay
until removed with POST /api/Index/Remove?key=…; removing a key only forgets the handle and never
takes the object out of the project.
Making an Edit Reach the Run
A project holds its equipment twice: the setup that is saved, and a runtime copy that the runner, the physics, collision checking and the stock mesh are built from. The runtime copy is not patched field by field — apart from the environment values (spindle capability, coolant condition, ambient temperature), which the spindle-capability routes forward at once. It is rebuilt from the setup at two moments:
- at the start of a fresh session — the first play after a project is loaded, or after a Reset;
- when an install route marks the setup edited — at once when no session is open, otherwise at the
next play or Reset. The install routes are the ones that put an object into a slot or edit the
equipment's topology: creating or replacing a workpiece geometry, installing a transformer on a
branch with
update-transformer, binding a role, adding or deleting an anchor or a branch, adding or removing an error term, setting or clearing a mark, writing the project's error-term switch, loading a machine chain, a material or cutting parameters, aligning the workpiece to a work offset.
The status says when an install is waiting. GET /api/Execution/status answers hasSession —
something has played since the last Reset — and setupEquipmentDirty — an install route has marked
the setup edited and the runtime copy has not been rebuilt from it yet — beside resetCount, which
each Reset raises by one, and instanceId, the identity of the service process the runId counts
in (instanceStartedUtc says when that process started). While both flags are true the web
client's Equipment page shows a banner with a Reset now button. In host code the flag is
IsSetupEquipmentDirty, and
SetupEquipmentDirtyChanged is raised on each change.
An edit made through a child key marks nothing. StaticTranslation/UpdateTrans,
StlFile/UpdateSource, Box3d/Update, a cylindroid's pair updates and every other per-kind route
change the setup object in place. The change reaches the runtime copy at the next fresh session and not before: a play started on
a session that is still open — a second start with no Reset in between — runs against the old
placement or the old shape, and nothing in its messages says so. The setup canvas draws the
workpiece's raw and target solids from the runtime copy, so a nested edit to either geometry goes on
showing the old shape there; the anchors, the fixture and the machine it draws from the setup
itself.
After the last nested edit inside a slot or a branch, re-install the slot's own object — posting the same key again is enough — and clear its cached solid where it has one:
| Edited inside | Re-install | Then |
|---|---|---|
| Workpiece raw geometry | POST /api/Workpiece/UpdateRawGeometry?geometryKey=<slot key> |
POST /api/Workpiece/ClearRawGeomCache |
| Workpiece target geometry | POST /api/Workpiece/UpdateIdealGeometry?geometryKey=<slot key> |
POST /api/Workpiece/ClearIdealGeomCache |
| The transformer of a fixture, workpiece or machine-tool branch | POST /api/mech/equipment-topology/{owner}/branch/{guid}/update-transformer?transformerKey=<its key> |
— |
| The worktable attachment's transformer | POST /api/mech/equipment-topology/equipment/branch/{guid}/update-transformer?transformerKey=<its key> |
— |
This is what the browser client does after every nested edit, and it makes the edit effective whatever state the session is in. Resetting before every start is the second guard, and saving and reloading rebuilds the runtime copy as well — one more reason every build ends with save, reload and a read-back.
Running a Replay
A scripted replay is reset, start, then poll by run id:
GET /api/Execution/status. IfexecutionStatusisRunningorPaused, a run is live — do not reset, start or stop it; a reset stops a live run too.POST /api/Execution/reset. Start does not reset. After a finished run the session stays open, holding the machined workpiece, its steps and its messages, andPOST /api/Execution/startplays the mission again on top of it. Every Read On First Or Write record then sees steps already produced and writes instead of reading, so the stage-0 record overwrites the cached stock with the machined part — and every later run, reset or not, starts from stock that has already been cut until that file is deleted. Setup edits made through child keys are not picked up by such a play either. Reset before every start. While an NC program plays on the session outside the player's run — aPOST /api/SessionShell/PlayNcFilerequest, for one, whichexecutionStatusdoes not show — the reset answers409 { success: false, code: "Reset--Busy", message }and resets nothing: wait for that play to end, then reset. A write under the machine tool's or the fixture's solids answers409 { success: false, code: "SolidEditRefusedWhileRunning" }during such a play as during a run, only with no banner on the Equipment page.POST /api/Execution/start, and read the body. An enabled Script command that does not compile refuses the start with HTTP 200,success: falseandcode: "script_compile_error", naming the script and the error. A start while a run is live — running or paused — answersalreadyRunning: trueand starts nothing; therunIdbeside it is the live run's, and the service cannot tell whether it is yours (a client whose own start answer was lost may compare it with the lastrunIdit knew) — do not poll for it as your own. A start while an NC program plays on the session outside the player's run — aPOST /api/SessionShell/PlayNcFileorPlayNcrequest, for one — answers409 { success: false, code: "Start--BusyAnotherThread", message }and starts nothing: wait for that play to end, then start. While a project is being changed, or another request'sResetRuntimeorEndSessionruns, it answers the same withStart--BusyReset;run-lineandrun-stepanswer both the same way. A real start answersalreadyRunning: falsewith therunIdof your run and theinstanceIdof the service process it counts in: keep both. It also says whether the run began on a fresh session.continuesSession: true, with anotesaying so, means nothing came between the last play — finished or stopped — and this start to end its session, neither a Reset nor a project load or reload: the run replays the mission on the session that play left, its steps, its messages and the machined workpiece kept.continuesSession: false, with nonote, is a start on a fresh session. Atrueon a replay meant to be fresh says step 2 was skipped: reset, which stops the run, and start again.- Poll
GET /api/Execution/statusuntil it reports thatrunId, under the sameinstanceId, withexecutionStatuseitherFinishedorReady. After a Reset,Readyis how a run that was stopped, or that ended in an exception, reports itself, so a poller that waits forFinishedalone never returns from a failed run. Without a Reset in between, such a run inherits the previous run'sFinishedand passes for a completed one — one more reason to reset before every start. The exception behind aReadyis in the service log, whichGET /api/Project/logsreturns for the current day. A mission script's own error does not end the run: it is reported asScriptCommand-Run--Faultin theshelllist ofGET /api/Execution/messagesand the mission goes on, so read that list for aFinishedrun as well. A higherrunIdmeans another caller has started a run since, so this one is over. A differentinstanceIdmeans the service restarted: a new process counts its runs from 1 again, so the run is gone whatever therunIdand the status beside it say. - Read what the run produced before anything resets the session: a Reset empties the step strip, the meshed workpiece and the session's message lists. Check each answer's status, and that it is JSON, before reading it (Reading a Reply): a misspelt evidence route answers 404. The checks that tell a finished run from a passing one are in Replay Acceptance over the HTTP API.
If the poller gives up, let it give up. A timeout is not a reason to call POST /api/Execution/stop:
stop ends whatever run is current, which on a shared instance may not be this caller's, and a stopped
run skips everything below the point it reached — no Post-Execution output, no end-of-stage record.
Step Color Trees
A script or an agent colors a run's steps by condition under /api/execution/step-color-trees. Three
rules keep it from disturbing anyone else on the instance: check and preview a tree before putting it,
add a tree with If-None-Match: *, and set the color index — a project setting every connected user
sees — only when the tree must show on screen, writing the old key back afterwards while the color index is still the tree's key. The routes, the
shape of a tree, the answers and the requests in order are
Step Color Trees over HTTP.
Request Encoding
Send request bodies as UTF-8. The service decodes JSON as UTF-8, and a client that encodes the body in another code page loses every character outside that code page before the request leaves the machine — while the call still reports success. The damage shows only when the value is read back.
Windows PowerShell 5.1 is the usual case: Invoke-RestMethod with a string -Body and a content type
that carries no charset sends ISO-8859-1, so a group title written in Chinese is saved as ??.
Send bytes instead:
Invoke-RestMethod -Method Put -Uri $uri -WebSession $session `
-Body ([System.Text.Encoding]::UTF8.GetBytes($json)) `
-ContentType 'application/json; charset=utf-8'
or use an HTTP library that sends UTF-8 by default. The same holds for a program or any other text
sent through write-text: what the request body carried is what lands in the file.
See Also
- Getting Started with HiAPI — the in-process C# route to the same engine, with no service in between
- Project Construction — the build order these routes are used in, and which controller routes the run actually reads
- Replay Acceptance over the HTTP API — the evidence that tells a finished replay from a passing one
- Program and Hosting — the two OpenAPI documents, the request pipeline and where sign-in sits in it
- Login and Authentication — the gate's defaults, the cookie's lifetime and the three auth routes
- File Explorer Page — the complete file-route table
- Object Store (IndexService) — the store behind the key-based routes, and its lifetime
- Workpiece — the re-install-then-clear chain the browser client runs after every nested edit, and the workpiece's three roles
- Fixture — the fixture's roles, topology and solid, and the topology routes behind its panel
- Equipment Topology — the whole
api/mech/equipment-topologyroute table, the role checks and the error codes - Step Color Trees over HTTP — coloring a run's steps by condition: the color tree routes, the shape of a tree, the answers and the shared color index