Install
$ agentstack add skill-wolframresearch-system-modeler-ai-toolkit-annotate-modelica-graphics ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Annotate Modelica Graphics
This skill adds graphical annotations to a text-only Modelica .mo file so it renders as a clean schematic in Wolfram System Modeler. It is a self-contained source transform — it parses the .mo, classifies each class, and splices annotations back into the source:
- Category classes (packages, runnable examples, records, functions) get the idiomatic
extends Modelica.Icons.*; base — the same icons the Modelica Standard Library uses.
- Leaf components / sub-circuit building blocks get a custom
Icon(graphics=…)with their
connectors anchored on the icon boundary. This works for any domain — connector detection spans every Modelica Standard Library domain (electrical, mechanical, rotational/translational, MultiBody, control signals, thermal/heat, fluid, magnetic, digital, …) plus any connector class defined in the file itself. Recognized component kinds get a hand-drawn glyph (transistor, amplifier, tank, pump, valve, pipe, heat capacitor); anything else gets a generic block placeholder that you are expected to replace with an icon you draw from the component's name and description (see step 3b — this is a normal part of the workflow, not an edge case).
- Connectors get their own domain-colored square icon. When the domain is recognized the
color is automatic; when it isn't, you author the symbol the same way as for components.
- Composite models get an auto-laid-out Diagram: each component instance gets a
Placement, and every connect(…) gets an orthogonal, domain-colored connection Line.
It is idempotent: re-running only fills in what is missing (use --force to regenerate). By default it prints a dry-run diff; nothing is written until you pass --write.
> --force is destructive — ask the user first. It strips every Placement, connection > Line, Icon(...), Diagram(...), and extends Modelica.Icons.* in scope and regenerates them > from scratch. The tool leaves no marker, so it matches by shape, not provenance: hand-written > and hand-tuned annotations are deleted too (custom placements, manual line routing, bespoke icon > graphics). Do not pass --force on a model that may carry hand-authored graphics without first > confirming with the user that discarding it is intended. Review the dry-run diff (no --write) > to see exactly what would be removed before applying.
The engine is pure Python (standard library only) — no third-party packages or WSMKernelX are needed to generate the annotations. WSMKernelX is used only afterwards, as a safety gate, to confirm the edited file still flattens.
Running the annotator
The annotator lives in the Schematic/ package next to this file. Run it as a module from this skill's directory so the package is importable:
cd ""
python3 -m Schematic.main --file "" --analyze
` is the folder containing this SKILL.md (use python instead of python3 on Windows if that's what's on PATH). The only argument that must be an absolute or correctly-relative path is --file`.
Cross-platform launcher (for the validation gate in step 5)
> The validation gate in step 5 uses the shared launcher scripts/wsm_run.py. > Read [the shared-conventions appendix at the end of this file](#appendix-shared-conventions-for-the-modelica-skills) (launcher resolution, shell > rules, temp dir, JSON-array output, MSL 4.x notes) before that step. In a normal > install the launcher is ../scripts/wsm_run.py (` is the shared > scripts/` folder); if that path doesn't exist, see > [Appendix → Locating the launcher](#locating-the-launcher).
Workflow
1. Identify the model file
The user gives a .mo path, or you are already working with one. The file may be a package with several nested models — the tool handles all of them in one pass.
2. Analyze — see what each class will receive
python3 -m Schematic.main --file "" --analyze
This prints every class with its category and what it would get (a standard Modelica.Icons.* icon, a custom icon, and/or a diagram layout), plus its connectors / instances / connects. Present this to the user and confirm the scope. By default all non-trivial classes are annotated; restrict with --class if the user wants just one.
Read the glyph: line under each leaf and connector. Recognized ones name a built-in glyph or a domain color. Unrecognized ones say domain unrecognized — author … and are collected in an Unrecognized classes/connectors list at the end. Treat that list as a work item: you must author a glyph for each entry in step 3b before applying. Do not ship the generic-block / neutral-square placeholder when you have a name and description to draw from.
3. Preview — dry-run diff
python3 -m Schematic.main --file "" --annotate
Shows the unified diff without writing. Summarize what will be added (icons, placements, connection lines). Useful flags:
--class— only that nested class.--no-glyphs— plain rounded-rectangle icons instead of typed glyphs.--extent N— force the diagram coordinate system to{{-N,-N},{N,N}}.--force— strip all graphical annotations (including hand-written ones) and regenerate;
destructive, so confirm with the user first (see the caution above).
--glyphs-file— supply your own icon glyphs for named classes (see step 3b).
3b. Author glyphs for unrecognized classes and connectors (required when the list is non-empty)
The tool recognizes every Modelica Standard Library domain and draws those automatically. For anything it can't recognize — a custom component or a connector in a domain outside the MSL — it emits a placeholder (generic block / neutral square) and lists the class in Unrecognized classes/connectors. For each listed name, you (the LLM running this skill) draw a real icon from the component's name and description. This is expected, not exceptional: the engine deterministically handles layout, placement, colors and recognized glyphs; you supply domain knowledge for the long tail.
For each unrecognized entry:
- Read its
description, its connectors, and (if helpful) its equations to decide what it is
and what it should look like.
- Express that as Modelica graphic primitives —
Rectangle,Ellipse,Line,Polygon,
Text, etc. — in the {{-100,-100},{100,100}} icon frame. A component gets a representative symbol (leave room where pins sit); a connector typically gets a single filled Rectangle/Ellipse spanning the frame in a sensible domain color.
Write a JSON file mapping each class name to its glyph and (optionally) which edge each connector sits on, then pass it with --glyphs-file:
{
"Membrane": {
"graphics": [
"Rectangle(extent={{-40,-90},{40,90}}, lineColor={90,90,90}, fillColor={210,225,235}, fillPattern=FillPattern.Solid)",
"Line(points={{0,90},{0,-90}}, color={90,90,90}, pattern=LinePattern.Dash)"
],
"ports": {"feed": "L", "permeate": "R"}
}
}
graphics— a list of primitive strings spliced verbatim intoIcon(graphics={…}). A%name
label is added automatically (set "name_text": false to suppress it). Keep shapes within ±100 and put nothing where a connector pin will sit.
ports— optionalconnector → "L"|"R"|"T"|"B"map. Omitted ports fall back to the
name-based heuristic. The tool computes the exact pin coordinates on that edge. (Irrelevant for a connector class itself, which has no sub-connectors — give it graphics only.)
- A bare list value (
"Membrane": ["Rectangle(...)", …]) is accepted as graphics-only.
The JSON can target both components and connectors in one file — key every unrecognized name from the analyze list. Example connector entry: "MolarPort": {"graphics": ["Ellipse(extent= {{-100,-100},{100,100}}, lineColor={0,140,90}, fillColor={120,220,170}, fillPattern= FillPattern.Solid)"]}. Then preview/apply as usual with the same --glyphs-file argument; the connectors are still placed and (when instantiated) routed automatically.
4. Apply — write in place
python3 -m Schematic.main --file "" --annotate --write
Re-running without --force is a no-op for already-annotated classes (idempotent).
5. Validate — confirm the model still flattens (use the validate-modelica gate)
Annotations must not change the flatten result. Confirm with the shared launcher (this is the validate-modelica skill's gate). Target a concrete instantiable model, not the package:
python3 "/wsm_run.py" --mode validate \
--model "" --name "." --timeout 90
Parse _wsm_validate_temp/validate.out.json (a JSON array — take the first element; field reference: [Appendix → Reading the JSON output](#reading-the-json-output)) and check status.flatten == "Pass". A Pass confirms the edits didn't corrupt the source. If it Fails after annotating but passed before, report it — that's a bug, not the user's model. Then remove the temp dir (rm -rf "/_wsm_validate_temp").
Notes and edge cases
- Pick an instantiable model for the gate, not the package (e.g.
OTAlib.VCA) — see
[Appendix → Picking the model name](#picking-the-model-name).
- Multi-name declarations (
Pin b, c, e;orPNP Q3, Q4;) are split into one declaration
per component so each gets its own placement. This is a structural rewrite but semantically identical; it flattens to the same model.
- Layout is heuristic, not pixel-perfect. Components are laid out left-to-right by signal
flow (feedback loops handled), grounds pinned to the bottom and supplies to the top, with orthogonal connection routing between connector pins — each line terminates exactly on the pin anchor (instance origin + the connector's icon-edge offset) and leaves it perpendicular to that edge, so the rendered line is right-angled end to end with no diagonal bridge from the pin; interior waypoints snap to a grid. Pin offsets come from the model's own leaf/sub-circuit icons and a heuristic table for standard-library components (resistors, sources, ground, blocks). Library components render with their own MSL icons; only domain leaves without an MSL equivalent get a custom icon. The user can fine-tune positions afterward in System Modeler.
- Two-terminal components are rotated to avoid wrap-around. A component with two pins on
opposite left/right edges (resistor, capacitor, inductor, …) is stood up vertically (pins top/bottom) when both its neighbours sit on the same horizontal side or are separated more vertically than horizontally — so the two connection lines fan out instead of one wrapping around the body. Sources, grounds and supplies keep their orientation. Known limitation: a connection to a class connector that is inherited (declared in an extended base, not in this class) is emitted as a harmless zero-length stub, since the inherited connector has no diagram placement to anchor to.
- Idempotency / re-layout. Default runs never duplicate annotations. To re-generate (e.g.
after editing the model's connections), pass --force — but it discards all existing graphics, hand-written included, so confirm with the user first (see the caution near the top).
- MSL dialect (this toolchain ships MSL 4.x). Annotations don't affect flattening, but if the
model uses 3.2 names it will fail the gate for unrelated reasons (Modelica.Units.SI.*, source param f, uses(Modelica(version="4.0.0"))) — see [Appendix → MSL 4.x dialect](#msl-4x-dialect).
Appendix: shared conventions for the Modelica skills
> Shared by every Modelica skill that drives WSMKernelX through the > bundled launcher; inlined here at release time. For the CLI/option > reference, environment variables (WSM_HOME, WSM_VSDEVCMD), > install discovery, and the analysis scripts, see > [../scripts/README.md](../scripts/README.md).
Locating the launcher
` (used throughout the skills) is the shared scripts/` folder. Some installs symlink the skill directories without it, so resolve it in this order and use the first that exists:
$WSM_SKILLS_SCRIPTS(bash) or$env:WSM_SKILLS_SCRIPTS(PowerShell), if set.../scriptsrelative to the skill directory — in a normal install
../scripts/wsm_run.py already exists, so use that path directly; do not run a shell probe to "resolve" it.
- The repo checkout you installed from, e.g.
.../agentskills/scripts. - Last resort, search the home directory:
- PowerShell:
Get-ChildItem $HOME -Recurse -Filter wsm_run.py -ErrorAction SilentlyContinue | Select-Object -First 1 - bash/zsh:
find ~ -name wsm_run.py -path '*scripts*' 2>/dev/null | head -1
If only #4 finds it, the install is missing the scripts/ link — tell the user to run install.sh (or install.ps1) from the repo, which links scripts/ too.
Shell and Python
On Windows, use PowerShell. The Git-Bash/cygwin layer may be broken (even ls/find can be absent, giving a misleading "exit 127 / command not found"). Run wsm_run.py with python (not python3); those calls are single-line and shell-agnostic. For cleanup use Remove-Item -Recurse -Force, not rm -rf. On macOS/Linux any POSIX shell is fine and python3 is the usual name.
Let the launcher own .mos/.bat and paths
Do not hand-write .mos scripts, .bat files, or hardcode install/compiler paths. The bundled scripts/wsm_run.py handles every OS difference — it finds the System Modeler install and kernel binary (macOS / Windows / Linux), finds and loads the right MSL files, generates the .mos, and runs the kernel with a working compiler environment per platform (system clang/gcc on macOS/Linux; the Visual Studio dev environment via VsDevCmd.bat on Windows). See [../scripts/README.md](../scripts/README.md) for WSM_HOME, the Windows compiler prerequisites, and the full option table.
When the install or compiler isn't found
The launcher searches each OS's standard install locations. If it prints ERROR: Could not locate a Wolfram System Modeler installation, the install is in a non-standard place — ask the user for it and re-run with --wsm-home "" (or have them set WSM_HOME).
Building and simulating also need a C++ toolchain:
- Windows: Visual Studio Build Tools. The launcher locates
VsDevCmd.bat
itself; if it reports the compiler environment is missing, pass --vsdevcmd "" (or set WSM_VSDEVCMD) and make sure Build Tools are installed.
- macOS: the Xcode command-line tools (
xcode-select --install). - Linux: gcc/g++.
Run python3 "/wsm_run.py" --mode info to see what the launcher discovered.
Temporary directories
The launcher works in a _wsm__temp/ directory next to the .mo file (_wsm_validate_temp/, _wsm_simulate_temp/, _wsm_diagnose_temp/) and leaves its outputs there. Tell the user, e.g.: "Working in temporary directory _wsm__temp/. This will be deleted afterwards." Pass --tempdir to reuse one directory across models in a session.
Clean up by removing the whole directory — use the user's shell:
rm -rf "/_wsm__temp" # macOS / Linux
# PowerShell: Remove-Item -Recurse -Force "\_wsm__temp"
Picking the model name
- The user may provide a path to a
.mofile, or you may already be working with
one in context.
- Extract the model name: the identifier after
modelon the first non-comment
line, e.g. model FooBar → FooBar. The filename does not always match the model name — parse the actual model/package declaration.
- For packages or nested models, use the top-level model name.
- Pick an instantiable model, not a package, for any kernel call. A
package
cannot be validated or simulated ("Invalid instantiation … is a package") — use a nested model's full dotted name, e.g. Package.Model.
- Pass an absolute path
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: WolframResearch
- Source: WolframResearch/system-modeler-ai-toolkit
- License: MIT
- Homepage: https://www.wolfram.com/system-modeler/
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.