Work Coordinates
Work Coordinates is the Controller branch's G54 ledger: one table of coordinate ids against an X / Y
/ Z machine offset, edited through whichever offset provider the active runner resolves. It lives on
the General Setup page at /general-setup under the Control-Tree id
equipment/controller/program-data/work-coordinates, reached as
?tree=equipment/controller/program-data/work-coordinates, and no snapshot flag gates it — every
runner grows it. The face is the same on all five brands; what sits behind it is a different object
on each, and on two of them that object is also the subject of a node of its own.
Important
Anatomy makes no claim that any id named here is stable across versions. The migration hop that
keeps an older ?tree= value resolving is described in
Tree Ids and Routes.
The Discriminator
The panel does not probe the runner. It reads one field of the shared runner snapshot,
workCoordinateKind, and uses it for exactly one thing: the grey caption above the table. The field
is a type switch over the first IIsoCoordinateConfig in the runner's
proxy-resolved dependency list, and it has six arms.
| Arm | Provider matched | Brands that reach it | Caption the panel shows |
|---|---|---|---|
fanuc |
FanucParameterTable | Fanuc, Mazak | "Stored in the Fanuc parameter table (#5221+ / #7001+)." |
syntec |
SyntecParameterTable | Syntec | "Stored in the Syntec parameter table (Pr5221+ / Pr7001+)." |
siemens |
SiemensFrameTable | Siemens | "Stored as Siemens settable frames ($P_UIFR; G500 cancels and is always zero)." |
heidenhain |
HeidenhainDatumTable | Heidenhain | "G54–G59 map onto Heidenhain datum preset rows 1–6." |
generic |
any other implementer, i.e. IsoCoordinateTable | none | "Brand-neutral work-coordinate table." |
none |
nothing resolved | none | "Brand-neutral work-coordinate table." |
Two arms answer for no shipped preset, and they answer alike because the caption's switch falls through to one default for both.
genericneeds the brand-neutral table to be the FIRST provider the runner resolves. The Fanuc, Mazak and Syntec presets do carry that table — seeded with the nineG59.xids — but always behind their brand table, so the arm is reachable only from a runner composed by hand or deserialized from a file that put the brand-neutral table first. Which brand carries which dependency is Brand Matrix.nonehas two producers, and a different guard stops each of them. The snapshot's early return leaves the field's default in place when no runner resolves at all — the same string the client's empty snapshot carries — and the shared no-runner guard then replaces the whole body. The type switch's own null arm answersnonefor a runner that does resolve but carries no offset provider; that state reports a runner, so what replaces the body is the absent-table guard instead, because the lookup that answerednoneis the one the panel's own read reports absent. Neither path reaches the caption.
The brand marker plays no part here. workCoordinateKind is derived from the resolved object's
type, while the badge on the branch root reads a free-form string, so a marker edited to say
something else changes the badge and leaves this caption exactly where it was.
The Row Inventory
Every row is one id from the provider's own id enumeration, and each provider enumerates a different set. That is the whole reason the row counts differ: the endpoint asks the resolved provider what ids it has and fills each row from its offset accessor, substituting a zero triad for a null answer.
| Brand | Provider | Ids enumerated | Always visible | Hidden while all-zero |
|---|---|---|---|---|
| Fanuc, Mazak | Fanuc parameter table, then the extended table | G54–G59, G54.1P1–G54.1P48; G59.1–G59.9 | the six G5x rows and the nine G59.x rows |
the 48 G54.1P rows |
| Syntec | Syntec parameter table, then the extended table | G54–G59, G54.1P1–G54.1P48; G59.1–G59.9 | the six G5x rows and the nine G59.x rows |
the 48 G54.1P rows |
| Siemens | Siemens frame table | G54–G57, G505–G599 | the four G5x rows | the 95 G5xx rows |
| Heidenhain | Heidenhain datum table | G54–G59 | all six | none |
| — | brand-neutral table (standalone) | G54–G59, G59.1–G59.9 | all fifteen | none |
The extended table on the three ISO presets is the brand-neutral table seeded with the nine
G59.x ids only; a standalone brand-neutral table (a runner built without a brand table) seeds all
fifteen.
Three consequences of that column are worth reading twice.
- Siemens seeds no G58 and no G59 row. The frame table's constructor seeds G54, G55, G56 and G57
plus the extended G505–G599 series, and nothing else, and no panel on the branch can add an id to
the set. A
G58in a Siemens program resolves through the same ISO path as on any other brand, finds no frame, and falls back to a zero offset. - Fanuc and Syntec enumerate an id only while at least one of its three parameter addresses is
present. IsoCoordinateAddressMap maps G54–G59 onto
#5221with a stride of 20 and G54.1 P1–P48 onto#7001with the same stride, three consecutive addresses per entry, and both brand tables seed all 162 of them with zero at construction. So all 54 rows exist from the first read; the enumeration rule bites only a table whose addresses have been removed. Native Parameters is the surface that removes one — its row delete drops a system-parameter address outright, and dropping all three of an entry's addresses removes that row from this leaf. The same form is where those addresses are visible under their own numbers, each annotated with the component it holds. - Heidenhain shows six ids for a twenty-row table. The datum table seeds preset rows 1–20 and shift rows 1–20, and its ISO face aliases only preset rows 1–6 onto G54–G59. Rows 7–20, and every shift row, have no id here at all.
G500 is a row on no brand. The frame table treats it as the cancel frame: reads answer a zero
offset, a write through the coordinate accessor is dropped, and it is deliberately not stored in the
frame dictionary, so it never reaches the id enumeration — which is why the Siemens caption names a
code the table below it never shows.
Two Nodes, One Table
Warning
On Siemens and on Heidenhain this leaf is a second face on an object that already has a node of its own. The Siemens frame table is edited here and under Frames (Siemens); the Heidenhain datum preset table is edited here and under Datum Presets (Q339). These are not two copies kept in step — they are one instance, and a write through either node lands in the same cell.
The aliasing is exact, and its two halves are addressed differently.
- Siemens. This leaf reads and writes through the ISO interface, so the payload is keyed by the
G-code id. Frames reads and writes the frame dictionary directly,
keyed by the same string. Both panels render the same id set with the same values, and a
G54edit made on either is the same assignment. The two routes part on one id only: this leaf's write goes through the coordinate accessor and drops aG500, where the frame route would assign it into the dictionary — an id neither panel ever sends. Nor are the three value columns the whole of what the frame table holds: they are the X / Y / Z translation, while the translation components for other axis letters live in a second dictionary that only the$P_UIFRbridge writes and neither panel exposes. What does differ between the two panels is furniture — this leaf carries the Actions column with P0 and M0, and Frames has no counterpart for it. - Heidenhain. This leaf's
G54…G59rows are preset rows 1–6. Datum Tables shows the same six cells under the Q339 column, alongside rows 7–20 and the whole datum-shift table, which this leaf cannot reach. So the aliasing is one-directional in coverage: everything on this leaf is on that one, and most of that one is not here.
Nothing in the branch reconciles the two views while both are in scope, and nothing needs to: the editor row mounts one panel at a time and each panel fetches once on mount, so moving the selection from one node to the other is itself the refresh. What that costs, and what a mounted panel therefore never sees, is Editing Contract.
Which Rows Show
A row is always visible when its id reads G5, one digit in 4–9, and optionally a dot and one more
digit. Every other row is hidden unless one of its three values is non-zero, or the Show all
toggle is on. That rule is the panel's own regular expression, evaluated in the browser over the rows
the read returned; nothing about it reaches the server.
The toggle itself is conditional. It renders only while at least one row fails the always-visible
test, so it is present on Fanuc, Mazak, Syntec and Siemens and absent on Heidenhain — and absent on
the brand-neutral table too, whose fifteen ids all pass. The Frames leaf carries the same toggle
unconditionally and applies a narrower always-visible test of its own, G54 through G57; on the
Siemens table the two tests keep the same four rows visible, so only the toggle's presence differs.
Editing a Row
Every value cell is the shared numeric field, so it commits on blur or on Enter and never per keystroke — its parsing, its bounds behaviour and the double commit that follows Enter are Numeric Input. No cell here passes a minimum or a maximum, and the id column is plain bold text that cannot be edited.
The write is the whole row. A commit assigns the new number into the local row, then sends that
row's x, y and z as they now stand to the row's id. A failure restores the one component the
handler captured and raises the branch's standard toast; the request that failed carried all three. A
cleared cell parses to null, and a cell holding NaN or an infinity parses to a non-finite number;
the handler returns before the request in both cases, so neither empties a stored offset. The cell
keeps showing what the field made of the text — blank for a cleared cell and for NaN, the literal
Infinity or -Infinity for an infinity — until the panel is remounted.
Where that row lands differs by provider, and this is the point at which the uniform face ends.
- Fanuc, Mazak and Syntec write three consecutive parameter addresses, all three unconditionally.
- Siemens replaces the frame entry for that id.
- Heidenhain replaces the preset row the id aliases onto.
The Fanuc-family and Heidenhain writes silently drop an id they do not map: the Fanuc-family write
resolves the address first and does nothing without one, and the Heidenhain write matches the id
against its two recognised forms. The Siemens frame table and the brand-neutral table do the
opposite — an id they have never held is stored as a new row, the frame table refusing only G500.
Neither behaviour is reachable from this leaf, because the panel only ever sends an id the same
provider enumerated.
P0 and M0
Two flat buttons sit in the Actions column of every row and write the whole triad in one call.
- M0 sets the row to machine zero.
- P0 sets it to the machine coordinate at which the workpiece's program-zero anchor sits with every dynamic axis of the equipment assembly — not only the machining chain's — stepped to zero, falling back to the machining chain's table buckle when the workpiece declares no program-zero anchor.
Both re-read the whole table on success rather than patching the local row, so a value the provider stored differently from what was sent is picked up at once. Neither is confirmed. P0 carries a failure of its own that no other write in the branch produces: with no chain resolving a tool buckle the position is null, and the endpoint answers unsuccessfully with “Could not get the machine position at program zero” before it ever reaches the offset provider.
The Canvas Marker
Clicking a row also chooses which coordinate the General Setup canvas draws its triad on. That is the one write in this branch addressed to another surface, and it behaves differently from the rest in four ways worth naming.
- Where it is stored. The id goes to the equipment-display surface, which assigns it on the shared user configuration and schedules a debounced save of that file. It is device state, not project state: it is not written into the project, it outlives the project that set it, and a failure of the file write itself is logged rather than reported.
- What reads it. The canvas marker resolves the first IIsoCoordinateConfig on the active runner — the same object this panel edits — so an offset edited here moves the marker on the next frame. A marked id the provider does not answer draws nothing at all, which is the state after a brand switch leaves behind an id the new provider has never heard of. The marker is also suppressed while its Scene flag is off, and while no machining chain resolves an anchor.
- How it fails. The handler follows the same optimistic shape as a cell commit — assign, await, restore and toast on failure — but it is the branch's one call through the plain-JSON helper rather than the envelope helper, so only a non-2xx status throws there. On mount the panel seeds its highlight from the same surface and swallows any failure, so an unhighlighted table is not evidence that nothing is marked.
- What triggers it. The click handler is bound on the whole row and nothing inside the row stops propagation, so editing a cell or pressing P0 or M0 marks that row as well as doing its own work. A click on the row already marked returns immediately, which is what keeps repeated cell edits on the marked row from re-writing the user configuration on every pass.
The highlight is a tinted row background, and the row carries the native tooltip “Click to mark this coordinate on the General Setup canvas”.
What a Brand Switch Carries
The brand switch is the one operation in the branch that treats work coordinates specially. Its panel carries a checkbox, Carry work-coordinate XYZ (G54…) into the new brand's table, ticked by default, and the flag reaches the endpoint with the brand.
With it set, the offsets are read from the outgoing provider before the preset is swapped in, and written into the incoming one afterwards — but only for the ids the incoming provider already enumerates, and only when the two providers are different instances. Everything else is dropped. So the carry is lossy in a shape that follows directly from the inventory above: Fanuc to Siemens keeps G54 through G57 and loses G58, G59 and all 48 extended entries; Siemens to Fanuc keeps the same four and loses the whole G505 series; a switch to Heidenhain keeps at most six.
The instance guard matters on the pair that shares a table. Mazak and Fanuc proxy the same parameter table, so a switch between them resolves the same instance on both sides and the carry is skipped as redundant — the values are already there. What a switch keeps, resets and destroys elsewhere is Brand Switch.
What the Rows Feed
A work-coordinate word in a program is resolved into a translation composed onto the block's program-to-machine transform. Two details of that path do not match what this panel shows.
The run-time lookup walks every provider; so does the panel. The resolver iterates all offset
providers in the effective list and takes the first non-null answer, which is what lets a brand
table cover its hardware-mapped ids while a second provider covers ids the brand table does not map
— the shape the Fanuc, Mazak and Syntec presets ship in, with the extended G59.1–G59.9 on a
brand-neutral table behind the brand table. The panel lists every provider's ids in the same order
(the first provider that holds an id wins) and writes an edit to the provider that enumerates the
id; the snapshot's kind field still names the first provider only, which is why the caption reads
“Fanuc parameter table” above rows the second table holds.
The 48 extended rows are reached by G54.1 Pn — and by G54 Pn. The ISO coordinate syntax
reads G54–G59 and G59.1–G59.9 from the block's parsed flags, and the additional work coordinate
systems from the G54.1 P capture the parsing bundle writes as a sub-object; both spellings Fanuc's
manual gives that chapter (“G54.1 or G54”) land in that capture, with or without a space before the
P word, so G54 P48 and G54P48 select row 48 exactly as G54.1 P48 does. A G54 with no P word
in its scope stays the plain G54 flag. The row's id is the un-padded G54.1P48, the same key the
Fanuc-family table maps to #7001+, so on Fanuc, Mazak and Syntec the extended rows this panel edits
are the rows the program selects. A row that is selected but was never entered — the brand tables
seed every extended row with zero, as a fresh-battery controller reads them — reports
Coord-WorkOffset--AdditionalZero on the selecting block; the program then runs on the machine
origin, which is almost never what a pallet or fixture offset meant. A zero G54–G59 stays silent: it
is a common authoring convention. The Fanuc-family table is also the macro variable lookup, so on
Fanuc and Mazak those rows read back from a macro program as #7001–#7999, alongside G54–G59's
own #5221–#5328; the Syntec table implements no variable lookup, so on Syntec the rows are
reached by the coordinate words only.
The extended G59.1–G59.9 ids live on a second table behind the brand table. The Fanuc-family
tables map G54–G59 and the 48 additional rows and nothing else, so the Fanuc, Mazak and Syntec
presets mount a brand-neutral coordinate table holding only G59.1–G59.9 right behind their
brand table. The two id sets are disjoint, the run-time lookup finds each id on the one provider
that holds it, and this leaf shows the nine rows among the others and writes each edit to the
provider that carries the id. Those rows are seeded with zero and, like G54–G59 and unlike the
48 additional rows, a zero one stays silent: leaving a row of the standard series at zero is a
common authoring convention. A project that kept a G59.x value in its
legacy coordinate table carries it into this second table when the legacy table migrates (each row
lands on the provider that carries its id). A controller saved before the second table existed
gains it on load; one that lost it resolves no offset for a G59.x at all and reports
Coord-WorkOffset--NoTableEntry instead.
Which row an untagged program starts on is the brand preset's static initializer, and the three answers differ.
| Preset | Initial coordinate id | Effect |
|---|---|---|
| Fanuc, Mazak, Syntec | G54 |
the G54 row is active from the first block |
| Siemens | G500 |
no frame is active; the offset is zero until a frame word appears |
| Heidenhain | none | no coordinate section is seeded; the datum cycles set one |
On Heidenhain the program's own route into the same table is the datum cycle rather than a G word: a preset cycle reads the preset row its number names and writes a synthetic coordinate id that the table resolves back, which is how rows 7–20 are consumed at run time despite having no row on this leaf. A word or a cycle that resolves no offset composes a zero translation rather than failing.
Layout
- General Setup Control Tree — the left dock of
/general-setup- Controller Node Row
- Program Data Group Row —
equipment/controller/program-data- Work Coordinates (G54…) Node Row —
equipment/controller/program-data/work-coordinates
- Work Coordinates (G54…) Node Row —
- Program Data Group Row —
- Controller Node Row
- Editor Row — the panel of whichever node is selected
- Work Coordinates (G54…) Panel
- No-Runner Body — "No NC runner — load a project first."; the shared empty state, shown instead of everything below while the snapshot reports no runner
- Absent-Table Body — "No work-coordinate table on the active runner."; shown instead of everything below while the panel's own read reports no provider
- Header Strip
- Storage Caption — the one-line note for the snapshot's coordinate kind, left-aligned and grey
- Show all Toggle — right-aligned, dense; rendered only while a row exists that the always-visible test rejects
- Coordinate Table — dense, flat, bordered; no sort, no pagination, no column menu
- Header Row — Id, X (mm), Y (mm), Z (mm), Actions. The three axis headers are literals in the template; Id and Actions are the shared translated labels
- Coordinate Row, one per visible id — the whole row is clickable and carries the tooltip
“Click to mark this coordinate on the General Setup canvas”; the marked row is tinted
- Id Label — bold plain text, never editable
- Value Numeric Field, one per axis column — no minimum, no maximum, no unit suffix
- P0 Button — flat, dense, tooltip “Set to the machine position at program zero”
- M0 Button — flat, dense, tooltip “Set to machine zero”
- Toast — negative, three seconds, the panel's context followed by the server's own message
- Work Coordinates (G54…) Panel
The panel carries no heading, no save button, no unsaved marker and no dialog, and it reports no structural change, so no edit made on this leaf rebuilds the branch.
Source Code Path
See HiNC App Anatomy for git repository links.
Web Application
HiNC-2025-webservice (Quasar CLI SPA):
wwwroot-src/src/components/controlTree/SoftNcWorkCoordinatesPanel.vue— the panel: the storage caption's five-way switch, the always-visible test and the conditional Show all toggle, the whole-triad cell commit with its non-finite guard, the P0 / M0 actions and their re-read, and the row click that writes the canvas marker.wwwroot-src/src/components/controlTree/softNcItemTypes.ts— the branch builder: this leaf's node id and label key, and its position as the first ungated member of the program-data stem.wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue— the shared no-runner line the panel opens with.wwwroot-src/src/components/widgets/NumericInput.vue— the value cell: commit on blur or Enter, the empty-text-to-null parse, and the infinity and NaN literals the panel's handler then rejects.wwwroot-src/src/api/softNcRunner.ts— the snapshot's coordinate-kind field and its parser, the work-coordinate reader and its row shape, the whole-triad setter, the two zeroing actions, and the brand switch's carry flag.wwwroot-src/src/api/equipmentSetup.ts— the canvas marker id: the getter the panel seeds its highlight from and the setter the row click writes, on the equipment-display surface rather than this branch's own.wwwroot-src/src/api/http.ts— the two helpers this panel mixes: the envelope helper every table call uses, and the plain-JSON helper the marker call uses.wwwroot-src/src/composables/useSoftNcRunner.ts— the shared snapshot the coordinate kind and the no-runner guard are read from.wwwroot-src/src/components/controlTree/SoftNcFramesPanel.vue— the other node on the Siemens frame table: the same rows through the frame-keyed reader, with an unconditional toggle and a narrower always-visible test.wwwroot-src/src/components/controlTree/SoftNcDatumTablePanel.vue— the other node on the Heidenhain datum table: the preset rows this leaf aliases, plus the rows and the shift table it does not reach.wwwroot-src/src/components/controlTree/SoftNcBrandPanel.vue— the carry checkbox and its default.wwwroot-src/src/i18n/en/softNc.ts— the node label, the two empty bodies, the five storage captions, the row and button tooltips, and the five error contexts.wwwroot-src/src/i18n/en/common.ts— the shared Id and Actions column headers and the Show all label.Mech/SoftNcRunnerController.cs— the REST surface: the coordinate-kind type switch in the snapshot builder, the reader that enumerates the provider's ids and zero-fills a null offset, the whole-triad writer, the two zeroing actions with the program-zero lookup's own failure message, and the brand switch's capture-swap-carry-sweep with its accepted-id filter and its instance guard.Mech/EquipmentSetupDisplayController.cs— the marker id's reader and writer, the user-config slot it lands in, and the debounced save that follows.Disp/EquipmentSetupDisplayee.cs— the marker's binding to the first offset provider on the active runner, and the guards that drop it with no chain or with its Scene flag off.Disp/EquipmentSetupDisplayeeConfig.cs— the stored marker id, itsG54default and its serialization.Environments/UserService.cs— the loose save behind the marker write, and the failure it logs rather than returns.
HiAPI Engine
HiMech/NcParsers/Dependencys/IIsoCoordinateConfig.cs— the offset-provider contract: the id-keyed get and set, and the id enumeration every row of this leaf comes from.HiMech/NcParsers/Dependencys/IsoCoordinateAddressMap.cs— the Fanuc-family address scheme shared by the Fanuc and Syntec tables: the two base addresses and the stride, the read that treats an entry with no address as absent, the write that lays down all three, the enumeration this leaf's rows follow, the per-address description the native form annotates with, and the seeding of every entry with zero.HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTable.cs— the Fanuc and Mazak provider: its offset accessors over the address map, its default table, and the variable lookup that also exposes the same addresses to a macro program.HiMech/NcParsers/Dependencys/Syntec/SyntecParameterTable.cs— the Syntec provider: the same address scheme without the variable lookup.HiMech/NcParsers/Dependencys/Siemens/SiemensFrameTable.cs— the Siemens provider: the seeded G54–G57 and G505–G599 ids, the cancel-frame handling that keeps one code out of the row set, and the per-axis translation dictionary neither panel exposes.HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainDatumTable.cs— the Heidenhain provider: the twenty preset and twenty shift rows, the G54–G59 alias onto preset rows 1–6, and the synthetic ids the datum cycles resolve through.HiMech/NcParsers/Dependencys/Generic/IsoCoordinateTable.cs— the brand-neutral table behind thegenericarm, and the fifteen ids it seeds.HiMech/NcParsers/Dependencys/Generic/IsoCoordinateTableProxy.cs— its get-or-create proxy, the other shape a runner can carry it in.HiMech/NcParsers/Dependencys/Siemens/SiemensFrameTableProxy.cs,HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainDatumTableProxy.cs— the seedless proxies those two brands carry: each installs a fresh table into a project holding none, which is why those two brands open on their constructors' defaults.HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTableProxy.cs— the seeded proxy the Fanuc and Mazak presets share, and the reason those two resolve one instance.HiMech/NcParsers/SoftNcRunner.cs— the five brand presets and the offset provider each carries, the proxy resolution every read goes through, and the legacy import that replays an older coordinate table into whichever provider resolves.HiMech/NcParsers/LogicSyntaxs/CoordinateOffsetUtil.cs— the run-time resolver that walks every provider and takes the first non-null answer, and the translation it composes.HiMech/NcParsers/LogicSyntaxs/IsoCoordinateOffsetSyntax.cs— the G-word path: the flag vocabulary it consumes, the modal lookback that keeps a coordinate alive across blocks, and the zero fallback when nothing answers.HiMech/NcParsers/LogicSyntaxs/Heidenhain/HeidenhainCoordinateOffsetSyntax.cs— the datum-cycle path into the same table, and the synthetic ids it writes.HiMech/NcParsers/Keywords/Generic/IsoKeywords.cs— the recognised G-word series.HiMech/NcParsers/Keywords/Siemens/SiemensKeywords.cs— the extended frame series and the cancel frame.HiMech/NcParsers/Initializers/StaticInitializer.cs— the three brand defaults for the coordinate active at the first block.HiMech/Machining/MachiningEquipmentUtils/MachiningEquipmentUtil.cs— the program-zero machine position P0 writes: the reflection that zeroes every dynamic branch of the equipment assembly, and the anchor fallback when the workpiece declares none.
See Also
- Program Data Plane — the plane this leaf opens, and the per-case tables beside it that a brand switch keeps or sweeps
- Frames — the other node on the Siemens frame table, where the same rows are edited as settable frames rather than as G54 offsets
- Datum Tables — the other node on the Heidenhain datum table, carrying the preset rows this leaf aliases plus the fourteen rows and the shift table it cannot reach