Table of Contents

Program Reading

Program Reading is the pair of Machine / Controller leaves that change how the runner reads a program rather than how the machine moves: which block-skip layers are active, and where a subprogram call goes looking for its file. Both live on the General Setup page at /general-setup, under the Control-Tree ids equipment/controller/machine/block-skip and equipment/controller/machine/subprograms. The pair is also unevenly branded — every shipped brand preset carries the subprogram-folder config, and one of the five carries no block-skip config at all.

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 Asymmetry

Both nodes are grown behind a plain presence probe over the runner's proxy-resolved dependency list: hasBlockSkip is set when the list holds any IBlockSkipConfig, hasSubprogramFolders when it holds a SubProgramFolderConfig. Neither probe inspects a value.

Brand preset Block-skip config Subprogram-folder config
Fanuc yes yes
Siemens yes yes
Syntec yes yes
Mazak yes yes
Heidenhain no yes

GenericBlockSkipConfig is the only implementer of the block-skip interface anywhere in the engine, and the Heidenhain preset's pipeline list is the one of the five that does not carry it. That single omission is what makes Block Skip / Delete the one node in the whole Controller branch that exists on four brands and not the fifth; every other gate on this plane is satisfied either by all five presets or by a Siemens-only table. Which flag each row answers to is Brand Matrix.

The config is not the only thing missing there. The block-skip switches gate a parsing syntax, BlockSkipSyntax, and that syntax appears in the Fanuc, Siemens, Syntec and Mazak syntax lists and in no other — the Heidenhain list carries no block-delete parser, so on that preset the nine switches would have nothing to gate.

The subprogram-folder config runs the other way: all five presets carry one, and five call statements across three brand families read the same two values. The node's own vocabulary is Fanuc's, because the storage is, but the setting is not Fanuc-only.

Block Skip / Delete

The panel is a caption over nine checkboxes and nothing else. The caption reads "Enabled layers skip their /n-prefixed blocks (/ alone = layer 1).", the first box is labelled "Layer 1 (bare /)" and the rest “Layer 2” through “Layer 9”.

What a layer does to a block

BlockSkipSyntax matches a leading / optionally followed by a single digit 19; a bare slash is layer 1. It then always writes a BlockSkip section recording the symbol and the layer, and always consumes the prefix out of the block's unparsed text — so the / itself never survives as residue for the downstream unrecognized-text check, whatever the switches say.

What the switch decides is the block's body:

  • Layer on — the remaining text is moved into Body and the unparsed text is cleared. Every downstream parsing syntax then sees nothing, so the block emits no NC act, and the run records BlockSkip--Skipped at message severity for that block.
  • Layer off, or no config at all — the body stays null and the rest of the block parses exactly as an unprefixed one would. A runner with no block-skip config therefore simulates the whole program, which is the safe reading of an unknown machine setting.

Ordering matters for one thing and is fixed in each brand's list: the comment and NC-embedded script syntaxes run before the block-skip syntax, so a comment on a skipped block — and any script embedded in it — still takes effect while the motion beside it does not.

One value, seven spellings of the same range

Layers 1 through 9 are the whole range, and no shared constant carries it. The bound is written out separately in the parsing syntax's regular expression, in the config's XML reader and again in its CSV setter, in its per-layer mutator guard, in the read endpoint that enumerates the layers, in the write endpoint that loops over them, and in the panel's checkbox repeat. A tenth layer is therefore seven edits, not one, and a partial change would be silent — a switch the panel offered but the config refused to store would simply never take.

What a fully cleared set does not survive

A freshly applied preset has layer 1 on and the other eight off. The stored form is a comma-separated list of the enabled layers, and an empty set serializes as an empty element. The reader treats a blank list and a missing element identically — both mean layer 1 only — so clearing every box and then reloading the project brings layer 1 back on. Every other combination round-trips exactly.

The write

Each box commits on the click, and the request is the whole enabled set rather than the one layer that changed: the panel sorts its set and sends it, and the endpoint walks 1 through 9 setting each layer to whether the payload names it. A failed write restores the previous set and raises a toast; the shared rules behind that shape are Editing Contract.

On the brand that carries no config, the read answers not present with an empty layer list and any write is answered unsuccessfully with “No IBlockSkipConfig on the active runner” — but the node is not built there, so neither answer is reachable from the tree.

Subprogram Folders

Two plain text fields, labelled "Internal folder (M98)" and "External folder (M198)", hinted “Relative to the host NC file's folder; empty = that folder itself” and “Fanuc external-storage calls; empty = fall back to the internal folder”. They write InternalFolder and ExternalFolder; an empty or whitespace-only box is stored as null rather than as an empty string.

Which call statements read these two values

Preset Call statement Field it reads File-name chain, first match wins
Fanuc, Mazak, Syntec M98 P_ L_ Internal O{P:D4}.NC, O{P}.NC, O{P:D4}, O{P}, {P:D4}.NC, {P}.NC
Fanuc, Mazak, Syntec M198 P_ External, falling back to Internal when it is null the same chain
Fanuc G65 / G66 macro call Internal the same chain
Siemens name call (L9810, a named cycle) Internal {name}.SPF, {name}.MPF, {name}
Heidenhain CALL PGM name Internal {name}.h, {name}.H, {name}

Two consequences follow from the table, and both are invisible from the field labels.

The External folder is inert on two brands. Only the Fanuc-family inliner, SubProgramCallSyntax, ever reads it — the Siemens and Heidenhain call syntaxes look up their callee through the Internal folder alone. The field is offered on every brand because the storage is brand-neutral, and it is honoured on the three that have an M198.

The Internal folder is read by more than the M98 label says. On the Fanuc preset it also anchors both Custom Macro B lookups — FanucMacroCallSyntax for the G65 one-shot call and FanucModalMacroSyntax for the G66 modal — and on Siemens and Heidenhain it is the only subprogram root there is. Emptying it to fix an M98 path therefore moves the macro and named-call lookups with it.

Each brand also brings its own file-name chain, and the chains are per-syntax properties rather than one shared list — the Fanuc chain is FilenamePatterns, a static array every Fanuc-family caller shares, while FilePatterns and FilePatterns are instance properties of their own call syntaxes and are re-writable from the runner file. Case matching is delegated to the host file system throughout, which is why the Heidenhain chain carries both .h and .H.

Where a relative folder is anchored

The resolver takes the folder as written. An absolute path is used unchanged. A relative path — and an empty one, which means the root itself — is combined with the base directory supplied by ProjectFolderDependency, and the machining session wires that dependency to the project's own root before each play.

So the anchor is the project folder, not the folder the host NC file happens to sit in. A program played from a subfolder of the project resolves NC against the project root, not against its own neighbour of that name. The field's hint says otherwise, and so does the API summary on the two config properties the panel writes; the resolver is what runs.

Two further conditions end the lookup before any pattern is tried: a relative folder with no base directory resolves to nothing, and so does a folder that does not exist as a directory.

A missing file is an error on one family and a warning on the other

The same empty result is reported at two different severities, and the split follows the call statement rather than the setting.

  • Fanuc, Mazak, Syntec — an unresolved M98 / M198 raises SubProgramCall--FileNotFound at error severity, quoting the folder that was searched, and the call is consumed.
  • Siemens, Heidenhain — an unresolved call raises SiemensCall--Skipped or HeidenhainCall--Skipped at warning severity and is consumed with no motion effect. That is deliberate: the common unresolved callee on those controls is an OEM or measuring cycle whose definition file ships with the machine and never travels with the program, so a hard error would fire on ordinary, correct programs.

A resolved call is inlined: the callee is segmented through the runner's own segmenter and its blocks are prepended into the program ahead of the host block, each stamped with a record naming the call it came from. M98 with an L count above one inlines the same file that many times in series, each repetition its own segmentation pass.

The nesting ceiling, and where there is none

A self- or mutually-recursive callee re-captures its own call statement inside every inlined body, so a call path needs a rail of its own — no loop watchdog covers it. That rail is declared twice, independently, and is missing on the third path.

Preset Ceiling Where it is declared Exceeded
Siemens 32 frames DefaultMaxCallDepth SiemensCall--DepthLimitExceeded, consumed as a safe skip
Heidenhain 32 frames DefaultMaxCallDepth HeidenhainCall--DepthLimitExceeded, consumed as a safe skip
Fanuc, Mazak, Syntec none

The two constants are separate const int declarations on separate classes that happen to hold the same number, each surfaced as its own writable MaxCallDepth property and each serialized into the runner file as its own element. Raising one raises nothing else. Both compare against the depth of the call stack already stamped on the host block, so the count is frames entered, not files listed.

On the Fanuc-family inliner and on both Custom Macro B calls there is no depth comparison at all. None of them reads the host block's frame count before inlining, so none carries the recursion rail the Siemens and Heidenhain syntaxes declare. The depth itself is recorded either way — every inlined block is stamped with a pushed call-stack frame that the matching return statement pops — so what is missing is the comparison, not the count.

The Heidenhain call syntax carries a second, unrelated ceiling for its section-repeat form: MaxRepetitions caps a REP literal at 65534, since the repeat re-scans the file once per repetition.

What an empty Internal folder does not survive

The config's default internal folder is NC — the layout where the main program sits beside an NC/ directory of subprograms — and that default is applied by the property itself, before the XML reader runs. The writer omits an element for a null folder, and the reader keeps whatever the property already holds when the element is absent. Clearing the Internal folder box therefore stores a null, writes no element, and reads back as NC the next time the project is opened.

The External folder box has no such default and round-trips cleanly: cleared, it stores null, writes no element, and reads back null — which is the state the field's own hint describes, falling back to the internal folder.

Editing and Storage

Both panels open with the branch's two empty layers — the shared "No NC runner — load a project first." line first, then the panel's own read reporting "No block-skip config on the active runner." or "No subprogram-folder config on the active runner." The first layer is a live guard against the snapshot emptying under a panel that is already mounted, which is what closing the project does. The second is unreachable by navigating: the node's gate and the panel's read probe the same dependency on the same resolved list, so the leaf exists only where the read is about to succeed.

Their commit shapes differ, and both are recorded in Editing Contract: the block-skip boxes commit on the click and roll back on failure, while the two folder fields commit as a pair on blur or Enter of either one, compared against a saved snapshot of both rather than a per-field captured value — so editing one folder writes both, and a failure restores both.

Both configs are plain runner-owned entries rather than proxies, so their values serialize with the runner and follow it. Installing a controller file replaces them outright, and a brand apply assigns a whole fresh preset, so an applied brand change returns both to that preset's defaults — layer 1 alone, and the NC internal folder with no external one. Selecting the brand already in force is not a route to that reset: the apply stays disabled until the selection differs from the runner's own brand. Which half of a project each value on this plane lands in is Machine and Controller Plane.

Layout

  • General Setup Control Tree — the left dock of /general-setup
    • Controller Node Row
      • Machine / Controller Group Row
        • Block Skip / Delete Node Row — equipment/controller/machine/block-skip, grown while the snapshot reports a block-skip config
        • Subprogram Folders Node Row — equipment/controller/machine/subprograms, grown while it reports a subprogram-folder config
  • Editor Row — Block Skip / Delete Panel
    • No-Runner Line — "No NC runner — load a project first.", the branch's shared empty state
    • No-Config Line — "No block-skip config on the active runner."
    • Description Caption — "Enabled layers skip their /n-prefixed blocks (/ alone = layer 1)."
    • Layer CheckBox Column — nine dense boxes in one column
      • Layer 1 (bare /) CheckBox
      • Layer 2Layer 9 CheckBoxes
  • Editor Row — Subprogram Folders Panel
    • No-Runner Line — the same shared empty state
    • No-Config Line — "No subprogram-folder config on the active runner."
    • Internal folder (M98) Field — outlined and dense, hinted “Relative to the host NC file's folder; empty = that folder itself” — a hint the resolver contradicts, the anchor being the project root
    • External folder (M198) Field — outlined and dense, hinted “Fanuc external-storage calls; empty = fall back to the internal folder”

Every label above is a translated role string rather than a type name, so a locale change rewrites what is read without touching an id. Both panels mount against one node and take nothing from it: each serves a single tree id, so neither reads a role off the node's path.

Source Code Path

See HiNC App Anatomy for git repository links.

Web Application

HiNC-2025-webservice (Quasar CLI SPA):

  • wwwroot-src/src/components/controlTree/SoftNcBlockSkipPanel.vue — the block-skip panel: the two empty layers, the caption with its two code slots, the nine boxes and their layer-one label, and the whole-set write with its rollback.
  • wwwroot-src/src/components/controlTree/SoftNcSubprogramPanel.vue — the folder panel: the two outlined fields, the blur and Enter bindings on both, the saved-snapshot guard that skips a redundant write, and the paired rollback.
  • wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue — the shared no-runner line both panels open with.
  • wwwroot-src/src/components/controlTree/softNcItemTypes.ts — the two node ids, their labels and label keys, the snapshot flag each is pushed behind, and the item types that bind them to their panels.
  • wwwroot-src/src/api/softNcRunner.ts — the two readers and their coercions, the layer-array writer, the paired folder writer, and the snapshot fields the gates read.
  • wwwroot-src/src/api/http.ts — the envelope reader that turns an unsuccessful body into the error a panel's toast quotes.
  • wwwroot-src/src/composables/useSoftNcRunner.ts — the shared snapshot both first empty layers read, and its once-per-session install.
  • wwwroot-src/src/i18n/en/softNc.ts — the two node labels, the block-skip caption and layer labels, the four folder strings, the two empty-state lines and the error contexts.
  • Mech/SoftNcRunnerController.cs — the REST surface: the two presence probes on the snapshot, the block-skip read that enumerates the nine layers and the write that sets each one, the folder read that substitutes an empty string for a null and the write that substitutes a null for a blank, the brand switch whose remark names both of these among the runner-owned values it resets, and the shared helper that answers a missing dependency with an unsuccessful body rather than an error status.

HiAPI Engine

  • HiMech/NcParsers/Dependencys/IBlockSkipConfig.cs — the block-skip contract: the per-layer query and mutator, and the stated safe default when the dependency is absent.
  • HiMech/NcParsers/Dependencys/Generic/GenericBlockSkipConfig.cs — the only implementer: the layer-one default, the comma-separated stored form, the range filter repeated in its reader and its setter, and the blank-equals-absent rule behind the cleared-set round trip.
  • HiMech/NcParsers/ParsingSyntaxs/BlockSkipSyntax.cs — the prefix parser: the layer regular expression, the always-recorded section, the body move that silences a block, and the message the skip reports.
  • HiMech/NcParsers/Keywords/BlockSkip.cs — the recorded section: symbol, layer, and the body that is null exactly when the skip did not take effect.
  • HiMech/NcParsers/Dependencys/Generic/SubProgramFolderConfig.cs — the two folders, the NC default applied by the property itself, the null fallbacks, and the writer that omits an element for a null.
  • HiMech/NcParsers/EvaluationSyntaxs/SubProgramCallSyntax.cs — the Fanuc-family inliner: the internal-versus-external choice, the repetition loop, the file-not-found error, and the absence of any call-depth comparison.
  • HiMech/NcParsers/EvaluationSyntaxs/MacroFileResolver.cs — the shared resolver: the Fanuc file name chain, the absolute-versus-relative anchoring, and the directory-exists precondition.
  • HiMech/NcParsers/EvaluationSyntaxs/Fanuc/FanucMacroCallSyntax.cs, HiMech/NcParsers/EvaluationSyntaxs/Fanuc/FanucModalMacroSyntax.cs — the other two Fanuc readers of the internal folder: the G65 one-shot macro call, and the G66 modal that resolves the same file again on every motion block it fires on.
  • HiMech/NcParsers/EvaluationSyntaxs/Siemens/SiemensSubProgramCallSyntax.cs — the name-call inliner: its own file-pattern chain, its own depth constant and property, and the safe-skip that answers an unresolved OEM cycle.
  • HiMech/NcParsers/EvaluationSyntaxs/Heidenhain/HeidenhainSubProgramCallSyntax.cs — the klartext call inliner: the CALL PGM lookup through the same internal folder, the second depth constant, and the separate repeat ceiling.
  • HiMech/NcParsers/Keywords/CallStack.cs, HiMech/NcParsers/EvaluationSyntaxs/SubProgramReturnSyntax.cs — the frame each inliner stamps on an inlined block, and the M99 return that pops it.
  • HiMech/NcParsers/NcDiagnosticProgress.cs — the category and severity behind each diagnostic id named above.
  • HiMech/NcParsers/Dependencys/SystemWired/ProjectFolderDependency.cs — the base directory a relative folder is anchored against, and the host-wired provider behind it.
  • HiMech/MachiningProcs/MachiningSession.cs — where that provider is wired to the project root before a play.
  • HiMech/NcParsers/SoftNcRunner.cs — the five brand presets, and which of them carries each of these two dependencies.
  • HiMech/NcParsers/Syntaxs/FanucSyntaxUtil.cs, HiMech/NcParsers/Syntaxs/SiemensSyntaxUtil.cs, HiMech/NcParsers/Syntaxs/SyntecSyntaxUtil.cs, HiMech/NcParsers/Syntaxs/MazakSyntaxUtil.cs, HiMech/NcParsers/Syntaxs/HeidenhainSyntaxUtil.cs — the four lists that carry the block-skip parser and the one that does not, and which call syntax each preset runs.

See Also

  • Machine and Controller Plane — the plane these two leaves sit on, and the storage split that decides which of its values a brand apply resets and which it leaves alone
  • Brand Matrix — the two flags that grow these nodes, what each one probes, and the brand sets they produce
  • Editing Contract — the commit, rollback and empty-state rules these panels share with the branch, and the paired-field write recorded as one of its exceptions