User manual › Recipes and templates — recording, replaying and reusing diagrams
kind: manual#recipe#icore#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 .icore, plus 7 example projects; measured 2026-09-26).

What a recipe records#

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

  1. Global definitions first, only when the recipe is of the whole project: the project's enumeration types (defineEnum(Name, {A, B}, [1 2], Default), below), then the global variables (name = value lines).
  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/ICoreBlocks/ICoreBlockLibrary/Templates/Subsystems/first_order_lag.icore, 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. A subsystem template may also carry // @face: <setting> = <value> lines, which set the inserted subsystem block itself (its Treat as Atomic Unit, say) rather than a block inside it. 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.
  • Each subsystem has its own undo and redo (since 2026-09-23). Ctrl+Z undoes the last change made in the subsystem the canvas you are looking at shows — not the last change anywhere in the project; with no canvas focused it acts on Home. A change that touched two levels at once (creating a subsystem, adding or removing a gate port, deleting a subsystem) is one step in both, and it is refused — with a message naming the other level — while that other level has newer changes of its own: undo those first. Undo and redo are still recipe replays underneath (ICoreStudioStateMachine.h), and scope settings (axis labels, colours) come back as they were at that step.
  • A subsystem can live in a file of its own. Give a Subsystem a Referenced File in its settings, a path inside the project folder such as References/Filter.icore, and it becomes an instance of that file: the project file keeps the path and the subsystem's ports, and the file keeps what is inside. Every instance of one file shows that file's contents when the project opens. Editing an instance edits the file. The first instance you edit holds the file until you save, and saving writes the file first, then the project. Until then, an edit in another instance of the same file is refused and taken back with a message naming the instance that holds it, and so is an undo or redo there. Saving also creates the file from the instance when it is not there yet. A file that is missing when the project opens is reported, and the instance keeps its ports and links with nothing inside; the project still opens.
  • 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.
  • An enumeration type is a project definition. defineEnum(BfMode, {Off, On, Fault}, [0 1 9], Off) defines a type the way MATLAB's Simulink.defineIntEnumType('BfMode', {'Off','On','Fault'}, [0 1 9], 'DefaultValue', 'Off') does, with the same rules: names are identifiers and appear once, values are whole numbers that fit an int32, a value two names share makes the second an alias, and without a default the first member is the default. Defining the type again replaces it. It is saved with the project and comes back when the project opens; one statement is one undo step.

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).
  • "I inserted one of my own templates and my solver settings changed." A template you saved yourself carries the solver settings of the model it was saved from, as setModelConfig lines ahead of its blocks, and inserting it replays them into the model you insert it into (run 7 below). The shipped templates carry none. Check modelConfig after inserting one of yours, or delete those lines from its file.
  • "Where do saved scripts run?" The Code Engine → ICore Script IDE window (Run, Run All, New, Save, Delete) keeps scripts under <Project>/scripts/*.icore 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... (*.icore)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 ICore Script IDE

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 .icore into Templates/Subsystems/ (or a project folder into Templates/Examples/<Name>/; create the folder if it is not there yet) 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. Export as ICore Recipe... also writes the solver configuration ahead of the blocks, one setModelConfig <property> <value>; line per setting (since 2026-09-03), and an import applies those lines through the console's own command, so the file carries the run's settings as well as the diagram; a bad line is reported and the rest still lands. A project keeps its solver settings in solver.ini instead, and a shipped subsystem template carries none, which is why an example that needs a configured solver ships as a project folder (.iproj + solver.ini). A template you save yourself is written by the same writer as Export as ICore Recipe..., so it does carry the setModelConfig lines, and inserting it applies them. The Simulink script does the same in Simulink's words — see the Simulink bridge entry of User glossary — the vocabulary of using ICoreBlocks.

Real runs#

Headless, on the build of 2026-09-25 23:58 (approximately commit 1dac24c5), with a private HOME so nothing touches real projects; re-run 2026-09-26. Startup lines stripped and the scratch path shortened to <scratch>; 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> ICoreBlocks.app/Contents/MacOS/ICoreBlocks --console "templates" | grep "template(s)"
49 template(s):

$ HOME=<scratch> 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> 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(Sampling Time (s), -1);
Step.setConfig(Step Time (sec), 1);
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, 1790448651635, 1790448653962);
First_Order_Lag.setTimes(1790448653908, 1790448653962);
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
$ 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, after the solver settings it carries (excerpt of its 44 lines):

$ HOME=<scratch> 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.icore
  insert it with:  useTemplate user/subsystems/My_Gain_Stage

$ head ".../Templates/Subsystems/My_Gain_Stage.icore"
// ICoreBlocks recipe (source: ICore Blocks/Home)
// @title:    My Gain Stage
// @category: My Templates
// @kind:     subsystem
// ---- Solver configuration (modelConfig) ----
setModelConfig startTime 0;
setModelConfig stopTime 10;
setModelConfig infiniteSimulation false;
setModelConfig maximumConsecutiveTimeBuffer 10000;
setModelConfig slowPaceStepDelay 0;

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

  useTemplate <id> [parent] [x] [y]     insert as a subsystem (default Home)
  openTemplate <id> [name]              open as a new project

(d) A name clash on insert, opening an example as a project, and a saved template's solver settings arriving with it (runs 5, 6 and 7):

$ ... --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"
[ICoreRunDiagnosis] Loading config at block: ICore Blocks/Home/Discrete PID Controller
[ICoreRunDiagnosis] Loading config at block: ICore Blocks/Home/Discrete PID Controller 2DOF
opened 'Digital PID Loop' as a new project:
  <scratch>/Documents/ICore Blocks/My PID Loop
$ ls "<scratch>/Documents/ICore Blocks/My PID Loop"
AGENTS.md  CLAUDE.md  GEMINI.md  Images  My PID Loop.iproj  exportTargets.ini
projectPreferences.ini  solver.ini

$ ... --console "setModelConfig stopTime 5; g = block(Gain); saveTemplate Five"
saved 'Five' as a subsystem template:
  <scratch>/Library/Application Support/ICore Blocks/Templates/Subsystems/Five.icore
  insert it with:  useTemplate user/subsystems/Five
$ ... --console "setModelConfig stopTime 20; useTemplate user/subsystems/Five; getModelConfig stopTime"
5

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:).