User manual › Recipes and templates — recording, replaying and reusing diagrams
kind: manual#recipe#iscript#iproj#templates#console#undo#project-file#manual

Recipes and templates — recording, replaying and reusing diagrams#

A recipe is a block diagram written as text: one statement per line, in the same language you type into the command window (The command window — the command engine for a user) — block(...), .move, .setConfig, connect(...). The application can write a recipe from any diagram and rebuild the diagram from a recipe, and it uses that pair for three things you meet as a user: your project file, undo/redo, and templates. A template is a recipe that has been shipped with the application or saved by you so it can be dropped into any diagram again. There are 49 built-in templates in this build (42 subsystem templates, listed on Template catalog — every .iscript, plus 7 example projects; measured 2026-08-17).

What a recipe records#

Read a recipe top to bottom and you have the order the application recreates things in (src/ICoreSDK/ICoreCoder/ICoreCommandSystem/ICoreRecipeSerializer.cpp):

  1. Global variables first (name = value lines), only when the recipe is of the whole project.
  2. Every block, in the order they sit in the level: h = block(Type) binds a handle, then .rename, .move(x, y), .resize(w, h), .rotate if rotated, .commentOut if commented out, one .setConfig(key, value) per non-private parameter, and the block's editable ports (clearPorts() then addPort(...)). Scope blocks also get their chart settings (buffer width, axis labels, fonts, line styles) — never the plotted data.
  3. Subsystems the same way, with h = subsystem(); the recipe then descends into the subsystem's contents before continuing. A subsystem's own ports are not written — they reappear as the gate blocks inside it are recreated.
  4. Links after all blocks of a level: l = connect(a<0>, b<0>), the link's name, and its route as l.setCorners((x, y), ...). A branch off an existing link is a further connect(l, c<0>).
  5. Areas, images and text boxes last, with position, size, colour, title and text.
  6. Two timestamp lines (subsystemTimes(...), h.setTimes(...)) — created and last-modified times, as integers.

So geometry is recorded: a replayed diagram places every block at the recorded coordinates and routes every link through the recorded corners. Coordinates are relative to the level's origin anchor, with y increasing upward. Handles (Step, l_2, …) are minted from the live names and only matter inside the recipe; the visible name is what .rename(...) sets. Every generated statement ends in ;, which is the console's "do not echo" marker, so a replay prints nothing unless a line fails.

This is what a shipped template looks like (src/ICoreSDK/ICoreBlockLibrary/Templates/Subsystems/first_order_lag.iscript, trimmed):

// @title:    First-Order Lag
// @category: Control Systems
// @summary:  A step into a 1/(s+1) transfer function, watched on a scope.
// @kind:     subsystem

src = block(Step)
src.move(40, 295)
src.setConfig(Final Value, 1)

plant = block(Transfer_Function)
plant.move(240, 305)
plant.resize(130, 90)
plant.setConfig(Numerator, [1])
plant.setConfig(Denominator, [1, 1])

out = block(Scope)
out.move(460, 295)

connect(src<0>, plant<0>)
connect(plant<0>, out<0>)

The // @title / @category / @summary / @kind comment lines are the template's metadata; the file describes itself, there is no separate index. Whole-line // and # comments are skipped on replay; a hand-written recipe may leave the trailing ; off.

The rules a user meets#

  • Your project file is a recipe. <Project>/<Project>.iproj is plain recipe text of the whole project (ICoreStudioSerialization.h); solver settings and export targets travel beside it as solver.ini and exportTargets.ini, not inside the recipe. Opening a project empties the diagram and replays the file; if a line is not understood by this version, loading stops there and the project is reported as failed rather than saved back half-rebuilt.
  • Undo and redo are recipe replays — the application keeps a recipe per step and rebuilds the diagram from it (ICoreStudioStateMachine.h). Chart appearance (axis labels, colours) is not part of undo history; a project load restores it in full.
  • Importing a recipe or inserting a template APPENDS. Nothing on the level is removed; the recipe's top-level objects are re-parented into the level on screen. The whole insert is one undo step (ICoreRecipeFileTransfer.h, ICoreTemplateLibrary.h).
  • A template inserts as one subsystem, named after its title; if that name is already taken in the level you get a numbered sibling (First-Order Lag 2), never a refusal and never a merge (uniqueSiblingName in ICoreTemplateLibrary.cpp, run 5 below).
  • A failing line does not stop a replay. Each failed statement becomes one warning (! '…' failed: …) under an otherwise successful "inserted/imported" message; read them — a block that is missing from the result is reported there and nowhere else.
  • A block name must be unique within its level. rename() refuses a taken name (rename(): a block named 'X' already exists in …) — the usual cause is replaying a recipe into a level that still holds the originals.
  • Your own templates never overwrite silently. From the console, saveTemplate refuses a name that exists; the toolbar dialog asks Replace Template? first.
  • Built-in templates are read-only and refreshed at every launch. The application wipes and rewrites its on-disk copy of the shipped set from the binary each start (Initialization.cpp, mirrorBuiltInTemplates), so editing a file in the .builtins folder is lost at the next launch. Save your own copy instead.

Things that surprise users#

  • "I inserted a template and it landed on top of my blocks." A template is placed at the position you give (useTemplate <id> [parent] [x] [y], default the origin) or, from the toolbar, at the centre of the visible canvas; its interior keeps the authored coordinates. Move the new subsystem — its contents come with it.
  • "The imported diagram is there but every link is dangling / a block is missing." Scroll the notification: the per-line warnings name the statement that failed. Typical cause: the recipe names a block type this build does not have, or a port index that does not exist on it.
  • "My template shows no summary and sits under Uncategorized." Metadata is read from the // @… header lines at the top of the file. A hand-written file without them lists under its file stem, category Uncategorized.
  • "k = block(Gain) // note did nothing, and every later k.move fails too." Only whole-line comments are skipped; a trailing comment is handed to the interpreter as part of the statement, the handle never binds, and every later use of it fails. Put comments on their own line.
  • "I edited an example project and my changes are in the template." They are not: an example opens as a copy under your projects folder (Documents/ICore Blocks/<Name>/); the shipped copy is untouched. Once you save, the copy has lost its // @title header and is an ordinary project (observed in run 6).
  • "Where do saved scripts run?" The Code Engine → Script Runner window (Run, Run All, New, Save, Delete) keeps scripts under <Project>/scripts/*.iscript and stops at the first failing line; its Import Diagram button writes the recipe of any subsystem into a new script so you can start from something already drawn (ICoreScriptRunner.h, ICoreCommandScriptWindow.cpp).

Where the surfaces are#

You want toToolbar / menuConsole (see Command glossary — console commands, verbs, functions)
list templatesTemplates button → Insert Template (by category); Library Navigator → Subsystem Templates; the New Tab card → SUBSYSTEM TEMPLATEStemplates [category or kind]
insert a template as a subsystemTemplates → Insert Template → category → title; or drag from the Library NavigatoruseTemplate <id> [parent] [x] [y]
start a project from a templateNew Project form → PROJECT TEMPLATES cards (or Blank Project)openTemplate <id> [name]
save the current level as a templateTemplates → Save '<level>' as Template… (title, category, summary)saveTemplate <name> (Home, category My Templates)
find your template filesTemplates → Open Templates Folder…path printed by saveTemplate
write a level to a recipe fileExport → Export as ICore Recipe... (*.iscript)generateRecipe [path] prints it
replay a recipe file into the level on screenImport → Import as ICore Recipe...paste the lines into the command window, or Script Runner

Template ids are stable addresses: subsystems/<stem> and examples/<Folder> for the built-ins, user/subsystems/<stem> for yours; ids are matched case-insensitively.

Your own templates live in the application's data folder, not with your projects: ~/Library/Application Support/ICore Blocks/Templates/ on macOS, %LOCALAPPDATA%\ICore Blocks\Templates\ on Windows, ~/.local/share/ICore Blocks/Templates/ on Linux (ICoreDocumentsFolder.cpp, ICoreStandardPaths.cpp). Drop a .iscript into Templates/Subsystems/ (or a project folder into Templates/Examples/<Name>/) and it is in every list on the next look — the catalog rescans the folder each time it is opened, no restart needed. The shipped set is mirrored into Templates/.builtins/ (hidden) beside them.

How it works, briefly#

A recipe line and a console line are the same thing — the interpreter tries the recipe grammar before the console's math grammar. useTemplate builds one text (a subsystem() statement, a move, then the template body with its top-level parents rewritten to the new subsystem) and replays it as one stream, which is why the insert is one undo step. saveTemplate and Export as ICore Recipe... use the same writer as the project file plus a // ICoreBlocks recipe (source: …) header that tells a later import which parents to rewrite. Solver settings are never in a recipe, which is why an example that needs a configured solver ships as a project folder (.iproj + solver.ini).

Real runs#

Headless, on the build of 2026-08-16 23:39 (approximately commit 2e126fbf), with a private HOME so nothing touches real projects. Qt platform noise stripped; everything else verbatim.

(a) List, insert, and read the level back as a recipe — the read-back is what generateRecipe (or Export as ICore Recipe...) produces from a live diagram:

$ HOME=<scratch> QT_QPA_PLATFORM=offscreen ICoreBlocks.app/Contents/MacOS/ICoreBlocks --console "templates" | grep "template(s)"
49 template(s):

$ HOME=<scratch> QT_QPA_PLATFORM=offscreen ICoreBlocks.app/Contents/MacOS/ICoreBlocks \
    --console "useTemplate subsystems/first_order_lag Home 40 40"
Software Initialized
inserted 'First-Order Lag' into Home as subsystem 'First-Order Lag' — 4 block(s), 2 connection(s)
exit=0

$ HOME=<scratch> QT_QPA_PLATFORM=offscreen ICoreBlocks.app/Contents/MacOS/ICoreBlocks \
    --console "useTemplate subsystems/first_order_lag Home 40 40; generateRecipe Home"
Software Initialized
First_Order_Lag = subsystem();
First_Order_Lag.rename(First-Order Lag);
First_Order_Lag.move(40, 40);
First_Order_Lag.resize(160, 120);
First_Order_Lag.setConfig(Sampling Time (s), -1);
Step = block(Control_Systems/Sources/Step, First_Order_Lag);
Step.rename(Step);
Step.move(40, 295);
Step.resize(70, 70);
Step.setConfig(Initial Value, 0);
Step.setConfig(Final Value, 1);
...
l = connect(Step<0>, Transfer_Function<0>);
l.rename("ICoreDouble");
l.setCorners((105, 260), (245, 260));
l_2 = connect(Transfer_Function<0>, Scope<0>);
l_2.rename("ICoreDouble 1");
l_2.setCorners((365, 260), (465, 260));
subsystemTimes(ICore Blocks/Home, 1786955246451, 1786955248214);
First_Order_Lag.setTimes(1786955248203, 1786955248214);
exit=0

Note the "4 block(s)": the count includes the subsystem itself. Note also that the read-back spells full block types (Control_Systems/Sources/Step) where the shipped file wrote Step; both replay.

(b) The mirrored built-in templates after one launch:

$ cd "<scratch>/Library/Application Support/ICore Blocks" && ls -a Templates
.  ..  .builtins  Examples  Subsystems
$ ls Templates/.builtins/Subsystems | wc -l ; ls Templates/.builtins/Examples
      42
Closed_Loop_Speed_Control  DC_Motor_Drivetrain  Digital_PID_Loop  Kalman_Tracking_Loop
Mobile_Base_Steering  Recursive_Identification_Rig  Two_Degree_PID_Loop

(c) Build a small diagram from recipe lines, save it as a template, and see it list — the file it produced is the recipe the diagram serializes to (excerpt of its 24 lines):

$ HOME=<scratch> QT_QPA_PLATFORM=offscreen ICoreBlocks.app/Contents/MacOS/ICoreBlocks --console \
  "g = block(Gain); g.setConfig(Gain Value, 2.5); g.move(100, 200); s = block(Sine_Wave); s.move(0, 200); connect(s<0>, g<0>); saveTemplate My Gain Stage"
Software Initialized
saved 'My Gain Stage' as a subsystem template:
  <scratch>/Library/Application Support/ICore Blocks/Templates/Subsystems/My_Gain_Stage.iscript
  insert it with:  useTemplate user/subsystems/My_Gain_Stage

$ head ".../Templates/Subsystems/My_Gain_Stage.iscript"
// ICoreBlocks recipe (source: ICore Blocks/Home)
// @title:    My Gain Stage
// @category: My Templates
// @kind:     subsystem
Gain = block(Control_Systems/Base_Blocks/Gain);
Gain.rename(Gain);
Gain.move(100, 200);
Gain.resize(70, 70);
Gain.setConfig(Gain Value, 2.5);
Sine_Wave = block(Control_Systems/Sources/Sine_Wave);

$ HOME=<scratch> QT_QPA_PLATFORM=offscreen ICoreBlocks.app/Contents/MacOS/ICoreBlocks --console "templates My Templates"
1 template(s):
My Templates
  user/subsystems/My_Gain_Stage       My Gain Stage  (yours)

(d) A name clash on insert, and opening an example as a project (runs 5 and 6):

$ ... --console "useTemplate subsystems/first_order_lag; useTemplate subsystems/first_order_lag Home 300 40"
inserted 'First-Order Lag' into Home as subsystem 'First-Order Lag 2' — 4 block(s), 2 connection(s)

$ ... --console "openTemplate examples/Digital_PID_Loop My PID Loop"
opened 'Digital PID Loop' as a new project:
  <scratch>/Documents/ICore Blocks/My PID Loop
$ ls "<scratch>/Documents/ICore Blocks/My PID Loop"
Images  My PID Loop.iproj  exportTargets.ini  projectPreferences.ini  solver.ini

For contributors#

Authoring geometry, the replay traps behind the warnings above and the rebuild-free verification loop are on the contributor page for adding a template; the full recipe grammar and the serializer's permanent round-trip limits are on the ICoreCoder module page (both in this page's related:).