API — ICoreSDK/ICoreCoder
The public contract of 27 header(s) under src/ICoreSDK/ICoreCoder — 29 class/struct definition(s), 92 declaration(s). Each section shows the header's banner and its public (and protected-virtual) surface exactly as the file writes it.
ICoreCoderAPI.h#
src/ICoreSDK/ICoreCoder/ICoreCoderAPI.h
The one header C++ callers outside ICoreCoder include to run command scripts: everything here forwards to the public API of ICoreScriptRunner, so callers never reach into the module tree themselves. Add a forwarder here when a runner API is meant for outside use; internal-only helpers stay off this surface.
The terminal-style REPL rides along: ICoreCoderShell (a sibling of this header) is part of the same outside-facing surface, so including this one file also brings ICoreCoderShell::execute() and the blocking run() loop.
The contracts are the runner's, not restated here — see ICoreScriptRunner.h for the GUI-thread rule, first-failing-line semantics and comment handling.
File-scope declarations#
// Which line failed and what the console printed for it (line 0 = the
// script never ran a line, e.g. its file could not be read).
using LineFailure = ICoreScriptRunner::LineFailure;
ICoreCoderShell.h#
src/ICoreSDK/ICoreCoder/ICoreCoderShell.h
ICoreCoderShell#
ICoreCoderShell.h:27 · class · pImpl · 6 declaration(s)
A terminal-style shell over the command console: prompt, read a line, evaluate, print, repeat.
class ICoreCoderShell {
public:
ICoreCoderShell();
~ICoreCoderShell();
// One terminal interaction: a line in, the printed result out. Blank
// lines and '#' comments succeed silently, exactly as in a script.
static ICoreCommandResult execute(const ICoreString& rawLine);
// Blocking REPL over the given streams: prompt, read, execute, print,
// until EOF or an "exit"/"quit" line. Returns the number of failed
// lines (0 = every command succeeded).
int run(std::istream& in, std::ostream& out);
// The prompt text printed before each read ("icore> " by default).
void setPrompt(const ICoreString& prompt);
const ICoreString& prompt() const;
private:
class Impl; // the two-line residue; state lives here
std::unique_ptr<Impl> impl;
};
ICoreCommandEngine.h#
src/ICoreSDK/ICoreCoder/ICoreCommandSystem/ICoreCommandEngine.h
ICoreCommandEngine#
ICoreCommandEngine.h:25 · class · pImpl · 6 declaration(s)
Central registry + dispatcher for the self-use command console.
class ICoreCommandEngine {
public:
// args = tokens after the command name. Returns text for the history panel.
using Handler = std::function<std::string(const ICoreStringList& args)>;
// Same, for a command that can fail. A plain Handler's text is always taken
// as success, which is right for the many commands whose only outcome is
// "here is what you asked for" — but a command that can genuinely fail has
// to say so, because the ok flag is what colours the console red, what stops
// a script at its first bad line (ICoreCommandScriptWindow, ICoreScriptRunner),
// and what --console turns into the process exit code.
using ResultHandler = std::function<ICoreCommandResult(const ICoreStringList& args)>;
static ICoreCommandEngine* instance();
void registerCommand(const ICoreString& name,
Handler handler,
const ICoreString& description = ICoreString());
void registerCommand(const ICoreString& name,
ResultHandler handler,
const ICoreString& description = ICoreString());
bool hasCommand(const ICoreString& name) const;
// Parse `rawLine` into name + args and dispatch. Never throws: a handler
// that throws is reported as a failure result instead of propagating.
ICoreCommandResult run(const ICoreString& rawLine) const;
ICoreStringList commandNames() const; // sorted — for help / autocomplete
ICoreString describe(const ICoreString& name) const;
private:
class Impl; // the two-line residue; state lives here
std::unique_ptr<Impl> impl;
};
ICoreCommandGlossary.h#
src/ICoreSDK/ICoreCoder/ICoreCommandSystem/ICoreCommandGlossary.h
ICoreCommandGlossary#
ICoreCommandGlossary.h:9 · class · nested Entry · 0 declaration(s)
Single catalog of everything the console understands: registered commands (from ICoreCommandEngine) plus the matrix functions (from ICoreExpressionEvaluator).
class ICoreCommandGlossary {
public:
struct Entry {
ICoreString name;
ICoreString kind; // "command" or "function"
ICoreString signature;
ICoreString description;
};
static ICoreList<Entry> entries(); // commands then functions
static ICoreStringList names(); // sorted, de-duplicated — for completion/suggestions
static ICoreString formatted();// human-readable multi-section listing
};
};
ICoreCommandResult.h#
src/ICoreSDK/ICoreCoder/ICoreCommandSystem/ICoreCommandResult.h
ICoreCommandResult#
ICoreCommandResult.h:8 · struct · 4 declaration(s)
Outcome of running one command line through ICoreCommandEngine.
struct ICoreCommandResult {
public:
bool ok = true; // false => render the output as an error (red)
bool handled = true; // false => the command name was not registered
ICoreString output; // text to show in the history panel
static ICoreCommandResult success(const ICoreString& text);
static ICoreCommandResult failure(const ICoreString& text);
static ICoreCommandResult unknown(const ICoreString& name);
};
};
ICoreCommandSamples.h#
src/ICoreSDK/ICoreCoder/ICoreCommandSystem/ICoreCommandSamples.h
ICoreCommandSamples#
ICoreCommandSamples.h:8 · class · 1 declaration(s)
Sample command registrations, kept out of the engine so the engine stays a generic framework.
class ICoreCommandSamples {
public:
static void registerAll();
};
};
ICoreGitCommands.h#
src/ICoreSDK/ICoreCoder/ICoreCommandSystem/ICoreGitCommands.h
ICoreGitCommands#
ICoreGitCommands.h:23 · class · 1 declaration(s)
Exposes the Git panel's actions as console commands: gitInit, gitStatus, gitLog, gitCommit, gitPull, gitPush, gitFetch, gitRemote/gitSetRemote and gitRoot.
class ICoreGitCommands {
public:
static void registerAll();
};
};
ICoreLogSinkRegistry.h#
src/ICoreSDK/ICoreCoder/ICoreCommandSystem/ICoreLogSinkRegistry.h
ICoreLogSinkRegistry#
ICoreLogSinkRegistry.h:15 · class · pImpl · nested ActiveScope · 3 declaration(s)
Lets the console cls / clear / clearAllLogs commands reach the log panels they should wipe.
class ICoreLogSinkRegistry {
public:
using ClearFn = std::function<void()>;
static ICoreLogSinkRegistry* instance();
// `owner` is any stable pointer identifying the panel (typically `this`).
// Re-registering the same owner replaces its callback.
void registerSink(const void* owner, ClearFn clearFn);
void unregisterSink(const void* owner);
// RAII: mark `owner` as the active sink for the duration of a run, restoring
// the previously active sink on scope exit (so nested runs behave).
class ActiveScope {
public:
explicit ActiveScope(const void* owner);
~ActiveScope();
ActiveScope(const ActiveScope&) = delete;
ActiveScope& operator=(const ActiveScope&) = delete;
private:
class Impl;
std::unique_ptr<Impl> impl;
};
// Command handlers.
bool clearActive(); // clears the active sink; false if none is active/registered
int clearAll(); // clears every registered sink; returns how many were cleared
private:
class Impl; // the two-line residue; state lives here
std::unique_ptr<Impl> impl;
};
ICoreMathToolCommands.h#
src/ICoreSDK/ICoreCoder/ICoreCommandSystem/ICoreMathToolCommands.h
ICoreMathToolCommands#
ICoreMathToolCommands.h:13 · class · 1 declaration(s)
Console commands for the math tool windows' non-numeric affordances -- the things a panel shows in a combo box or a hint label rather than computes.
class ICoreMathToolCommands {
public:
static void registerAll();
};
};
ICoreModelConfigCommands.h#
src/ICoreSDK/ICoreCoder/ICoreCommandSystem/ICoreModelConfigCommands.h
ICoreModelConfigCommands#
ICoreModelConfigCommands.h:16 · class · 1 declaration(s)
Console bindings for the simulation/solver settings held by ICoreModelConfigurator.
class ICoreModelConfigCommands {
public:
static void registerAll();
};
};
ICoreModelConfiguratorCommands.h#
src/ICoreSDK/ICoreCoder/ICoreCommandSystem/ICoreModelConfiguratorCommands.h
ICoreModelConfiguratorCommands#
ICoreModelConfiguratorCommands.h:8 · class · 1 declaration(s)
Exposes every ICoreModelConfigurator setting (solver type, stepping, tolerances, time step bounds, time budget validation, ...) as a matching pair of set<Name>/get<Name> console commands, mirroring...
class ICoreModelConfiguratorCommands {
public:
static void registerAll();
};
};
ICoreNavigationCommands.h#
src/ICoreSDK/ICoreCoder/ICoreCommandSystem/ICoreNavigationCommands.h
ICoreNavigationCommands#
ICoreNavigationCommands.h:17 · class · 1 declaration(s)
Console bindings for the subsystem-navigation access setting held by ICoreSubsystemTreeNodeRegistry: navigationRootAccess print the setting and what it hides setNavigationRootAccess <on|off> switch...
class ICoreNavigationCommands {
public:
static void registerAll();
};
};
ICoreRecipeFileTransfer.h#
src/ICoreSDK/ICoreCoder/ICoreCommandSystem/ICoreRecipeFileTransfer.h
ICoreRecipeFileTransfer#
ICoreRecipeFileTransfer.h:19 · class · nested Result · 5 declaration(s)
Native recipe file transfer: save the CONTENTS of a diagram level as an .iscript script (ICoreRecipeSerializer's full-fidelity output — charts, decorations, corner routing and all, unlike the Simul...
class ICoreRecipeFileTransfer {
public:
struct Result {
bool ok = false;
ICoreString failureReason; // set when ok == false
ICoreStringList warnings; // per-line replay failures and skip notes
int blockCount = 0; // block/subsystem creations that landed
int linkCount = 0; // connect() statements that landed
};
// Serialize `node`'s contents and write them (with the source header) to
// `filePath` (".iscript" appended when missing).
static Result exportRecipe(ICoreSubsystemTreeNode* node, const ICoreString& filePath);
// Read `filePath`, retarget it at `node`, and replay it into the live
// diagram as ONE undo step.
static Result importRecipe(ICoreSubsystemTreeNode* node, const ICoreString& filePath);
// Same, for recipe text already in hand (a bundled template, a string built
// in memory) rather than a file. `recipeText` may carry the same source
// header an exported file does; it is read for the retarget and otherwise
// ignored.
//
// `offset` shifts the arriving objects by (dx, dy), +y up like move(), so a
// snippet dropped into a populated diagram can be placed clear of what is
// already there instead of landing on top of it. Only TOP-LEVEL objects
// move: a nested block is positioned against its own subsystem's origin
// anchor, and shifting those too would scatter the insides of every
// subsystem the snippet brings with it. Positions written by a chained
// statement (`block(Gain).move(...)`) are left alone -- the serializer
// never emits one, so this only affects hand-written recipes.
static Result importRecipeText(ICoreSubsystemTreeNode* node, const ICoreString& recipeText,
const ICorePoint& offset, const std::string& undoLabel);
// The retarget alone, without replaying: rewrites `recipeText`'s top-level
// creations to sit under `targetToken` and shifts their positions by
// `offset`, returning the rewritten script.
//
// `targetToken` is anything block()'s parent argument accepts — a level PATH,
// or a subsystem HANDLE bound EARLIER IN THE SAME SCRIPT. The handle form is
// the point of exposing this: a caller can emit `h = subsystem(...)` and then
// append a template body retargeted at `h`, so creating the container and
// filling it replay together as one statement stream, and therefore as one
// undo step. Retargeting against a node that has to exist first could not do
// that.
static ICoreString retargetRecipe(const ICoreString& recipeText, const ICoreString& targetToken,
const ICorePoint& offset = ICorePoint());
// The level an exported recipe came FROM, read off the "source:" header
// exportRecipe writes. Empty for text with no header (hand-written, or
// exported from Home, which leaves parents off entirely).
//
// Public because knowing which creations are TOP LEVEL means comparing their
// parent against this, and more than the retarget wants that — drawing a
// preview of a template has to skip the contents of the subsystems inside
// it. The header format stays spelled in exactly one place.
static ICoreString sourcePathFromHeader(const ICoreString& recipeText);
// The shared replay loop (also used by ICoreSimulinkBridge's import):
// recipe grammar first, plain `name = value` lines fall back to a
// global-variable declare, //-comments are skipped. Action logging is
// paused for the duration and `undoLabel` logged once, so the whole replay
// is a single undoable step. Per-line failures land in warnings; ok is
// always true (a partial replay still applied the rest).
static Result replayIntoDiagram(const ICoreString& recipeText, const std::string& undoLabel);
};
};
ICoreRecipeInterpreter.h#
src/ICoreSDK/ICoreCoder/ICoreCommandSystem/ICoreRecipeInterpreter.h
The one live-object handle that needs more than a raw pointer: a weak, self-nulling reference to a chart line path. It is a studio type because the self-nulling is Qt's (ICoreChartLinePath is a QObject); naming it here rather than QPointer is what keeps this header clear of Qt. Everything else this file needs from the UI side goes through ICoreRecipeStudioBridge, included by the .cpp.
ICoreRecipeInterpreter#
ICoreRecipeInterpreter.h:259 · class · pImpl · nested Result · 12 declaration(s)
Interprets the block-diagram "recipe" mini-language used to construct a diagram from a script.
class ICoreRecipeInterpreter {
public:
struct Result {
bool handled = false; // false => not a recipe line; caller falls through to its own grammar
bool ok = true; // false => recipe line failed (render as an error)
ICoreString output; // confirmation or error text for the history panel
};
static ICoreRecipeInterpreter* instance();
// Try to evaluate `line` as a recipe statement. handled==false means the line
// was not recipe syntax and the caller should continue with its own grammar.
Result evaluate(const ICoreString& line);
// Drop all handles. Called on project (re)load so pointers never dangle.
void reset();
// Read-only snapshots of the current handle bindings, sorted by handle name.
// For the pointer-inspector UI; callers should check the objects' isAlive().
ICoreList<std::pair<ICoreString, ICoreBlock*>> heldHandles() const;
ICoreList<std::pair<ICoreString, ICorePort*>> heldPortHandles() const;
ICoreList<std::pair<ICoreString, ICoreLinkBranch*>> heldBranchHandles() const;
ICoreList<std::pair<ICoreString, ICoreCanvasArea*>> heldAreaHandles() const;
ICoreList<std::pair<ICoreString, ICoreImage*>> heldImageHandles() const;
ICoreList<std::pair<ICoreString, ICoreCanvasTextBox*>> heldTextBoxHandles() const;
ICoreList<std::pair<ICoreString, ICoreCanvasSelectionModel*>> heldSelectionModelHandles() const;
// The property keys readable via handle.get()/handle.info, with a one-line
// description each, in display order. For the command glossary.
static ICoreList<std::pair<ICoreString, ICoreString>> blockPropertyGlossary();
// Resolve a diagram-level PATH token the way a recipe's parent argument
// does: '~' and ':' are app-root aliases, leading/trailing slashes are
// optional, and a missing app-name prefix is supplied. An empty token means
// Home. Returns null when no such level exists.
//
// Public because every console command taking a "which subsystem" argument
// (generateRecipe, useTemplate, ...) has to spell paths exactly the way the
// recipe grammar does, and one shared spelling is the only way that stays
// true. Handles are NOT accepted here — those belong to the interpreter's
// own bindings, which a command argument has no business reaching into.
static ICoreSubsystemTreeNode* resolveTreeNodeByPathToken(const ICoreString& token);
private:
class Impl; // the two-line residue; state lives here
std::unique_ptr<Impl> impl;
};
ICoreRecipeSerializer.h#
src/ICoreSDK/ICoreCoder/ICoreCommandSystem/ICoreRecipeSerializer.h
ICoreRecipeSerializer#
ICoreRecipeSerializer.h:76 · class · 2 declaration(s)
Inverse of ICoreRecipeInterpreter: walks a LIVE block diagram and emits a recipe script (see ICoreRecipeInterpreter's class comment for the language itself) that reconstructs it when the script is ...
class ICoreRecipeSerializer {
public:
// Builds the recipe script reproducing everything inside `root`. Returns
// an empty string for a null root. The result has no trailing newline;
// join with "\n" already applied between statements.
static ICoreString serialize(ICoreSubsystemTreeNode* root);
// Inverse of the ';' terminator serialize() appends: returns `line` trimmed,
// with one trailing ';' removed if present. Safe on hand-written recipe text
// (which has no terminator) and on comment/blank lines.
static ICoreString stripStatementTerminator(const ICoreString& line);
};
};
ICoreRecipeSnapshot.h#
src/ICoreSDK/ICoreCoder/ICoreCommandSystem/ICoreRecipeSnapshot.h
ICoreRecipeSnapshot#
ICoreRecipeSnapshot.h:17 · struct · 0 declaration(s)
One entry per live ICoreSubsystemTreeNode (keyed by getPath()) at the moment of capture, plus the one global-variables blob (Home-scoped, not per-node - see ICoreRecipeSerializer::serializeGlobalVa...
struct ICoreRecipeSnapshot {
public:
std::unordered_map<std::string, ICoreRecipeSerializer::LocalFragment> perNode;
ICoreString globalVariables;
};
};
ICoreRecipeToSimulinkEmitter.h#
src/ICoreSDK/ICoreCoder/ICoreCommandSystem/ICoreRecipeToSimulinkEmitter.h
ICoreRecipeToSimulinkEmitter#
ICoreRecipeToSimulinkEmitter.h:14 · class · pImpl · 7 declaration(s)
Accumulates a MATLAB/Simulink model-building script as a side effect of a recipe running for real against the live model.
class ICoreRecipeToSimulinkEmitter {
public:
ICoreRecipeToSimulinkEmitter();
~ICoreRecipeToSimulinkEmitter();
// Append one line of MATLAB source (e.g. an add_block(...) call) to the
// script.
void addLine(const ICoreString& line);
// A block() or subsystem() statement succeeded. blockType is the
// recipe's bare leaf type name (e.g. "Gain"), or "Subsystem" for a
// subsystem's own face block (mapped to Simulink's built-in Subsystem
// block - see simulinkLibraryPathForType). blockPath is the block's
// full path relative to the model root, slash-separated, e.g. "Gain1"
// at the top level or "Sub1/Gain1" nested one subsystem deep - the
// caller (ICoreRecipeInterpreter) is responsible for resolving nesting;
// this class only ever deals in already-resolved paths, never live
// tree-node pointers. Emits the model-opening boilerplate once, on the
// first call. A blockType with no known Simulink library equivalent
// produces a "%" comment instead of a broken add_block call.
void accumulateBlockCreated(const ICoreString& blockType, const ICoreString& blockPath);
// A block's position was (re)established - either the implicit
// placement every block()/subsystem() gets at creation, or an explicit
// move(). (x, y) is relative to the origin anchor, +y up - the same
// convention the recipe's move() takes; width/height are the block UI's
// pixel size, used to turn a point into Simulink's [left top right
// bottom] Position rect. A no-op if blockPath was never successfully
// added (accumulateBlockCreated skipped it for lack of a type mapping) -
// set_param on a block that was never add_block'd would be broken MATLAB.
void accumulateBlockMoved(const ICoreString& blockPath, double x, double y,
double width, double height);
// A plain port-to-port connect() statement succeeded (branching off an
// existing link is a later phase). tailBlockPath/headBlockPath are full
// model-relative block paths, same convention as accumulateBlockCreated;
// tailPortNumber/headPortNumber are 1-based (Simulink convention) output/
// input port numbers. Both blocks must live in the same subsystem (the
// interpreter already enforces this before calling), so their paths
// share the same containing system. A no-op if either endpoint's block
// was never successfully added.
void accumulateConnected(const ICoreString& tailBlockPath, int tailPortNumber,
const ICoreString& headBlockPath, int headPortNumber);
// A .setConfig() statement succeeded. blockType is the recipe's bare
// leaf type name; blockPath is the block's full model-relative path
// (same convention as accumulateBlockCreated); varName is the recipe's
// config variable name (e.g. "Gain Value"); value is the string it was
// set to. Produces a set_param(...) call if (blockType, varName) has a
// known Simulink parameter mapping, else a "%" comment - same skip
// convention as an unmapped block type. A silent no-op (not even a
// comment) if blockPath was never successfully added - the block itself
// is already fully explained by accumulateBlockCreated's own comment.
void accumulateConfigSet(const ICoreString& blockType, const ICoreString& blockPath,
const ICoreString& varName, const ICoreString& value);
// A recipe statement was recognized and succeeded against the live model,
// but has NO Simulink equivalent at all - plot(), selectionModel(), and
// the canvas decoration objects area()/image()/textbox() (see
// ICoreRecipeInterpreter's class comment). Unlike an unmapped block type
// or config key, there is no table to consult here - these statements
// can never gain a mapping, so this always emits a "%" comment.
// `description` is a short human-readable label for what was skipped,
// e.g. "area() in Sub1" or "plot()".
void accumulateUnsupportedStatement(const ICoreString& description);
// The generated script so far, one statement per line, no trailing
// newline. Empty if nothing has been accumulated.
ICoreString script() const;
// Drop everything accumulated so far.
void reset();
private:
class Impl; // the two-line residue; state lives here
std::unique_ptr<Impl> impl;
};
ICoreScriptRunner.h#
src/ICoreSDK/ICoreCoder/ICoreCommandSystem/ICoreScriptRunner.h
ICoreScriptRunner#
ICoreScriptRunner.h:11 · class · nested LineFailure · 8 declaration(s)
Headless runner for the saved command scripts under "<active project folder>/scripts".
class ICoreScriptRunner {
public:
// The custom script file extension (single source of truth).
static const ICoreString& scriptExtension();
// "<active project folder>/scripts", created if missing.
static std::filesystem::path scriptsFolderPath();
// Sorted (case-insensitive) list of script file names in the scripts folder.
static ICoreStringList listScripts();
// The script the Script Runner had open, remembered per project so that
// reopening the panel - in this session or a later one - lands back where
// the user left off instead of on whatever sorts first. Empty when there is
// none, or when the remembered file has since been deleted.
//
// Stored in "<scripts folder>/session.ini" rather than in the project's
// .iproj: it is UI session state, not diagram content, and putting it in
// the recipe would make selecting a script an undo step. listScripts()
// filters on scriptExtension(), so the ini never shows up as a script.
static ICoreString lastOpenedScript();
static void setLastOpenedScript(const ICoreString& fileName);
// Failure detail for the run* entry points below. `line` is 1-based;
// 0 means the script never ran a line (e.g. the file could not be read).
struct LineFailure {
int line = 0;
ICoreString text; // the failing command line, trimmed
ICoreString output; // what the console printed for it
};
// Run command lines handed in directly — the programmatic entry point for
// C++ callers that assemble their own command sequence, no file involved.
// Blank lines and '#' comments are skipped without failing. Stops at the
// first failing line and reports it through `failure` (when given).
//
// MUST be called on the GUI thread: it touches the variables space, command
// engine and notification center. Same for the two entry points below.
static bool runLines(const ICoreStringList& lines, LineFailure* failure = nullptr);
// Run one saved script from the scripts folder, by file name (as returned
// by listScripts()). A file that cannot be read counts as a failure.
static bool runScript(const ICoreString& fileName, LineFailure* failure = nullptr);
// Run every script in the folder, in name order. Stops the whole batch at
// the first failing line and posts a warning notification; otherwise posts
// a friendly summary. Returns true only if every line of every script
// succeeded (empty folder = true).
static bool runAllScripts();
};
};
ICoreMatlabCommandBridge.h#
src/ICoreSDK/ICoreCoder/ICoreCommandSystem/MatlabBridge/ICoreMatlabCommandBridge.h
ICoreMatlabCommandBridge#
ICoreMatlabCommandBridge.h:30 · class · nested Result · 2 declaration(s)
Facade over the console -> MATLAB command translation, the sibling of ICoreSimulinkBridge one directory up: that bridge carries the DIAGRAM recipe language to Simulink, this one carries the console...
class ICoreMatlabCommandBridge {
public:
struct Result {
bool ok = false;
ICoreString failureReason; // set when ok == false
ICoreStringList warnings; // user-facing skip notes, one per refused statement
int statementCount = 0; // statements seen
int translatedCount = 0; // statements that crossed
};
// Translate ONE console line (which may hold several ';'-separated
// statements) into MATLAB text. Returns an empty string when nothing on
// the line could cross; every refusal appends one warning. Helper names
// the translation used are collected into `usedHelpers` when given (the
// caller appends their bodies via ICoreMatlabCommandCatalog::helperBody).
static ICoreString translateLine(const ICoreString& line,
ICoreStringList& warnings,
std::set<std::string>* usedHelpers = nullptr);
// Whole console script -> standalone .m text: header, translated lines
// (blank lines and '#' comments carried across as blank lines and
// '%' comments), ICORE-UNSUPPORTED comments for refusals, used helper
// bodies appended as local functions.
static ICoreString translateScript(const ICoreStringList& lines, Result& result);
// translateScript + write to `filePath` (".m" appended when missing).
static Result exportToMatlabScript(const ICoreStringList& lines,
const ICoreString& filePath);
// Console commands: `toMatlab <console statement>` (print the translation)
// and `matlabExportScript <script name> <absolute .m path>` (export a
// saved console script). Called once from Initialization.
static void registerConsoleCommands();
};
};
ICoreMatlabCommandCatalog.h#
src/ICoreSDK/ICoreCoder/ICoreCommandSystem/MatlabBridge/ICoreMatlabCommandCatalog.h
ICoreMatlabCommandCatalog#
ICoreMatlabCommandCatalog.h:36 · class · nested Entry · 4 declaration(s)
The dictionary between the console math language (ICoreExpressionEvaluator's functions plus the operator layer) and MATLAB: one entry per console function, saying whether it can cross to MATLAB at ...
class ICoreMatlabCommandCatalog {
public:
enum class Support {
Yes, // translates via matlabTemplate (helpers included)
None, // no MATLAB equivalent; reported and skipped, see notes
};
// How a parity case over this function must compare the two sides.
enum class Compare {
Exact, // element-by-element |a-b| <= tolerance
SortedRows, // sort rows on both sides first (eigenvalue/root order
// is implementation-defined on both sides)
Skip, // translation is sound but the VALUE is not comparable
// (nondeterministic, or representation-unique); see notes
};
struct Entry {
const char* icoreName; // console function name ("inv")
Support support = Support::None;
// MATLAB expression with %1..%9 argument slots; "" when support is None.
const char* matlabTemplate = "";
// Name of the icp_* helper the template calls, or "" for none. The
// body comes from helperBody(); listing the name here is what lets the
// bridge know which bodies an exported script needs.
const char* helperName = "";
// Number of console arguments matlabTemplate expects. A call with a
// different count is refused with a warning naming the signature —
// except when it matches altArgCount below.
int argCount = 1;
// Second accepted form for optional-argument functions ("tf(num,den)"
// vs "tf(num,den,Ts)"). altArgCount 0 means there is none.
const char* altTemplate = "";
int altArgCount = 0;
Compare compare = Compare::Exact;
double tolerance = 1e-12;
const char* notes = ""; // why unsupported / mapping caveats, user-facing
};
static const std::vector<Entry>& entries();
// Exact name lookup, or nullptr (a name absent from the catalog is treated
// by callers exactly like Support::None).
static const Entry* findByName(const std::string& icoreName);
// The MATLAB function body for one icp_* helper name, newline-terminated,
// or "" for an unknown name. Bodies are complete `function ... end` blocks
// valid both as end-of-script local functions and as standalone .m files.
static std::string helperBody(const std::string& helperName);
// Every helper name, for the parity suite to emit as icp_*.m files.
static std::vector<std::string> helperNames();
};
};
ICoreSimulinkBlockCatalog.h#
src/ICoreSDK/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkBlockCatalog.h
ICoreSimulinkBlockCatalog#
ICoreSimulinkBlockCatalog.h:25 · class · nested EnumPair, ParamRule, Entry · 7 declaration(s)
The dictionary between the ICore block library and the Simulink standard library: one entry per registered ICore block type, saying whether it can cross to/from Simulink at all, which Simulink libr...
class ICoreSimulinkBlockCatalog {
public:
enum class Support {
Both, // exchanges in both directions
ExportOnly, // ICore -> Simulink only
ImportOnly, // Simulink -> ICore only
None, // no equivalent; reported and skipped
};
// How the Simulink-side "number of inputs" parameter is derived from the
// block's ICore input-port LIST (these are port-list edits in ICore, not
// config variables, so they can't ride through ParamRule). For the two sign
// kinds the parameter also carries each input's sign, which ICore keeps in
// the port's description label — so they depend on the port descriptions
// too, not the count alone.
enum class PortsParam {
None,
SumSigns, // Sum: Inputs = one sign per input port, "+" default
SubtractSigns, // Subtract: same, but a fresh block defaults to "+-"
DivideSigns, // Divide: Inputs = one "*" or "/" per input port, "*" default
MuxCount, // Mux: Inputs = input-port count as a number
ScopeNumInputs, // Scope: NumInputPorts, only when the count is > 1
// Math Function: the port count is not a parameter at all on either
// side. Simulink derives it from Operator (pow, hypot, rem and mod show
// a second input; the other eleven show one), so EXPORT writes nothing
// here - setting Operator is what moves the ports. Import is the
// direction that needs the rule: reading Operator has to widen the
// ICore port list to match, or the second add_line lands on a port that
// was never created. Unlike every kind above, this one does not consume
// its key - Operator is also a mapped config variable, so the reader
// sets the count and then lets the ParamRule translate the value.
MathFunctionOperator,
// Reshape: the same shape of rule as Math Function's, and for the same
// reason. Simulink derives the port count from OutputDimensionality -
// "Derive from reference input port" shows a second input carrying the
// shape to copy, the other four show one - so EXPORT writes nothing here
// and setting OutputDimensionality is what moves the ports. Import is
// the direction that needs the rule: without it the second add_line
// lands on a port that was never created. Like the Math Function kind it
// does not consume its key, because OutputDimensionality is also a
// mapped config variable whose value still has to be translated.
ReshapeOutputDimensionality,
// Trigonometric Function: the same shape of rule again, over a different
// operator list - atan2 is the ONE of the thirteen real operators that
// shows a second input port, and Simulink moves its own ports when
// Operator is set. Kept as its own kind rather than folded into
// MathFunctionOperator because the binary SET is what the rule actually
// is: sharing one kind between two blocks with different sets would have
// an imported Trigonometric Function consulting Math Function's
// {pow, hypot, rem, mod} and giving atan2 one port.
TrigFunctionOperator,
// MinMax: Inputs = input-port count as a number, exactly as MuxCount
// writes it - but floored at ONE rather than two. A separate kind
// because that floor is the whole difference and it is load-bearing:
// MinMax at one input is not a degenerate two-input block, it is the
// block's other mode, collapsing its single input over all elements to
// a scalar. Routed through MuxCount it would be exported as Inputs = 2
// and the reduction would silently become an elementwise comparison
// against a port that does not exist.
MinMaxCount,
// Matrix Concatenate: the same count-as-a-number rule again, over a
// parameter that is not called "Inputs". Its counterpart names it
// NumInputs, and set_param on a name a block does not define is a hard
// MATLAB error, so the name cannot simply be shared with MuxCount.
ConcatNumInputs,
// Identity Matrix: the same shape of rule as Reshape's and Math
// Function's - Simulink moves its own port when the parameter is set, so
// EXPORT writes nothing here and IMPORT is the direction that needs the
// rule - over InheritOutputPortAttributes rather than an operator list.
// Like those two it does not consume its key, because the parameter is
// also a mapped config variable whose value still has to be translated.
//
// IT IS THE ONE KIND WHOSE DEFAULT PORT COUNT IS ZERO. Identity Matrix is
// a SOURCE when the parameter is 'off' and grows a single input - whose
// DIMENSIONS the output copies, and whose values are never read - when it
// is 'on'. Every other kind here widens a block that already had at least
// one input, which is why ICoreSimulinkRecipeCodec's port-edit guard had
// to admit a zero default: at `defaultCount > 0` this kind's edits were
// skipped silently and an imported inheriting block came back with no
// input port at all.
IdentityInheritAttributes,
// Demux: Outputs = the OUTPUT-port count as a number. The first kind here
// that moves the output list rather than the input one, which is the
// whole reason it is not MuxCount with a different parameter name:
// clearPorts() clears whichever lists a block makes editable, and on
// Demux that is the outputs, so the codec's port-edit replay has to write
// addPort(out, ...) and consult the type's default OUTPUT count.
//
// Simulink also accepts a VECTOR here ("[2 3]"), which sets each output's
// width individually rather than splitting evenly. ICore's Demux derives
// its widths from the input, so only the COUNT crosses; the reader takes
// the vector's length and reports that the widths did not.
DemuxOutputs,
};
// One config variable <-> one Simulink parameter. An empty enumValues means
// the value text passes through unchanged (numbers and matrices — "[0 1;-1 -1]"
// is valid in both languages). A non-empty enumValues translates a combo-box
// value; export takes the first icore->simulink match, import the first
// simulink->icore match (so a many-to-one mapping round-trips to the first
// listed ICore value).
struct EnumPair {
const char* icoreValue;
const char* simulinkValue;
};
struct ParamRule {
const char* icoreConfig; // ICore config-variable name ("Gain Value")
const char* simulinkParam; // Simulink parameter name ("Gain")
std::vector<EnumPair> enumValues;
};
struct Entry {
const char* icoreFullType; // "Control_Systems/Base_Blocks/Gain"
const char* simulinkPath; // "simulink/Math Operations/Gain"; "" when support == None
Support support = Support::None;
PortsParam portsParam = PortsParam::None;
// ORDER IS SIGNIFICANT. The .m writer emits one set_param call carrying
// these pairs in the order listed here, and Simulink validates each pair
// as it is applied — so a parameter validated AGAINST another must come
// after it. Slider Gain is the case: `gain` is range-checked against
// `low`/`high` the moment it is set, and setting it first is a hard
// "Value ... is out of range" that aborts the whole generated script.
// Its entry therefore lists Minimum and Maximum before Gain. Every other
// entry's parameters are independent, so their order is just the order a
// reader finds most natural.
std::vector<ParamRule> params;
// Config variables that intentionally do NOT cross (ICore-side workflow,
// e.g. a discrete block's continuous source matrices). Anything neither
// mapped nor listed here raises a warning, so new config variables are
// never silently lost.
std::vector<const char*> ignoredParams;
const char* notes = ""; // why unsupported / mapping caveats, user-facing
// Simulink parameters this ICore block type always implies, with no
// config variable behind them because the block has no choice to offer:
// e.g. the Signal Recorder always produces a time series, so its Simulink
// counterpart is always 'SaveFormat','Timeseries'. Emitted on export, and
// on import a differing value is reported rather than silently accepted.
std::vector<std::pair<const char*, const char*>> fixedParams;
// Whether the Simulink counterpart actually HAS a SampleTime parameter.
// Almost all do, which is why the pair is mapped globally (see below) --
// but not all: Wrap To Zero, Coulomb & Viscous Friction, Rate Limiter and
// the Dynamic blocks define no such parameter, and `set_param` on a
// parameter a block does not define is a HARD ERROR in MATLAB, not a
// warning. An entry that sets this false keeps its rate on the ICore side
// instead of emitting a set_param that would break the whole script.
//
// What the writer then SAYS about it depends on the value, because the
// config exists on every block whether the user set it or not: a rate of
// <= 0 is ICore's "inherit", which a parameterless counterpart does
// anyway, so it is dropped in silence; a rate > 0 is a real setting that
// could not cross, and is reported both into the exchange report and into
// the generated .m itself (a `% ICORE-WARNING:` comment plus an executable
// `warning('ICore:SampleTime', ...)`).
//
// Kept last in the struct so existing aggregate initializers are unaffected.
bool hasSampleTimeParam = true;
// The Simulink parameter that carries the rate, for the counterparts that
// HAVE one but do not call it "SampleTime". Tapped Delay is the case: it is
// a masked S-Function whose rate parameter is `samptime`, so emitting the
// standard name would be the same hard set_param error hasSampleTimeParam
// exists to avoid -- but suppressing the rate entirely would throw away a
// mapping that genuinely works. Empty means the standard name; ignored
// when hasSampleTimeParam is false. Also kept last, for the same reason.
const char* sampleTimeParamName = "";
// The Simulink counterpart derives its PORTS from a SymbolSpec object
// (C Function, Python Code) instead of a port-count parameter: a fresh
// block has zero ports, so an add_line to it is a hard error. Entries
// that set this make the .m writer emit addSymbol statements deriving
// u1..uN inputs / y1..yM outputs from the ICore port list, and the
// reader fold the same statements back into port counts. SymbolSpec is
// not settable through set_param (the parameter is read-only) — only
// the live-object addSymbol form works, which is what the codec speaks.
// Also kept last, for the same aggregate-initializer reason.
bool symbolSpecPorts = false;
};
// Called from a block .cpp's static initializer (the same `registered`
// lambda that registers the solver environment and icon). The entry's
// icoreFullType must match the type the block registers with
// ICoreBlockFactory.
static void registerEntry(Entry entry);
static const std::vector<Entry>& entries();
// Exact full-type lookup, or nullptr.
static const Entry* findByICoreType(const std::string& fullType);
// Lookup by Simulink library path ("simulink/Sources/Step"), or nullptr.
static const Entry* findBySimulinkPath(const std::string& simulinkPath);
// ---- The per-block rate, handled globally ----
// EVERY ICore block carries a "Sampling Time (s)" config (created by
// ICoreBlockSolverEnvironment), and every Simulink block has SampleTime, with
// the same convention: <= 0 inherits, > 0 is an explicit period. So the pair is
// mapped here for all block types instead of being repeated in each entry —
// which is also why no entry lists it in params or ignoredParams.
static ICoreString icoreSamplingTimeConfigName(); // "Sampling Time (s)"
static const char* simulinkSampleTimeParamName(); // "SampleTime"
// Simulink writes SampleTime as a plain number, as "[period offset]", or as
// "inf". ICore stores a single double. Returns an empty string when the value
// has no single-period meaning (caller reports it and leaves the default).
static ICoreString simulinkSampleTimeToICore(const ICoreString& simulinkValue);
// Enum-value translation per the ParamRule convention above. Returns the
// input unchanged when the rule has no enum table; returns an empty string
// when the table exists but the value is not in it (caller reports it).
static ICoreString icoreToSimulinkParamValue(const ParamRule& rule, const ICoreString& icoreValue);
static ICoreString simulinkToICoreParamValue(const ParamRule& rule, const ICoreString& simulinkValue);
};
};
ICoreSimulinkBridge.h#
src/ICoreSDK/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkBridge.h
ICoreSimulinkBridge#
ICoreSimulinkBridge.h:26 · class · nested Result · 2 declaration(s)
Facade over the Simulink exchange pipeline.
class ICoreSimulinkBridge {
public:
struct Result {
bool ok = false;
ICoreString failureReason; // set when ok == false
ICoreStringList warnings; // user-facing notes about skipped content
int blockCount = 0; // blocks that made it across
int linkCount = 0; // connections that made it across
// Export only: the path the script was written to, which is NOT
// necessarily the one that was asked for — the stem is folded to a
// MATLAB identifier and ".m" is appended. Report THIS to the user, not
// the requested path, or the success message names a file that is not
// on disk.
ICoreString filePath;
};
// Export the CONTENTS of `node` (the diagram level currently on screen) as
// a Simulink model-construction script at `filePath`, whose LAST component
// is normalized first: the stem is folded to a MATLAB identifier (spaces
// and punctuation to '_', a leading non-letter prefixed with 'M') and ".m"
// is appended. A script MATLAB cannot name is a script MATLAB cannot run,
// and a subsystem's display name is under no obligation to be an
// identifier. Result::filePath carries what was actually written.
// Nested subsystems export recursively: each becomes a
// 'built-in/Subsystem' whose gate ports come out as Inport/Outport blocks.
static Result exportToSimulinkScript(ICoreSubsystemTreeNode* node, const ICoreString& filePath);
// Import the Simulink model script at `filePath` INTO `node` (the diagram
// level currently on screen). The whole import is captured as ONE undo
// step (action logging is paused during the replay, then logged once).
// Lines the interpreter refuses (e.g. a rename colliding with an existing
// block's name) fail individually into `warnings`; the rest of the model
// still lands.
static Result importFromSimulinkScript(ICoreSubsystemTreeNode* node, const ICoreString& filePath);
};
};
ICoreSimulinkExchangeModel.h#
src/ICoreSDK/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkExchangeModel.h
ICoreSimulinkExchangeBlock#
ICoreSimulinkExchangeModel.h:18 · struct · 4 declaration(s)
The neutral in-memory model both directions of the Simulink bridge meet at: recipe text -> ICoreSimulinkRecipeCodec::read -> [this] -> ICoreSimulinkMCodec::write -> .m text .m text -> ICoreSimulink...
struct ICoreSimulinkExchangeBlock {
public:
ICoreString handle; // recipe handle (unique across the script)
ICoreString name; // display name; defaults to the handle until a rename() is seen
ICoreString icoreType; // full ICore type path ("Control_Systems/Base_Blocks/Gain")
double x = 0, y = 0; // origin-anchored position, +y up (recipe move() coordinates)
double w = 70, h = 70; // block UI size (recipe resize() arguments)
// Port counts as replayed by the recipe (clearPorts()/addPort()). -1 means the
// recipe never edited that list (private/fixed list, e.g. Gain), so the block
// type's own default applies.
int inPortCount = -1;
int outPortCount = -1;
// Input-port description labels, indexed like the input port list and only
// as long as the labels that were actually seen (entries may be empty).
// Labels are presentation-only for most types, but on Sum they carry the
// per-input sign, which is the whole content of Simulink's `Inputs`.
ICoreStringList inPortDescs;
ICoreSortedMap<ICoreString, ICoreString> params; // ICore config-variable name -> raw value text
// A subsystem FACE: this block stands for a nested system, and childSystem
// indexes into the owning ICoreSimulinkExchangeSystem's `children`. The
// face is how parent-level links address the subsystem (its icoreType is
// the Subsystem type); the nested contents, gates included, live in the
// child system. -1 for ordinary blocks.
int childSystem = -1;
};
};
ICoreSimulinkExchangeLink#
ICoreSimulinkExchangeModel.h:43 · struct · 0 declaration(s)
struct ICoreSimulinkExchangeLink {
public:
int srcBlock = -1; // index into ICoreSimulinkExchangeSystem::blocks
int srcPort = 0; // 0-based OUTPUT port index on srcBlock
int dstBlock = -1;
int dstPort = 0; // 0-based INPUT port index on dstBlock
};
};
ICoreSimulinkExchangeSystem#
ICoreSimulinkExchangeModel.h:50 · struct · 1 declaration(s)
struct ICoreSimulinkExchangeSystem {
public:
ICoreString name; // model name ("Home", a subsystem name, ...)
ICoreList<ICoreSimulinkExchangeBlock> blocks;
ICoreList<ICoreSimulinkExchangeLink> links;
// Nested subsystems, each referenced by exactly one face block above
// (std::vector because it may hold its own incomplete element type).
// ICore gate blocks live in the child's `blocks` like any other block;
// the Simulink codec is what expands them to Inport/Outport blocks.
std::vector<ICoreSimulinkExchangeSystem> children;
// Global-variable declares ("k = 2") carried through verbatim: valid MATLAB
// and valid recipe alike, and block params may reference the names.
// Only meaningful on the root system.
ICoreStringList preludeAssignments;
};
};
ICoreSimulinkExchangeReport#
ICoreSimulinkExchangeModel.h:68 · struct · 0 declaration(s)
Shared by every bridge stage; the facade merges these into what the notification center shows.
struct ICoreSimulinkExchangeReport {
public:
bool ok = true;
ICoreString failureReason; // set when ok == false
ICoreStringList warnings;
};
};
ICoreSimulinkMCodec.h#
src/ICoreSDK/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkMCodec.h
ICoreSimulinkMCodec#
ICoreSimulinkMCodec.h:22 · class · 3 declaration(s)
The Simulink side of the bridge: translates between the exchange model and a MATLAB model-construction script built from the common Simulink commands — new_system / add_block / set_param / add_line...
class ICoreSimulinkMCodec {
public:
static ICoreString write(const ICoreSimulinkExchangeSystem& system,
ICoreSimulinkExchangeReport& report);
static ICoreSimulinkExchangeSystem read(const ICoreString& mText,
ICoreSimulinkExchangeReport& report);
// The recipe.m contract, for user-facing instructions (the import dialog):
// the statements read() APPLIES, one display line each ("add_block(source,
// target, 'Param', value, ...)").
static ICoreStringList supportedImportCommands();
// Statement names read() recognizes and deliberately IGNORES without a
// warning — session/console noise a generated script usually carries
// (save_system, close_system, sim, disp/fprintf prints, clc, ...). Both
// the call form ("disp(x)") and MATLAB's command form ("close all") are
// matched against these names.
static ICoreStringList ignoredImportCommands();
// `raw` folded to a valid MATLAB identifier: every character outside
// [A-Za-z0-9_] becomes '_', and a name that would not start with a letter
// gains an 'M'. This is what write() names the model, and it is also the
// only legal spelling for the SCRIPT FILE's stem — MATLAB reaches a script
// by its name, so `My Model.m` is a file it cannot run. Callers that choose
// a destination path pass the stem through here (see ICoreSimulinkBridge).
static ICoreString modelIdentifier(const ICoreString& raw);
};
};
ICoreSimulinkRecipeCodec.h#
src/ICoreSDK/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkRecipeCodec.h
ICoreSimulinkRecipeCodec#
ICoreSimulinkRecipeCodec.h:24 · class · 0 declaration(s)
The recipe-text side of the Simulink bridge.
class ICoreSimulinkRecipeCodec {
public:
static ICoreSimulinkExchangeSystem read(const ICoreString& recipeText,
ICoreSimulinkExchangeReport& report);
static ICoreString write(const ICoreSimulinkExchangeSystem& system,
const ICoreString& parentToken,
ICoreSimulinkExchangeReport& report);
};
};
ICoreTemplateCommands.h#
src/ICoreSDK/ICoreCoder/ICoreCommandSystem/TemplateLibrary/ICoreTemplateCommands.h
ICoreTemplateCommands#
ICoreTemplateCommands.h:19 · class · 1 declaration(s)
The template catalog (ICoreTemplateLibrary) on the console: templates [category|kind] list what is available useTemplate <id> [parent] [dx] [dy] insert a SNIPPET into a diagram level (default Home,...
class ICoreTemplateCommands {
public:
static void registerAll();
};
};
ICoreTemplateLibrary.h#
src/ICoreSDK/ICoreCoder/ICoreCommandSystem/TemplateLibrary/ICoreTemplateLibrary.h
ICoreTemplateLibrary#
ICoreTemplateLibrary.h:61 · class · nested Entry · 9 declaration(s)
The catalog of ready-to-use block diagrams: the ones shipped with the app and the ones a user drops in themselves.
class ICoreTemplateLibrary {
public:
// Subsystem inserts into an existing diagram; Example and Starter are both
// bundles and differ only in how they are presented - an Example is
// something to read, a Starter something to build on.
enum class Kind { Subsystem, Example, Starter };
struct Entry {
ICoreString id; // stable address: "subsystems/pi-controller", "user/examples/rig"
ICoreString title; // @title, defaulting to a prettified file stem
ICoreString category; // @category, defaulting to "Uncategorized"
ICoreString summary; // @summary; may be empty
Kind kind = Kind::Subsystem;
bool builtIn = true; // false => came from the user templates folder
ICoreString recipePath; // the .iscript (subsystem) or .iproj (bundle)
ICoreString bundleFolder; // bundles only: the folder to copy out
// True when there is a project FOLDER to copy out — which is what both
// callers actually need, and is NOT the same question as what @kind
// claims. `kind` comes from the file's own metadata header, and §3 of
// ADDING_NEW_TEMPLATES.md lets a single .iscript declare
// "@kind: example" (it is how a project template is iterated before it
// is moved into Templates/Examples/). Keyed off `kind`, such a file
// reported isBundle() == true with bundleFolder EMPTY, and
// materializeAsProject() then handed that empty path to
// copyFolderRecursively() — where ICoreDir("") resolves to the
// process's WORKING DIRECTORY and the copy succeeds. One openTemplate
// on such a template copied the whole repository, build trees included
// (9.5 GB), and only then failed on the rename.
//
// Keyed off the folder, a single-file "example" falls to the branch
// that is already correct for it: the recipe is written as the new
// project's .iproj.
[[nodiscard]] bool isBundle() const;
};
// Every template found, built-ins first, each kind in title order. The
// filesystem is rescanned on every call: the sets are tiny, and a cache
// would go stale the moment a user drops a file into the templates folder
// with the app already running.
static ICoreList<Entry> all();
// Look up one entry by its id. False (leaving `out` untouched) when there
// is no such template.
static bool find(const ICoreString& id, Entry& out);
// The categories present across the catalog, sorted, for grouping a listing.
static ICoreStringList categories();
// Insert a template into `parent`: creates a subsystem there, named after
// the template (uniquified against its siblings), positioned at `position`
// (+y up, like move()), and replays the template's contents inside it.
//
// Takes ANY entry, not just subsystem kind: a bundle's .iproj is the same
// recipe text an .iscript holds (solver settings travel beside it and are
// simply not part of an insert), so an example project drops into a diagram
// as one subsystem holding its whole Home. The catalog is one merged set,
// and every surface offers every template - what differs per kind is only
// the default gesture, not what is possible.
//
// Creating the container and filling it are ONE statement stream, so the
// whole insert is one undo step rather than a subsystem the user has to
// undo separately from its contents.
//
// `createdName` receives the name the subsystem actually got, which is not
// always the template's title: a level that already holds one of these gets
// a numbered sibling instead, and the caller wants to say which one it made.
static ICoreRecipeFileTransfer::Result insertAsSubsystem(
ICoreSubsystemTreeNode* parent, const Entry& entry,
const ICorePoint& position = ICorePoint(), ICoreString* createdName = nullptr);
// The inverse: write `node`'s contents to the user templates folder as a
// SUBSYSTEM template, so what a user built by hand becomes something they
// can insert again. `title` names it and, sanitized, becomes the file stem;
// `category` and `summary` are optional and only affect how it lists.
//
// Returns the file written, or an empty path with `err` filled. An existing
// template of the same stem is overwritten only when `overwrite` is set,
// which is what lets a caller ask first.
static std::filesystem::path saveSubsystemAsTemplate(
ICoreSubsystemTreeNode* node, const ICoreString& title, const ICoreString& category,
const ICoreString& summary, bool overwrite, ICoreString& err);
// True when saveSubsystemAsTemplate would refuse for want of `overwrite` -
// i.e. a user template with this title's stem already exists.
static bool userTemplateExists(const ICoreString& title);
// Turn ANY template into a real project at `destinationFolder`. A bundle is
// copied out whole, its .iproj renamed to match the folder
// (ICoreProjectSession::isValidProjectFolder is what requires the stem and
// the folder agree). A subsystem template has no folder to copy, so the
// project is MADE instead: its recipe text - retargeted to bare Home-form
// statements - becomes the new .iproj, and the diagram loads as the
// project's Home content. Returns the folder, or an empty path with `err`
// filled - notably when the destination already exists, which is never
// overwritten.
//
// The full destination is the caller's to choose, because the two callers
// disagree: the console puts a template beside the current project, while
// the New Project form puts it wherever the user pointed the location field.
//
// The caller opens it: ICoreProjectSession::switchToProjectFolder(parent,
// <returned path>, false).
static std::filesystem::path materializeAsProject(const Entry& entry,
const std::filesystem::path& destinationFolder,
ICoreString& err);
// "<application home>/Templates", holding Subsystems/ and Examples/ in the
// same layout as the built-in resource. The application home is used rather
// than the active project folder: templates outlive any one project.
// Created on first look so there is somewhere to drop a file.
static std::filesystem::path userTemplatesFolderPath();
// "<application home>/Templates/.builtins" — the on-disk mirror of the
// built-in resource tree (":/Templates"), wiped and rewritten from the
// binary at every startup by Initialization (the layer that owns Qt). The
// catalog reads built-ins from HERE and never from ":/" directly: the
// filesystem wrappers underneath the scan are std::filesystem since the
// Qt-free swap, and std::filesystem cannot see into the Qt resource
// system. Dot-named so a directory listing of the user templates folder
// (which skips hidden entries) never offers the mirror as user content.
static std::filesystem::path builtInTemplatesMirrorPath();
// Display name for a kind ("subsystem" / "example" / "starter").
static ICoreString kindName(Kind kind);
// The drag-and-drop format a library entry carries and the canvas accepts.
// Its payload is an Entry::id. Declared here, next to the ids it quotes, so
// the dragging end and the dropping end cannot drift apart -- the block
// library's own "application/x-syntra-block" is spelled out at both ends,
// which is exactly the arrangement this avoids.
static const char* dragMimeType();
};
};