Table of Contents

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.

  • generic needs 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 nine G59.x ids — 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.
  • none has 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 answers none for 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 answered none is 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 G58 in 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 #5221 with a stride of 20 and G54.1 P1–P48 onto #7001 with 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 G54 edit 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 a G500, 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_UIFR bridge 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 G54G59 rows 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.1G59.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.1G59.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 G54G59 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
  • 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

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, its G54 default 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 the generic arm, 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