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):
- Global variables first (
name = valuelines), only when the recipe is of the whole project. - Every block, in the order they sit in the level:
h = block(Type)binds a handle, then.rename,.move(x, y),.resize(w, h),.rotateif rotated,.commentOutif commented out, one.setConfig(key, value)per non-private parameter, and the block's editable ports (clearPorts()thenaddPort(...)). Scope blocks also get their chart settings (buffer width, axis labels, fonts, line styles) — never the plotted data. - 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. - Links after all blocks of a level:
l = connect(a<0>, b<0>), the link's name, and its route asl.setCorners((x, y), ...). A branch off an existing link is a furtherconnect(l, c<0>). - Areas, images and text boxes last, with position, size, colour, title and text.
- 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>.iprojis plain recipe text of the whole project (ICoreStudioSerialization.h); solver settings and export targets travel beside it assolver.iniandexportTargets.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 (uniqueSiblingNameinICoreTemplateLibrary.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,
saveTemplaterefuses a name that exists; the toolbar dialog asksReplace 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.builtinsfolder 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, categoryUncategorized. - "
k = block(Gain) // notedid nothing, and every laterk.movefails 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// @titleheader and is an ordinary project (observed in run 6). - "Where do saved scripts run?" The
Code Engine → Script Runnerwindow (Run,Run All,New,Save,Delete) keeps scripts under<Project>/scripts/*.iscriptand stops at the first failing line; itsImport Diagrambutton 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 to | Toolbar / menu | Console (see Command glossary — console commands, verbs, functions) |
|---|---|---|
| list templates | Templates button → Insert Template (by category); Library Navigator → Subsystem Templates; the New Tab card → SUBSYSTEM TEMPLATES | templates [category or kind] |
| insert a template as a subsystem | Templates → Insert Template → category → title; or drag from the Library Navigator | useTemplate <id> [parent] [x] [y] |
| start a project from a template | New Project form → PROJECT TEMPLATES cards (or Blank Project) | openTemplate <id> [name] |
| save the current level as a template | Templates → Save '<level>' as Template… (title, category, summary) | saveTemplate <name> (Home, category My Templates) |
| find your template files | Templates → Open Templates Folder… | path printed by saveTemplate |
| write a level to a recipe file | Export → Export as ICore Recipe... (*.iscript) | generateRecipe [path] prints it |
| replay a recipe file into the level on screen | Import → 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:).