boxes-backed State-Machine Visualizer¶
sysmlpy.boxes_view is an optional renderer that produces a
boxes Diagram from a parsed SysML v2
state def. It complements as_state_transition_view() (the PlantUML
renderer) by producing native UML shapes — rounded-corner «state» boxes,
filled-circle initial pseudostate, bullseye final state, orthogonal
port-to-port routing — without needing a Java runtime or a PlantUML server.
Install¶
boxes is an optional dependency. From the sysmlpy checkout:
or, if you have boxes on PyPI / a wheel:
import sysmlpy keeps working without boxes installed — the new
symbols are lazy-loaded on first access and raise an informative
ImportError if boxes is missing.
Public API¶
import sysmlpy
# Build an in-memory boxes.Diagram you can introspect or further decorate
d = sysmlpy.as_state_transition_view_boxes(model, focus=None)
# Render straight to the terminal (braille characters)
print(sysmlpy.render_state_transition_view(model, routing="orthogonal"))
# Render to an SVG string
svg = sysmlpy.render_state_transition_view_svg(model, routing="orthogonal")
open("state.svg", "w").write(svg)
model accepts raw SysML text, a sysmlpy.Model, or the dict from
sysmlpy.load_grammar(). focus chooses a specific state def by name
when multiple are present in the same source.
The pseudostate shape classes are re-exported so diagram-author code can use the state-machine vocabulary directly:
from sysmlpy.boxes_view import (
InitialPseudostate, JunctionPseudostate,
ChoicePseudostate, ForkPseudostate, JoinPseudostate,
FinalState, TerminatePseudostate,
HistoryPseudostate, EntryPoint, ExitPoint, StateNode,
)
What the adapter handles¶
The adapter walks the visitor dict and emits one round-cornered StateNode
per state X declaration, one InitialPseudostate (filled black circle)
for entry; then X;, and one FinalState (bullseye) the first time a
transition targets the reserved done name. Transitions are emitted as
edges labelled with Trigger [guard].
| SysML v2 construct | Adapter output |
|---|---|
state def X { … } |
one diagram per state def (use focus= to choose) |
entry; then X; |
InitialPseudostate → edge (no arrowhead) → first state |
state A; |
StateNode with «state» stereotype, rounded corners |
entry action warmup : WarmUp; / do monitor; / exit act; |
attributes entry / warmup, do / monitor, exit / act in the state box |
transition T first A accept Trig if guard do Effect then B; |
one edge A → B with label Trig [guard] |
accept X then Y; (shorthand succession) |
synthesized transition with trigger X, target Y, source back-filled from the most-recently declared state in the region |
transition first A accept X then done; |
edge to a synthesized FinalState bullseye (one per region, reused if multiple transitions hit done) |
transition first A accept X then S2.S3; |
full feature-chain resolution — endpoint correctly identified as the nested substate S2.S3 |
state Composite { state A; state B; transition first A accept X then B; } |
composite state emitted as a StateNode; substates emitted as namespace-qualified siblings (Composite.A, Composite.B). Recursive — supports arbitrary nesting depth |
state R parallel { state A; state B; } (isParallel=true) |
composite state carries both «state» and «parallel» stereotypes |
The SysML v2 pseudostate landscape¶
The UML / SysML 1.x menagerie of pseudostates — initial, final,
terminate, junction, choice, fork, join, shallow-history,
deep-history, entry-point, exit-point — was deliberately collapsed in
SysML v2. The formal spec (formal/26-03-02, Sept 2025, §7.18) keeps only:
- Initial — not a named shape; expressed as a succession from the
state's (possibly empty) entry action:
entry; then X; - Final — the reserved transition target
done. Spec §7.18: "a transition todoneindicates that the source state is the final state of the containing state performance." - Parallel — the
parallelkeyword on a StateDefBody marks orthogonal regions composition (no transitions allowed between concurrent substates) - Guarded choice — UML 1.x's choice and junction pseudostates are
replaced by
if guard then targetconditional successions
The following UML 1.x / SysML 1.x state pseudostates have no token in the lexer, no production in the grammar, and no class in the Ecore metamodel, and are correspondingly not emitted by the adapter:
| Removed pseudostate | What replaces it in SysML v2 |
|---|---|
| Junction | guarded transitions |
| Choice | if guard then target conditional successions |
| Shallow history | (none — not in the language) |
| Deep history | (none — not in the language) |
| Entry point | (none — composite states are entered directly) |
| Exit point | (none — composite states are exited directly) |
| Terminate | done (final) covers the common case |
| Fork / Join | moved to action flow (fork-node, join-node in §8.2.2.14.1), not state machines |
The boxes package still ships first-class JunctionPseudostate,
ChoicePseudostate, ForkPseudostate, JoinPseudostate,
HistoryPseudostate, EntryPoint, ExitPoint, TerminatePseudostate
classes so you can build SysML 1.x / UML-style diagrams by hand when you
need them — they just aren't emitted by the SysML v2 adapter because the
source language has no equivalent input.
Example: the OMG StateTest.sysml state machine¶
import sysmlpy
text = """state def S {
entry; then S1;
state S1;
accept s : Sig then S2;
state S2 { state S3; }
accept Exit then done;
transition
first S1
accept s : Sig
then S2.S3;
}"""
print(sysmlpy.render_state_transition_view(text, routing="orthogonal"))
The output is a braille-character diagram showing:
- a filled black circle (initial pseudostate)
- a
«state» S1box →«state» S2(with shorthandaccept s : Sigedge) - a
«state» S3substate (sibling of S2 visually; nesting is a future layout enhancement) - a
«state» S1→«state» S3edge for the explicittransition first S1 … then S2.S3 - an
Exitedge from S1 (the most-recent declared state at that point in the region) to a bullseye final state
Use render_state_transition_view_svg(...) to produce a vector SVG
instead, suitable for embedding in docs or editing by hand.
Limitations / future work¶
- Composite nesting is currently visual-sibling, not visuallyenclosed
— boxes lays out substates as top-level nodes with
namespace-qualified names (
Composite.A). True UML "substates inside the parent box" rendering builds on theView.childreninfrastructure inboxesand is a focused future enhancement. - Edge labels are best-effort — complex guard expressions other than
a single
QualifiedNameare currently dropped to keep the diagram readable. Trivial to extend if needed. state usage(top-levelstate x :> StateDef parallel { … }) — parsed fine, but the adapter currently only descends intoStateDefinitionbodies. A small extension handlesStateUsagetop-level — beyond what the typical state-machine test fixture exercises.