Table of Contents

Frames

Frames is the Controller branch's Sinumerik settable-frame ledger: one row per $P_UIFR entry, each carrying the X / Y / Z translation HiNC stores for that frame. It lives on the General Setup page at /general-setup under the Control-Tree id equipment/controller/program-data/frames, reached as ?tree=equipment/controller/program-data/frames, and it is grown only while the active runner resolves a Siemens frame table — among the shipped brand presets, Siemens alone. That same table is the offset provider behind the ungated Work Coordinates (G54…) leaf on this brand, so the two nodes edit one object.

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.

What a Frame Is Here

On a Sinumerik, $P_UIFR is an array of frames and each entry carries a translation, a rotation, a scale and a mirror. HiNC consumes the translation and nothing else, so SiemensFrameTable stores one Vec3d per frame id in Frames, with a second, axis-letter-keyed dictionary beside it for translation components on axes other than X, Y and Z.

The table is a dependency in its own right rather than a section of the machine data, because $P_UIFR is not machine data on the control either: SiemensMachineDataTable holds the MD-numbered parameters — axis type, reference position, stroke limits — and carries no frame. That split is also why the frame table is the brand's ISO offset provider while the machine-data table is not.

The array index is not the row key. Rows are keyed by the G-code that selects the frame, and the bridge that serves $P_UIFR accesses maps one onto the other:

$P_UIFR index Frame id Row on this leaf
0 G500 none — the cancel frame is never stored
14 G54G57 the four base rows
599 G505G599 the extended tail

An index outside 099 maps to no id at all, and the bridge drops the access rather than inventing a frame for it.

Settable, and computed

Four parts of the Sinumerik frame chain show up in this branch's behaviour, and only one of them is a row here.

  • The settable frames are the table. Ninety-nine ids are allocated by the constructor — G54G57 plus every id in ExtendedCoordinateSeries, which is G505 through G599 — each seeded to zero, matching a control where every $P_UIFR[n] is allocated with an initial value of zero. Every one is editable on this leaf.
  • G500 is computed, not stored. The accessor answers a zero offset for it and drops a write, and the constructor deliberately keeps it out of the dictionary, so it is a row on neither this leaf nor Work Coordinates. The description above the table says as much: "Settable frames ($P_UIFR, translation only); G500 cancels and stores nothing."
  • The programmable frameTRANS / ATRANS / ROT / AROT and their solid-angle forms, together with the CYCLE800 tilt that shares its slot — is computed per block from the program and has no table, no node and no stored value anywhere in this branch.
  • A frame's rotation and fine-offset components reach neither this table nor the simulation. Only the TR component of a $P_UIFR access is bridged into the frame entry; a write to any other component is recorded on the block and reported as recognised-but-not-simulated by the Siemens system-variable catch-all.

How the Chain Composes

A frame word in a program selects one id. The Siemens coordinate syntax resolves that id's offset through GetEffectiveNcDependencyList(API), taking the first provider that answers, and writes the result twice: as the block's coordinate-offset section, and as one named entry in the block's program-to-machine transform chain. Entries in that chain are multiplied in the order they were written, so slot order is the whole of the composition rule.

Two placements in the Siemens syntax list decide what a settable frame ends up doing.

  • The programmable-frame entry is written first, deliberately. The tilt and programmable-frame syntaxes run ahead of the coordinate-offset syntaxes so their shared entry lands ahead of the settable frame's. That is what keeps a ROT turning the program coordinates inside the frame rather than turning the frame's own offset with them. Tool-height compensation is written between the two, and the kinematic pivot transform is written last so every frame-space operation is accumulated before the final inverse kinematics.
  • Both coordinate syntaxes are registered, and they share one entry. The Siemens preset carries the ISO coordinate syntax and the Siemens one side by side; each writes the chain entry under the same name, and a repeated name is replaced in place rather than appended. So the G54G59 vocabulary the ISO syntax recognises and the G500 and G505G599 vocabulary the Siemens one adds resolve into a single slot. The Siemens syntax leaves the coordinate flag in place instead of consuming it, and falls back to the block's existing coordinate section and then to the previous block's, which is what carries a frame selection modally.

Three codes bypass the composed transform for one block rather than changing it: G53, G153 and SUPA all reduce to the same one-shot machine-coordinate move, and the values on this leaf take no part in such a block.

The word vocabulary is also wider than the table. The ISO syntax's own series adds G58, G59 and G59.1G59.9, none of which the frame table allocates; such a word resolves to no offset from any provider and composes a zero translation.

The Rows and the Columns

The panel is a four-column table and nothing else — no toolbar, no add footer, no action column, no dialog. The Frame header is translated; X (mm), Y (mm) and Z (mm) are literals in the template and stay English under any locale. The id cell is bold plain text and cannot be edited.

The row set is fixed. The branch exposes exactly two frame routes, a whole-table read and a per-id write; no third route adds, deletes or resets a row. The write itself is an unguarded assignment by key rather than an update of an existing row, so an id the table has never held would be seated as a new one — but no panel has a way to send such an id. A row exists because the table allocated it, and the ninety-nine allocated ids are the same ninety-nine whatever a project has done to their values.

Nothing in the path sorts. The reader projects the dictionary as it enumerates it and applies no ordering, unlike the datum, retained-variable, R-parameter and tool-offset readers beside it, and the panel adds none of its own. What that yields depends on how the table was built. A table the proxy has just created lists G54G57 first and then the extended tail, in construction order. A table read back from a project file lists the ids in the order the file names them — and the file is written sorted by id as text, not as a number. Plain string order puts G505 ahead of G54, because the third character decides that pair, and G54 ahead of G540, because the shorter of two strings that share a prefix comes first. So a saved and reopened table does not push the four base frames to either end: it interleaves them into the extended tail, as G505G539, G54, G540G549, G55, G550G559, G56, G560G569, G57, then G570G599. Work Coordinates enumerates the same dictionary, so both leaves reorder together.

Show all

A Show all toggle sits at the right of the header strip. With it off, a row is listed only when its id matches G54 through G57, or when at least one of its three values is non-zero; with it on, every allocated id is listed. The test is the panel's own regular expression, evaluated in the browser over the rows the read returned — nothing about it reaches the server, and the toggle is a plain local flag that returns to off whenever the panel is remounted.

Two details of that rule are worth reading against the sibling leaf. The toggle here is unconditional, so it renders even on a table with no extended rows at all. And the always-visible test here is narrower than the work-coordinate one, which admits G54 through G59 and any of them followed by a dot and one further digit. On the seeded table the two tests keep the same four rows visible, because the frame table allocates none of the ids where they differ.

Two Nodes, One Table

Warning

On Siemens this leaf and Work Coordinates (G54…) are two faces of one instance, not two tables kept in step. The work-coordinate leaf resolves the first IIsoCoordinateConfig on the active runner; on this brand that is the frame table, because it is the only dependency the Siemens preset carries that implements that interface — the machine-data table included. A G54 edit made on either node is the same assignment into the same cell.

This is the reciprocal half of the warning Work Coordinates carries. What the two leaves share, exactly:

  • The row set. The work-coordinate reader enumerates the provider's own ids, which for this provider is the frame dictionary's key set. The two leaves therefore list the same ids in the same order, and gain or lose none independently.
  • The X, Y and Z cells. Both leaves write the whole triad for one id, and both land in the same dictionary entry. Neither mutates the vector already sitting there: each write installs a fresh Vec3d over it, so a reference taken before the write still reads the old values. A value typed on one leaf is what the other shows at its next mount.
  • The absence of G500. Neither leaf can show it, for the same reason: it is not in the dictionary the ids are enumerated from.

What is not shared, and what a reader must not infer from the shared face:

  • The per-axis translation components. A settable frame's translation on an axis letter other than X, Y or Z lives in the table's second dictionary, written and read only by the $P_UIFR bridge. Neither leaf returns it, and neither leaf disturbs it: both write a fresh Vec3d into the frame entry and leave the axis dictionary untouched, so a C-axis frame component set by a program survives every edit made from either panel.
  • The row actions. The P0 and M0 buttons and the canvas marker belong to Work Coordinates. This leaf has no action column at all — but P0 and M0 write through the same provider, so pressing one there changes what this leaf shows.
  • The presence gate. This leaf gates on the concrete frame table; the other gates on the ISO interface. On Siemens the two answers are one object and the leaves appear together; the empty lines they would show differ all the same — "No Siemens frame table on the active runner." here, "No work-coordinate table on the active runner." there.
  • The write path. The work-coordinate route writes through the provider's coordinate accessor, which drops a G500 write; the frame route assigns into the dictionary by key and would store one. Neither is reachable from a panel, because both panels only ever send an id their own read returned.
  • The caption. Work Coordinates picks its caption from the snapshot's coordinate-kind field and on this brand shows "Stored as Siemens settable frames ($P_UIFR; G500 cancels and is always zero)." — which is that leaf naming this one. This leaf's caption is fixed.

Nothing reconciles the two views while both are in scope, and nothing needs to: the editor row mounts one panel at a time and each fetches once on mount, so moving the selection between the two nodes is itself the refresh. What a panel that stays mounted therefore never sees is described in Editing Contract.

Editing a Frame

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 second commit that follows Enter are Numeric Input. No cell here passes a minimum or a maximum, and no cell carries a unit suffix — the millimetre is stated in the column header instead.

The write is the whole row. A commit assigns the new number into the local row and sends that row's x, y and z as they now stand under the row's id. The optimistic shape, the single-cell rollback and the toast a failure raises are the branch's own and are described in the Editing Contract; what is this panel's is the guard in front of them. The numeric field can emit an empty value as null and the literals Infinity, -Infinity and NaN as non-finite numbers, and the handler returns before the request on all four. So a cleared cell stores nothing and clears nothing: the box shows blank while the stored number stands, and the number returns when the panel is remounted by selecting another node and coming back.

No surface here renames a frame: the id is the write's route key and no route accepts a replacement for it. The panel reports no structural change either, so no edit on this leaf rebuilds the branch.

What Survives, and What a Brand Change Destroys

The table is per-case: SiemensFrameTableProxy stands in the runner's pipeline list and the real table lives on the project's PerCaseNcDependencyList, serialized inside the project file. That proxy carries no seed — it installs a freshly constructed table when the project holds none and takes the project's own table thereafter — so the runner file records only the placeholder, and a .Controller written from this project carries no frame values at all. The ownership rules the proxy pattern follows across the branch are Program Data.

Two consequences follow for the values on this leaf.

  • A same-brand runner install keeps them. The incoming preset's proxy re-binds to the table the project already holds rather than replacing it, and the sweep that follows keeps every per-case table the new runner resolves through a proxy.
  • A switch to any other brand destroys them. No other preset proxies a frame table, so the sweep removes it, and switching back installs a fresh all-zero table rather than the one that was there. The brand switch's optional carry salvages a corner of it: the offsets are read from the outgoing provider before the swap and written into the incoming one afterwards, but only for ids the incoming provider already enumerates. No other shipped provider allocates a G5xx id — the Fanuc-family and Syntec tables enumerate G54G59 and G54.1P1G54.1P48, the Heidenhain datum table G54G59 — so the carry keeps G54 through G57 and the whole extended tail is lost. What a switch keeps, resets and destroys elsewhere is Brand Switch; which leaf exists on which brand is Brand Matrix.

What a Run Writes Back

A played program does not only read this table; it writes into it, through a bridge with two halves.

  • $P_UIFR[n,axis,TR]=… writes into it. A literal numeric assignment is routed into the table: an X, Y or Z component replaces that component of the frame entry, and any other axis letter lands in the per-axis dictionary. A non-literal right-hand side is left for the expression evaluator earlier in the same block, and a write to index 0 is consumed and ignored, mirroring the cancel frame's own rule. With no frame table on the runner the bridge is a no-op and the assignment stays visible as unconsumed residue.
  • $P_UIFR reads come back out of it. The same mapping serves the read side, answering zero for an allocated frame's unset axis component and for every component of G500, and null — a fall-through to the next lookup in the chain — for an id the table does not hold.

The table is deliberately not session-resettable. Settable frames are setting data on a real control: a $P_UIFR write survives reset and power-off, so a replayed session must see what the previous run left. The runner clears every session-resettable dependency on the fresh-session edge and this table is not among them, which means a value a run wrote is what this panel shows afterwards, and what the project file keeps.

Which frame a program starts on is not stored in the table either. The Siemens preset's static initializer seeds the first block's coordinate id as G500, so an untagged Siemens program begins with no frame active and a zero offset until a frame word appears — unlike the Fanuc-family and Syntec presets, which begin on G54.

Layout

  • General Setup Control Tree — the left dock of /general-setup
    • Controller Node Row
      • Program Data Group Row — equipment/controller/program-data
        • Frames (Siemens) Node Row — equipment/controller/program-data/frames; grown while the snapshot reports a Siemens frame table
  • Editor Row — the panel of whichever node is selected
    • Frames (Siemens) 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 Siemens frame table on the active runner."; shown instead of everything below while the panel's own read reports no table
      • Header Strip
        • Description Caption — "Settable frames ($P_UIFR, translation only); G500 cancels and stores nothing.", left-aligned and grey
        • Show all Toggle — right-aligned, dense; always rendered
      • Frame Table — dense, flat, bordered; no sort, no pagination, no column menu
        • Header Row — Frame, X (mm), Y (mm), Z (mm). Only Frame is translated
        • Frame Row, one per visible id — not clickable, and carrying no tooltip
          • Frame Id Label — bold plain text, never editable
          • Value Numeric Field, one per axis column — no minimum, no maximum, no unit suffix
    • Toast — negative, three seconds, the panel's context followed by the server's own message

The panel carries no heading, no add or delete control, no reset button, no save button and no unsaved marker.

Source Code Path

See HiNC App Anatomy for git repository links.

Web Application

HiNC-2025-webservice (Quasar CLI SPA):

  • wwwroot-src/src/components/controlTree/SoftNcFramesPanel.vue — the panel: the two empty layers, the fixed description and the unconditional Show all toggle, the four-column table with its two literal header spellings, the always-visible regular expression, and the whole-triad cell commit with its null and non-finite guard.
  • wwwroot-src/src/components/controlTree/SoftNcWorkCoordinatesPanel.vue — the other node on this table: the same ids and values through the ISO reader, plus the wider always-visible test, the conditional toggle, the P0 and M0 actions and the canvas marker this leaf has none of.
  • wwwroot-src/src/components/controlTree/softNcItemTypes.ts — the branch builder: this leaf's node id and label key, the snapshot flag it is pushed behind, and its position among the program-data children.
  • 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 frames reader and its whole-triad setter, the shared offset-row shape and parser both leaves reuse, and the presence flag this leaf is gated on.
  • wwwroot-src/src/api/http.ts — the envelope helper that turns a success: false body into a thrown error indistinguishable from a transport failure.
  • wwwroot-src/src/composables/useSoftNcRunner.ts — the shared snapshot the first empty layer reads.
  • wwwroot-src/src/i18n/en/softNc.ts — the node label, the description, the Frame column header, the absent-table line and the two error contexts.
  • wwwroot-src/src/i18n/en/common.ts — the shared Show all label.
  • Mech/SoftNcRunnerController.cs — the REST surface: the presence probe behind the node's gate, the unsorted frames reader with its zero fill, the per-id writer that assigns into the dictionary directly rather than through the coordinate accessor, the work-coordinate routes that share the same object on this brand, and the brand switch's capture-swap-carry-sweep with its accepted-id filter.

HiAPI Engine

  • HiMech/NcParsers/Dependencys/Siemens/SiemensFrameTable.cs — the table: the ninety-nine ids its constructor seeds, the cancel frame it keeps out of them, the coordinate accessors the work-coordinate leaf uses, the per-axis accessors the $P_UIFR bridge uses, the axis dictionary neither panel exposes, the deliberate absence of session-reset, and the id-sorted serialization that decides the row order after a reload.
  • HiMech/NcParsers/Dependencys/Siemens/SiemensFrameTableProxy.cs — the seedless get-or-create placeholder: what the runner file records, and the fresh table it installs into a project holding none.
  • HiMech/NcParsers/Dependencys/IIsoCoordinateConfig.cs — the offset-provider contract this table implements, which is what makes it the work-coordinate leaf's provider on this brand.
  • HiMech/NcParsers/Dependencys/Siemens/SiemensMachineDataTable.cs — the MD table beside it, carrying no frames and implementing no offset-provider contract.
  • HiMech/NcParsers/Keywords/Siemens/SiemensKeywords.cs — the cancel frame and the extended series the constructor seeds from.
  • HiMech/NcParsers/Keywords/Generic/IsoKeywords.cs — the G54 series the ISO syntax recognises, wider than this table allocates.
  • HiMech/NcParsers/SoftNcRunner.cs — the Siemens preset that carries the frame-table proxy as its first per-case entry, behind only the brand marker; the proxy resolution every read goes through; and the session-reset sweep this table is deliberately outside of.
  • HiMech/NcParsers/ISessionResettable.cs — the marker the table does not carry, and the difference between session state and setting data it draws.
  • HiMech/NcParsers/NcRunnerSuit.cs — the per-case list the table hangs off and the proxy wiring that installs it.
  • HiMech/NcParsers/LogicSyntaxs/CoordinateOffsetUtil.cs — the resolver that walks every provider and takes the first non-null answer, the coordinate section it writes, and the translation it composes.
  • HiMech/NcParsers/LogicSyntaxs/Siemens/SiemensCoordinateOffsetSyntax.cs — the frame-word path: the vocabulary it detects, the flag it leaves in place, the modal lookback, and the zero fallback when nothing answers.
  • HiMech/NcParsers/LogicSyntaxs/IsoCoordinateOffsetSyntax.cs — the sibling registered beside it on this preset, writing the same chain entry from the G54G59.9 vocabulary.
  • HiMech/NcParsers/LogicSyntaxs/Siemens/SiemensProgrammableFrameSyntax.cs — the computed programmable frame, and the entry it shares with the tilt cycle ahead of the settable frame's.
  • HiMech/NcParsers/Syntaxs/SiemensSyntaxUtil.cs — the slot order that decides the composition: tilt and programmable frame, tool height, the two coordinate syntaxes, then the pivot transform.
  • HiMech/NcParsers/Syntaxs/TransformationUtil.cs — the transform chain: in-order multiplication, replacement of a repeated entry in place, and the pivot entry that must stay last.
  • HiMech/NcParsers/LogicSyntaxs/MachineCoordSelectSyntax.cs — the one-shot codes that bypass the composed transform, widened on this preset to G153 and SUPA.
  • HiMech/NcParsers/EvaluationSyntaxs/Siemens/SiemensUifrWritingSyntax.cs — the write half of the $P_UIFR bridge: the literal-only rule, the ignored cancel-frame index, and the no-op when no frame table resolves.
  • HiMech/NcParsers/EvaluationSyntaxs/Siemens/SiemensUifrVariableLookup.cs — the read half.
  • HiMech/NcParsers/EvaluationSyntaxs/Siemens/SiemensVariableKey.cs — the index-to-id map behind both halves, and the TR-only key pattern.
  • HiMech/NcParsers/EvaluationSyntaxs/Siemens/SiemensSystemVariableSyntax.cs — where a non-TR frame component goes instead: recorded on the block, reported unsupported, never reaching the frame table.
  • HiMech/NcParsers/Initializers/StaticInitializer.cs — the Siemens preset's first-block coordinate id, and the two brands that differ from it.
  • HiMech/NcParsers/Dependencys/IsoCoordinateAddressMap.cs — the Fanuc-family id set the brand switch's carry intersects this table against.
  • HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainDatumTable.cs — the third id set the same carry can meet.

See Also

  • Program Data Plane — the plane this leaf sits on, and the ownership rules that decide what a runner install keeps and what a brand switch sweeps
  • Work Coordinates — the other node on this table, where the same ids are edited as G54 offsets, with the row actions and the canvas marker this leaf has none of