AgentStack
SKILL verified MIT Self-run

Annotate Modelica Graphics

skill-wolframresearch-system-modeler-ai-toolkit-annotate-modelica-graphics · by WolframResearch

Add Modelica graphical annotations (Icon + Diagram) to a text-only .mo model so it renders as a clean, laid-out schematic in Wolfram System Modeler. Use this skill whenever a model has no icons or diagram, or the user asks to add graphics, draw icons, lay out the diagram, place components, or make a model look right when opened in System Modeler. Triggers on phrases like 'add an icon to this mode…

No reviews yet
0 installs
6 views
0.0% view→install

Install

$ agentstack add skill-wolframresearch-system-modeler-ai-toolkit-annotate-modelica-graphics

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No 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.

Are you the author of Annotate Modelica Graphics? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

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:

  1. Read its description, its connectors, and (if helpful) its equations to decide what it is

and what it should look like.

  1. Express that as Modelica graphic primitivesRectangle, 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 into Icon(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 — optional connector → "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; or PNP 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:

  1. $WSM_SKILLS_SCRIPTS (bash) or $env:WSM_SKILLS_SCRIPTS (PowerShell), if set.
  2. ../scripts relative 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.

  1. The repo checkout you installed from, e.g. .../agentskills/scripts.
  2. 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 .mo file, or you may already be working with

one in context.

  • Extract the model name: the identifier after model on the first non-comment

line, e.g. model FooBarFooBar. 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.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.