Workflow: Constructing a Project through the Web API
Basic Simulation describes what a project contains. This page describes how to build one so that it is actually finished — the ordering that avoids rework, how to shape the mission, and the acceptance test that tells you the project is complete rather than merely populated.
It is written for whoever assembles a .hincproj from a customer's deliverables — a person driving
the browser UI, or an agent driving the same operations over the HTTP API. Where a step has an API
form, the route sits with the step. Replaying and accepting the built project over the API is
Replay Acceptance over the HTTP API.
flowchart TD
New["1 · New project<br>(self-contained root)"]
Assets["2 · Place assets<br>NC, STL, resource files"]
Equip["3 · Equipment<br>chain, spindle"]
Job["4 · Job<br>stock, ideal geom, material"]
Ctrl["5 · Controller<br>brand + its own tables"]
Tools["6 · Tool house"]
Mission["7 · Mission<br>grouped, per-group resolution"]
Replay["8 · Replay and read<br>every message"]
Cross["9 · Numeric cross-checks"]
Assume["10 · Record every assumption"]
New --> Assets --> Equip --> Job --> Ctrl --> Tools --> Mission --> Replay --> Cross --> Assume
Replay -->|any warning| Ctrl
Build through the API, not by editing the XML
Create and modify projects through the service's HTTP API. It is the same code path the UI uses, so
a project built that way is on the supported create/load/save route. Hand-authoring the .hincproj
XML is a fallback for fields the API cannot yet reach — and when you hit one, the better fix is to
add the endpoint.
The sign-in, the reply envelope, the file roots, the project lifecycle and the key-based editing
pattern the steps below rely on are Driving the Web Service over HTTP,
which also lists the request bodies the framework's own OpenAPI document gets wrong. Three of its
rules decide whether a scripted build can be trusted: check success in every reply rather than only
the HTTP status; after editing inside a geometry or transformer through its key, re-install that
slot before the next run; and do not drive an instance someone else is using.
Which endpoints the run reads
A project carries two controller models: the runner the simulation plays, and a legacy model saved beside it. Several of their routes look alike, and a value written to the legacy one is accepted, saved and read back without complaint while the run never sees it. Write machine data through these:
| Datum | Route the run reads | Look-alike the run ignores |
|---|---|---|
| Machine chain | POST /api/mech/machine-tool/load — see below |
— |
| Spindle capability | POST /api/mech/spindle-capability/load-file {rootName, relFile} |
— |
| Controller brand | POST /api/mech/soft-nc-runner/brand {Brand, CopyWorkCoordinates} |
PUT /api/Controller/cnc-brand |
| Travel X, Y, Z | PUT /api/Controller/stroke-limit-xyz with exactly six numbers, [minX, maxX, minY, maxY, minZ, maxZ] in mm, which writes both models — any other count answers 400 and writes nothing; or PUT /api/mech/soft-nc-runner/machine-limits/{axis} {Positive, Negative} |
— |
| Stroke-limit check | PUT /api/Controller/stroke-limit-check true or false — a project flag outside both models, saved with the project and on by default. Off stops the check of each played step (Stroke-Limit--Over) and the session-begin StrokeLimit--Unconfigured warning; the position each NC block ends at is still compared with any limit set (StrokeLimit--Positive, StrokeLimit--Negative), but a motion that leaves the travel mid-block, such as a drilling cycle's plunge or an arc, then goes unreported |
— |
| Rapid rate | PUT /api/mech/soft-nc-runner/rapid-feedrates/{axis} {Value}, mm/min (deg/min on a rotary axis) |
PUT /api/Controller/rapid-feedrate |
| Home / G28 reference | PUT /api/mech/soft-nc-runner/home-positions/{axis} {Value} |
— |
| Tool-change position | PUT /api/mech/soft-nc-runner/tool-change/{axis} {Value}, or {Stay: true} to leave the axis where it is |
— |
| Tool-change time | PUT /api/mech/soft-nc-runner/tool-change/tooling-time {Value}, seconds |
PUT /api/Controller/tooling-time |
| Maximum spindle speed | PUT /api/mech/soft-nc-runner/controller-params/max-spindle-speed {Value}, rpm — recorded, not read (§4) |
— |
| Subprogram folders | PUT /api/mech/soft-nc-runner/subprogram-folders {InternalFolder, ExternalFolder}, relative to the project folder |
— |
The 400 that stroke-limit-xyz gives a wrong count names the shape it expects and the count it got
— for five numbers, Expected 6 values [minX, maxX, minY, maxY, minZ, maxZ] in mm; got 5. A missing
body or a JSON null is refused before the route runs: the framework's own validation answers it
with a 400 whose body is a problem-details JSON object rather than that message.
The four per-axis routes — machine-limits/{axis}, rapid-feedrates/{axis}, home-positions/{axis}
and tool-change/{axis} — take as {axis} one of the names their own GET lists, in any letter
case, and write under the listed spelling, so machine-limits/x sets X. The names listed are the
brand preset's seeded X, Y and Z plus the axes the machine tool's chain adds (§2); these routes add
none. Any other name is refused with HTTP 200, "success": false, the listed names in axes and a
message that names it — No axis 'Q' on this machine; its axes are X, Y, Z, B, C., or
No axis 'Q' on this machine; it has no axes yet. when nothing is listed — and nothing is written.
A name that matches several listed axes only when letter case is ignored is refused the same way
rather than guessed at:
No axis 'ax' on this machine: several axes (AX, Ax) match it only ignoring letter case, so none is taken; its axes are ….
The same split runs through the program-data tables: the work offsets and tool offsets a run
resolves are the runner's, under /api/mech/soft-nc-runner/ (§4). Read every value back with the
GET of the same route after a save and a reload.
Loading the machine chain
From a file, the chain loads through POST /api/mech/machine-tool/load with
{"RootName": "ProjectDirectory", "RelFile": "MachineTool/<name>.mt"}. RootName is
ProjectDirectory or ResourceDir, and any other root is refused with 400. A chain built elsewhere
therefore goes into the project folder first — uploaded through the file routes — and loads from
ProjectDirectory. A chain picked under ResourceDir is written into the project at the same
relative path, side files included, and referenced there. (POST /api/mech/machine-tool/update
installs a chain already in the object store, and …/create a blank cutter-location device; neither
reads a file.)
The route answers HTTP 200 whether or not the load worked: a missing file or a chain the loader
refuses comes back as "success": false with the reason in message. On success the body names the
chain's typeName and the relFile the project records, and GET /api/mech/machine-tool reports
the same afterwards. The load's reply does not carry the loader's messages: an end anchor the
machine lacks or carries in the wrong letter case is reported to the service log
(GET /api/Project/logs), and in the messages of the next POST /api/Project/reload once the
project is saved. A machine without both end anchors cannot run. A machine
the resource library does not carry can be built with no shapes —
A machine with no model.
1. One self-contained root
The .hincproj and every asset it references live under one folder — the project's own
directory — and every reference is relative to it with no ../.
<project>/
├── <name>.hincproj
├── NC/ the programs the mission plays
├── Delivered/ the customer's files, exactly as received
├── Geom/ workpiece / fixture STL
├── MachineTool/ the kinematic chain
├── SpindleCapability/
├── WorkpieceMaterial/
├── CuttingParameter/
├── CutterMaterial/
└── README.md what was assumed, and why
Two things do not move themselves, so bring them in by hand before you wire anything:
- NC files. A program-file command stores a plain relative path; nothing copies the file.
- Machine-tool side files. A chain that externalises its mechanism references the side file and
its STL bodies by bare name, relative to the
.mt's own folder — keep the whole folder together.
Resource files (chain, spindle, cutting parameters, workpiece material, cutter material) are
pre-prepared data loaded by reference. Loading one from the Resource root writes the project's own
copy at the same sub-path — WorkpieceMaterial/<name>, say — and records that project-relative
path, so the saved project never points into the shared library; a file already inside the project
is referenced where it is. Uploading the file into the project first and loading it from the Project
root comes to the same thing, and works on every version: check after each library pick that the
copy landed in the project folder, and upload it yourself where it did not
(Files and Named Roots).
Programs: keep the delivery, play a copy
Keep every program exactly as the customer delivered it, under Delivered/, and play copies from
NC/. Three kinds of change are made, and only to the copies:
- Names the call lookup needs. A subprogram or macro called by number is found only under a
fixed set of file names in the subprogram folder —
O0025.NCorO25.NCfor aP25call, among others; see General NC Code Support. A callee delivered asO25.datis copied under one of those names with its content untouched. A callee that is not found is reported as an error, the call is skipped, and the run still finishes — with nothing cut when all the cutting lives in the callee. - Constructs that would change the result. When a replay shows the reader not honouring a
construct the result depends on — a
Gcode chosen by a variable (G[#120]), a macro argument written as a variable (G65 P25 A#120), a reference return whose axis words sit behind anotherGcode in the block (G91 G28 G0 Z0) — rewrite that line in the copy into the literal form the machine executes (G55for aG[#120]holding 55;G65 P25 A55;G91 G28 Z0when that block returns Z home) and leave every other line as delivered. - What the operator does by hand. A program written for a machine whose operator changes the
tool by hand has no
T… M06, and nothing mounts a tool but a tool-change command or a Manual Tooling entry, so the copy on its own would play with the spindle empty and build no machining step (Step-Tool--NotEquipped). Keep the copy without the change, and put a Manual Tooling entry naming the tool above the program's Program File in the mission: it mounts the tool as the operator does, builds no step and takes no file number (Mounting the tool the operator loads by hand; its route is in the table of §3). The tool's length is still the program's: when it applies no length compensation, give the copy aG43 H1before its first move down, unless the length is folded into the work offset's Z instead, as the shop does (§4).
Open each rewritten copy with a comment naming what was changed, and list the same changes in the project's README with the reason for each, so the copy can be compared with the delivery line by line. When a release reads a construct natively, retire the rewrite and play the delivered line.
Constructs that only raise a known message without changing the motion — a named program header, a smoothing or look-ahead code, a cycle-time timer read — can stay as delivered. List them in the README as expected messages, by id and count per play, so the next replay is checked against that list instead of being investigated again.
2. Order the build to avoid rework
Later steps read earlier ones, so this order costs the least:
New project at its final path. It is not empty — see §3.
Machine tool, then spindle capability.
Workpiece: stock geometry, ideal (target) geometry, resolution, the transformers of the workpiece branches to its Program-Zero Anchor and its Fixture Mount, then material and cutting parameters. A material is used as its file writes it: one whose
HeatConductivity_WdmK(W/(m·K)),Density_gdcm3(g/cm³) orHeatCapacity_JdgK(J/(g·K)) is not a finite positive number, or lies outside the range of a solid — a density typed in kg/m³, say — comes back in the load'smessagesand is named again when a session or a play begins (Material-ThermalConstants--NotFinitePositive,Material-ThermalConstants--OutOfRange). One whose file lacks the element is not given, and no default stands in: the thermal model is skipped for every tool, with the temperatures left at ambient and the wear at zero, and the material is named at the same places (Material-ThermalConstants--NotGiven). When the part arrives as a mesh exported from CAM rather than as labelled stock and design CAD, first decide what the mesh is and where program zero sits in it — Building the Workpiece from a Reference Mesh, which also gives the API calls for an STL slot.Controller: brand first — switching brand later resets brand-specific tables — then the controller's own tables (§4).
Tool house, per tool, in this order — each step reads the one before:
- Create, then number. A new tool takes one past the largest number in the house; rename it
to the T number the program calls (
POST /api/ToolHouse/CreateTool, thenPOST /api/ToolHouse/RenameTool?toolId=…&newId=…). The number is also the offset row the H and D words read once the offset table is refreshed. - Shaper profile (
PUT /api/Cutter/{id}/shaper-profile), e.g.{"AptType": "BallApt", "Diameter_mm": 4, "FluteHeight_mm": 5};AptTypeis one ofColumnApt,ConeApt,BallApt,TaperAptandGeneralApt, case-insensitive and with or without theApt. A ball profile takes only the diameter and the length of cut. The cutter routes create the tool's milling cutter on first use, so no separate call is needed for it. - Fluting (
PUT /api/Cutter/{id}/fluting), after the profile: the edges are built against the profile of that moment, so re-send the fluting after any profile change. It is replaced whole:"Type": "uniform"is required — anything else is refused with 400 — and every other field left out takes the route's own default — 2 flutes, helix 30°, radial rake 0°, radial relief 5°, flute offset angle 0°. A rake of 0° is not neutral, since the rake orients the force each edge point produces; choose one from the work material when the tool sheet gives none (Cutter Geometry). Send every value, including a flute offset angle entered earlier, and the side contour itself ("SideContour": {"Type": "consthelix", …}): a fluting without one, or with a type the route does not recognise, is accepted and leaves the flutes with no side edge. - Exposed cutter height — the stick-out (
PUT /api/ToolHouse/{id}/exposed-height). A new tool starts at 0, with the cutter hidden inside the holder. - Setup angle — how far the cutter is turned in the holder
(
PUT /api/ToolHouse/{id}/setup-angle, the body a bare number of degrees). It defaults to 0 and only the milling physics reads it; enter it when the flutes' angular position matters, as it does for a smart holder reporting in the sensor's own frame (Smart Tool Holder). It is a property of the clamping, so it survives a fluting re-send. - Holder. List the library first
(
GET /api/file-explorer/list?rootName=ResourceDir&relativePath=Holder): a.Holderfile there carries a finished profile. No route loads one into a tool, so read it withGET /api/file-explorer/read-textand send its Z–R pairs whole as below. Set its type (POST /api/ToolHouse/SetHolderType), read its profile key (POST /api/CylindroidHolder/Getwith a session key answers it incylindroidKey, as<sessionKey>-HolderCylindroid), replace the whole profile withPOST /api/Cylindroid/UpdateAllPairs— a new cylindroid holder's profile is a Ø6 placeholder from Z 40 to Z 80 that adds no length until the holder re-derives it — thenPOST /api/CylindroidHolder/UpdateGeometryContentso the holder re-derives its length. The profile's frame is in Cutter Geometry. - Upper beam. Create it through the cutter
(
POST /api/Cutter/{id}/upper-beam/create?kind=Cylindroid&key=…— the new cylindroid is stored under the key you pass), write the pairs withPOST /api/Cylindroid/UpdateAllPairson that key — measured from the cutter tip, the first at the flute top and the last at the overall length (Cutter Geometry) — thenPOST /api/Cutter/{id}/upper-beam/resyncand read the warnings it returns; an empty list is the pass. - Cutter material, and coating layers if the tool has any. A tool without a flute material
runs no thermal physics and reports no yield-stress ratio (
Tool-FluteMaterial--Missing). Flute, shank and coating materials are checked as the workpiece's is: the load'smessagesand the tool change name a thermal constant that is not a finite positive number or lies outside the range of a solid, and one whose element the file lacks: not given, with no default standing in, it leaves the tool without a thermal step, as a missing flute material does (Material-ThermalConstants--NotGiven); a cutter material whose file lacks its hardness or wear-coefficient curve (TemperatureVsHardnessCurve,TemperatureVsWearCoefficientCurve) is named the same way where its curves are read — as the flute material or a coating layer's, never as the shank's (Material-WearCurves--NotGiven): the native thermal model does not run for the tool. - Smart-holder observation point, when the tool carries a sensor. A new tool points at the holder nose with a height of 0; see Smart Tool Holder.
Then refresh the offset tables from the tool house, and check each tool-length row against the holder's gauge length plus the stick-out.
GET /api/ToolHouse/{id}returns the stored fluting, profile and clamping values —setupAngle_degamong them — for a last read-back.- Create, then number. A new tool takes one past the largest number in the house; rename it
to the T number the program calls (
Program zero: with the workpiece placed and the chain loaded, align the two in the direction the job calls for — Program Zero Alignment. When the customer supplied recorded offsets, keep them and move the part onto them (§4); derive a row from the model only when planning a job that has no recorded offset. When several parts share the table, only the row of the part carrying the program-zero anchor can come from the model: the rows of the others are that row plus the part spacing.
Mission (§3).
Save, then load the saved file back and re-check. A project that cannot be reloaded cleanly is not built.
Important
Verify the axis set after loading a machine tool. Each axis is a role of the machine tool
(AxisX … AxisC) bound to one of its branches; a chain file binds them by branch name
(X/Y/Z/A/B/C), so a branch left at its authoring default name binds no axis, and that
axis silently does not exist. The controller's axis list cannot show this for a linear axis — every
brand preset already declares X, Y and Z, and a chain only adds axes — so read the roles:
GET /api/mech/equipment-topology/roles/machine-tool lists each axis with the branch it is bound
to and its checks (an unbound axis is simply unbound; AxisTransformerMismatch names a branch of
the wrong kind). Every axis needs a branch carrying a DynamicTranslation (X, Y, Z) or a
DynamicRotation (A, B, C); the chain itself is POST /api/mech/machine-tool/xml. The spindle
(Spindle) is not an axis and is found by topology, not by name: the branch arriving where the
tool mount hangs from, when it carries a DynamicRotation about the tool mount's −Z through its
origin. A machine without one plays as before, its spindle simply not turned on screen; a rotation
off the tool axis is flagged SpindleOffToolAxis.
3. Shape the mission around the process, not the file list
A new project arrives with a mission already in it: the five Setup commands, two Program File
entries naming the placeholders NC/NC-File-Name-1.nc and NC/NC-File-Name-2.nc, and a
Post-Execution with every output off. Each placeholder raises the error RunNcFile--NotFound on
every play and is skipped, and an entry added through POST /api/Mission/list-command/entries without
an insert index lands after them — below the Post-Execution, which writes only what has played
above it, so nothing that entry plays reaches its outputs; with nothing played above it either, its
writers end on StepFile-Write--NoFile and ShotFile-Write--NoFile (for the outputs switched on).
Pass "InsertIndex" with the Post-Execution's current index to put a program above it — 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, or delete them with
DELETE /api/Mission/list-command/entries/{path} from the last index down — the paths after a
deleted entry renumber — and read the result back with GET /api/Mission/list-command/entries.
A flat list of program files runs correctly but reads badly and gives you one knob for the whole job. Group the programs into one List command per operation family, in the order the process actually runs:
Machining Motion Resolution
Collision Detection [Off]
Pause on Failure [Off]
Physics [On]
Record Meshed Geometry Read On First Or Write · Cache/0-stock.wct (STL stock only)
▸ 1 · Drilling Machining Resolution 0.5 mm
centre drill, drill
Record Meshed Geometry Read On First Or Write · Cache/1-drilling-done.wct
▸ 2 · Outside roughing Machining Resolution 0.5 mm
roughing 1 … 5
Record Meshed Geometry Read On First Or Write · Cache/2-outside-roughing-done.wct
▸ 3 · Outside finishing Machining Resolution 0.2 mm
finishing 1 … 7
Record Meshed Geometry Read On First Or Write · Cache/3-outside-finishing-done.wct
▸ 4 · Pocket roughing Machining Resolution 0.5 mm
…
▸ 8 · Deburring Machining Resolution 0.2 mm
Post-Execution geometry diff on
Why this shape:
- The records are part of the shape, not an optimisation. Where the stock is an STL, keep a Record Meshed Geometry entry above the first group: the stock is meshed before the first line of NC plays, at the workpiece's Initial Resolution, and for a finely tessellated STL that costs minutes on every play until it is cached. Close each group with an end-of-stage record named after the group, so a later run can resume from any of them. The cached stock does not follow a later change to the stock geometry or its Initial Resolution until its file is deleted. A Mission That Resumes is the layout in full.
- Grouping must not reorder. The stock evolves from one operation to the next, so the sequence is load-bearing. Group adjacent operations; never sort the list into tidy families if that moves an operation past another one.
- Each group carries its own machining resolution. Roughing is about force, not surface, so a coarse resolution is enough and runs quickly. Finishing, rib and deburring passes leave the final surface with small and ball cutters and deserve a finer one. Setting the resolution mid-mission only changes the value the act runner cuts with — the workpiece is not rebuilt, and the geometry container is adaptive — so alternating coarse and fine between groups is safe.
- A group is a switch. Disabling one List skips a whole phase, which is how you re-run just the finishing passes without deleting anything.
- Iterate on a short program; keep the full job beside it, switched off. A replay of the whole
job is the acceptance run, not the debugging loop. While datums, tools and offsets are still
moving, play a derived main program that runs one representative piece of the job — for four
identical parts held on one fixture plate, the block that machines the first part — in a List of
its own, and keep the full program in a second List with its checkbox cleared;
PUT /api/Mission/commands/{path}/enabledwith a body oftrueorfalseflips which one runs. Put the stage-0 record at root level above both groups so either variant starts from the same cached stock, and give each variant its own end-of-stage file. The first plays of a new project are the ones most likely to produce no steps or to cut in the wrong place, and a one-part program shows that in a fraction of the time. - Budget the fine groups. Machining resolution drives the cost of subtracting material, not the number of simulation steps — the step count follows the motion resolution and does not change. On one 33-operation job, refining the finishing groups from 0.5 mm to 0.2 mm left the step count identical and multiplied wall-clock time by about 2.8. Decide the finish resolution with that trade-off in view, and say what it costs in the project's README so nobody is surprised.
- Setup commands are granular. Machining resolution, motion resolution, collision detection, pause-on-failure and physics are separate commands, so each can sit exactly where it should take effect — global ones at the top, per-phase ones inside a group.
Over HTTP the mission is built one entry at a time. POST /api/Mission/list-command/entries adds at
the root and POST /api/Mission/list-command/entries/{path} inside a list, with a body of
{"CommandType": "<kind>", "IsEnabled": true} and an optional "InsertIndex"; the reply's path is
the new entry's address for every later call. A path is the chain of indices joined by dots — 6 is
the seventh root entry, 6.1 the second entry inside it. The values the routes expect:
| What | Values |
|---|---|
CommandType |
the command's class name without Command, lower-cased: list, ncfile, manualtooling, nccode, script, machiningresolution, machiningmotionresolution, collisiondetection, pauseonfailure, physics, recordmeshedgeom, postexecution, exportmeshedgeom, ncoptoption; GET /api/Mission/command-catalog lists the ones this service can add |
| List title | PUT /api/Mission/commands/{path}/listcommand with {"Title": "…"} |
| Program file | PUT /api/Mission/commands/{path}/ncfile with the project-relative path as a JSON string |
| Manual Tooling | PUT /api/Mission/commands/{path}/fields/toolId with the tool number |
| On/off Setup commands | PUT /api/Mission/commands/{path}/fields/enable with true or false |
| Machining resolution | PUT /api/Mission/commands/{path}/fields/machiningResolution_mm with a number |
| Motion resolution | PUT /api/Mission/commands/{path}/machiningmotionresolution/type with "feedpercycle", "feedpertooth" or "fixed" |
| Record Meshed Geometry | PUT /api/Mission/commands/{path}/recordmeshedgeom/main-action with "ReadOnFirstOrWrite", and …/rel-file with the cache file |
| Post-Execution | one PUT /api/Mission/commands/{path}/postexecution/… route per value: enable-write-step-files and enable-write-shot-files with true or false, step-file-template and shot-file-template with the template as a JSON string, relative to the project folder, shot-file-time-resolution-ms with a number, enable-geom-diff with true or false; GET /api/Mission/commands/{path} reads them back |
GET /api/Mission/commands/{path}/fields lists a command's field keys when the one you need is not in
the table. Post-Execution has none: its fields list is empty, and its values go through the
postexecution/… routes above.
Tip
A project that loads an ideal (target) geometry and never compares against it is unfinished. Turn on the geometry diff in the Post-Execution command so a full replay ends by measuring the machined stock against the finished part. A diff with no target to compare against does nothing and still reports that it was built — Geometry Validation says how to tell.
4. Fill the controller's own tables
The tool house describes the physical tool. The controller has its own record of the same tool, and the NC program reads the controller's copy. Both must exist.
For a Siemens-style controller that is two tables:
| Table | Holds | If empty |
|---|---|---|
| Tool name → tool number | the names a T="…" call uses |
the tool never mounts, and the program produces no machining steps at all |
| Tool edge offsets | per (tool, edge): length along the tool axis, radius |
every edge call falls back to the generic offset table and logs a warning |
Take the values from the tool house and assert they match before writing. When the warning disappears and the cut does not move at all, that is the proof the rows carry the right numbers — if the geometry shifts, one of the two tables was wrong.
Note
Not every controller parameter has a consumer. Some, such as the maximum spindle speed, are recorded machine data that nothing in the pipeline reads today. Filling them is good hygiene, but do not report it as fixing a defect, and remember that copying a guessed value (an assumed spindle, say) into a second place means both must move when the real datum arrives.
Work offsets over the API
The service exposes two work-offset tables, and they are not kept in step:
| Table | Routes | Read by |
|---|---|---|
The runner's own table — on Fanuc and Mazak the parameter table (#5221+, #7001+) |
GET /api/mech/soft-nc-runner/work-coordinates, PUT …/work-coordinates/{id} {X, Y, Z} |
the run, and the Equipment page's Work Coordinates leaf |
| The legacy ISO coordinate table | GET /api/Controller/iso-coordinate-table, PUT …/iso-coordinate-table/{index} {Index, X, Y, Z} |
the align call below, the Execution page's coordinate marker, and scripts that name it |
Write every row the program selects into the runner's table, then read it back after a save and a
reload. A build that fills only the legacy table plays the whole program around machine zero:
nothing is cut, and the only symptom is one Play-Touch--None warning, because a G54–G59 row
left at zero is read as an authoring convention and stays silent. When the align call is used, write
the same row into the legacy table as well. Its PUT edits only rows the table already holds
(G54–G59 and G59.1–G59.9) and answers 400 for any other id.
A row is the machine position at which the spindle's tool mount meets program zero, with every axis
at machine zero. Tool length is not part of it: the height offset selected by G43 H adds the
holder length plus the stick-out. An offset recorded with a tool touched off and no length
compensation in the program already contains that tool's length, and has to be converted before it
is entered. The program still adds no length, so the run needs it from elsewhere, or the tip runs a
whole tool length below every programmed Z: a G43 H in the program's copy (§1), or, once the part
is placed, the length put back into the Z of the runner's row, one row per tool, as the shop keeps
it.
Placing the part on a recorded offset over the API
The reproducing direction — keep the recorded offset, move the part onto it — is one call,
POST /api/Controller/iso-coordinate-table/{index}/align-workpiece-program-zero. It reads row
{index} from the legacy table and rewrites the transformer of the fixture branch from its
Geometry Anchor to its Table Mount as the static translation that puts the Program-Zero Anchor on
that offset with every axis at machine zero. The answer carries the translation and the transformer
before and after, as XML; posting the “before” XML to
…/iso-coordinate-table/revert-align-workpiece-program-zero undoes it. A mission script has the
same operation as AlignWorkpieceProgramZeroToIso("G54").
- It needs a fixture on the project and a machine chain with linear (and optional rotary) axes. Without either it answers 400 and changes nothing; a pure cutter-location device is not such a chain.
- The branch it writes is the one leading from the fixture's Geometry Anchor straight to its Table Mount — every fixture in the default shape has it. A fixture without that branch, one whose branch carries a dynamic transformer, or one where the way from the table to program zero does not pass through that branch alone answers 400 with the reason, and changes nothing.
- It replaces whatever that branch held, rotation included, with a pure translation.
- It reads the workpiece geometry frame and the workpiece branches to its Fixture Mount and its Program-Zero Anchor as they stand, once. Set those first, and align again after changing any of them.
Prove the placement with a probe rather than trusting the call. Before the first play or after a
Reset — until then a played session keeps the placement it started with — press P0 on a row the
program never uses: POST /api/Controller/iso-coordinate-table/G59/set-to-program-zero answers with
the X, Y and Z it wrote, which must equal the recorded row to rounding. Return the probe row to
machine zero with …/G59/set-to-machine-zero before saving. The runner's
POST /api/mech/soft-nc-runner/work-coordinates/{id}/set-to-program-zero answers only success;
read its value with GET /api/mech/soft-nc-runner/work-coordinates.
Once aligned, an error in the recorded offset no longer changes what is cut, but it moves the whole setup in machine coordinates, where stroke checks and machine collisions see it. For several parts on one table, one offset each, P0 fills only the row of the part carrying the program-zero anchor — see Several parts, several offsets.
5. The finish line is a clean replay, not a filled form
Replay the whole mission and inventory the messages by id. Every warning is an item on the to-do list. Resist writing one up as an acceptable fallback: a fallback firing means the project never supplied the data the fallback exists to cover.
Read messages at a low severity so nothing hides. The severities are
Message, Success, Progress, Warning, Error — note there is no Info, and an unrecognised
name is rejected in the response body rather than by an HTTP error, which reads exactly like
“no messages”.
Stop when only declared informational messages remain — the ones that state a known, deliberate limitation — plus the expected messages of constructs left as delivered (§1), each listed in the README with its id and the reason it does not change the result. Typical survivors:
- one per NC file, reporting the line count;
- machine M-codes that the runner recognises and consumes but does not model (OEM auxiliary functions such as coolant valves), which are announced rather than silently ignored.
Anything else is work.
A clean message list is not yet a pass. A run started without a Reset plays on top of the one
before it, whose touching steps keep Play-Touch--None from firing; a re-run on stock already cut
leaves a Tool-Touch--None for each tool that cuts nothing instead, which reads as a tool-length or
offset fault rather than as the skipped Reset. Two other failures leave one warning each: a run that
never mounted a tool produces no steps at all and still ends Finished, with Step-Tool--NotEquipped
in the step list rather than an end-of-play warning (a Manual Tooling entry above the program
mounts the tool); a Post-Execution with no program above it writes nothing and reports
StepFile-Write--NoFile / ShotFile-Write--NoFile. All three are
covered, with the checks that catch them, in Replay Acceptance over the HTTP API.
6. Two numeric cross-checks — and what they do not prove
Messages catch what the runner noticed. These two catch datum errors that produce no message at all.
Depth of cut against the programmed layer depth. On a roughing pass whose step-down you can read from the NC, the peak cutting depth must equal it. This pins the Z datum: if program zero is wrong, the tool cuts a different amount and the peak does not land on the programmed number. A finishing program has no layer depth to read; check its datum against the surface it leaves instead (Building the Workpiece from a Reference Mesh).
Cycle time against the post-processor's own estimate. Many post-processors write a per-operation machining time into the program as a comment. Sum them and compare with the simulated time.
Warning
Scope the cycle-time check honestly. It confirms that feed rates and path length are consumed correctly. It is not a comparison against the real machine, because both sides ignore the same things: there is no acceleration model (time is path length over feed rate, with no ramps — which costs the most on finishing passes made of thousands of short segments), tool-change time defaults to zero, and the home and tool-change positions default to machine zero (on a tool change Z goes to 0 while X and Y stay) until the controller's Home / G28 Reference and Tool-Change Position tables are filled from machine data.
For the same reason, a customer's “total machining time” running well above the sum of the per-operation comments is usually not a contradiction — the comments are cutting time, the total is wall clock including tool changes and inter-operation moves. Two different quantities.
7. Record every assumption
Real deliverables are incomplete. Build with sensible values, and write each one down in a
README.md beside the .hincproj — what was assumed, what it was inferred from, and what changes
when the real value arrives. Recurring items: work offsets, blank size and material, tool helix
and rake angle, coating, stick-out, holder dimensions, the smart holder's sensor height, the
spindle's power–torque curve, fixture geometry, the rewrites made to program copies, and the
cycle-time inputs above.
Cross-reference: Project Data Checklist is the list to hand the customer before building; this section is what you hand back after.