Widgets¶
longeron.widgets is the way into the house widgets. The catalog
below re-exports the canonical entry point of every interactive
front-end, so one import surface covers them all:
from longeron.widgets import explore, mission_dashboard
The catalog is lazy (PEP 562). import longeron.widgets loads no
widget toolkit, and each entry imports its home module on first
access. If an entry’s extra is missing, the entry raises
MissingExtraError with the exact install
command when you reach for it.
The package is also the shared toolkit for widget authors and the
mandatory home for every new widget. New widgets land here as
submodules, beside the resident homes (longeron.widgets.explorer,
longeron.widgets.app, longeron.widgets.inspector,
longeron.widgets.replay, longeron.widgets.viewer3d,
longeron.widgets.mission3d, longeron.widgets.graph3d,
longeron.widgets.time). The pre-0.12 homes
(longeron.explorer, longeron.inspector, longeron.app,
longeron.analysis.viewer3d, plus replay_widget on
longeron.replay and mission_viewer on
longeron.analysis.mission3d) remain importable as deprecated aliases
that warn once and will be removed in a future release.
The catalog¶
Entry |
What it is |
Extras |
Taught in |
|---|---|---|---|
|
The model explorer: a tree navigator beside a diagram pane. |
|
|
|
The explorer widget class that |
|
|
|
The explorer’s tree engine: disclosure rows, kind badges, live filter. |
|
|
|
The review workbench class that |
|
|
|
The review workbench: model list, explorer tabs, item inspector. |
|
|
|
The property sheet; |
|
|
|
The diagram dispatcher: picks the view from the element’s kind. |
vendored ipyelk |
|
|
Parts, ports, and connections as an interactive ELK diagram. |
vendored ipyelk |
|
|
A state machine as an interactive ELK diagram. |
vendored ipyelk |
|
|
An action’s control flow as an interactive ELK diagram. |
vendored ipyelk |
Tutorial 3 (via |
|
Simulate an element and replay the run over its diagram. |
|
|
|
The MAUT requirements scoreboard: area is importance, color is utility. |
|
|
|
The linked mission-compromise dashboard. |
|
|
|
The grand tour: diagram, CAD, scoreboard, sizing, consistency, and the mission globe on one surface. |
|
|
|
Baked geometry meshes in a three.js canvas, at true scale. |
|
|
|
Fly a mission track on a Cesium globe. |
|
|
|
The RDF projection as an interactive 3D force graph. |
|
|
|
The shared playhead for one linked group of time-aware views. |
none |
|
|
One recording, many views: a trace plus its optional mission binding. |
none |
|
|
Wire time-aware views to one clock: the temporal |
none |
|
|
The standalone transport bar: play/pause, rate, the shared time axis. |
|
The pip extras install as pip install "longeron[replay,viz]" (or any
subset). The vendored ipyelk installs as pip install -e vendor/ipyelk
from a repo checkout. replay_widget also needs a node executable on
PATH for the baked SVG.
The widget layer: longeron’s interactive front-ends, in one place.
This package has three jobs. It is the shared toolkit for widget
authors (the anywidget conventions the house front-ends follow: baked
JSON traitlets, kernel-side computation, on-demand rendering). It is
the mandatory home for every new widget. And it is the catalog of the
house widgets: every canonical entry point, re-exported under one roof,
so longeron.widgets is the one import to learn.
The catalog is lazy (PEP 562). import longeron.widgets loads no
widget toolkit; each entry imports its home module on first attribute
access. If an entry’s extra is missing, the access (or the call, for
homes that guard at call time) raises
MissingExtraError with the exact install
command.
Loss tolerance (the sync discipline every widget follows). Comm messages are fire-and-forget: under load the channel drops them (jupyter-server’s iopub rate limiter, websocket reconnects mid-burst), and trait sync only sends CHANGES, so a dropped update stays wrong forever unless the widget’s protocol heals it. The kernel is the source of truth; front-ends reconcile. Three tiers, matched to the state a widget mirrors:
Baked idempotent payloads (every widget): kernel -> front-end state rides absolute JSON traitlets (
spec_json,czml_json,timeline_json…), never deltas, so any later push heals any earlier drop. This is the house convention already – keep it.Single-shot interaction traits (picks, selections, splitter ratios, tool toggles): a drop loses one gesture and the user’s retry is the retransmit; the kernel-side handler must therefore be idempotent and order-independent. No extra machinery.
Live bidirectional seams (the time seam’s
time/playing/rate: high-rate reports racing kernel seeks): generation stamps + acknowledged reports + full-state re-pushes + a trailing-edge verify, vialongeron.widgets._seam(kernel mixinSeamHost, front-endlgnSeam). A new widget with kernel-mirrored state that either side can write while the other is also writing MUST ride this seam client; see the module’s docstring for the protocol and the CI anatomy that mandated it.
The entries (tutorial numbers refer to Tutorials):
explore– the model explorer: a tree navigator beside a diagram pane (tutorial 3).Explorer– the explorer widget class thatexplorebuilds (tutorial 3).ModelTree– the explorer’s tree engine: disclosure rows, kind badges, live filter (tutorial 3).ModelApp– the review workbench class thatopenbuilds (tutorial 3).open– the review workbench: model list, explorer tabs, item inspector (tutorial 3).Inspector– the property sheet;openbuilds one asapp.inspector(tutorial 3).diagram– the diagram dispatcher: picks the view from the element’s kind (tutorial 3).structure_diagram– parts, ports, and connections as an interactive ELK diagram (tutorial 4).state_diagram– a state machine as an interactive ELK diagram (tutorial 2).action_diagram– an action’s control flow as an interactive ELK diagram (tutorial 3 reaches it throughdiagram).replay_widget– simulate an element and replay the run over its diagram (the replay reference).scoreboard– the MAUT requirements scoreboard: area is importance, color is utility (tutorial 6).mission_dashboard– the linked mission-compromise dashboard (tutorial 4).grand_dashboard– the grand tour: diagram, CAD, scoreboard, sizing, consistency, and the mission globe on one surface (tutorial 9).mesh_viewer– baked geometry meshes in a three.js canvas, at true scale (tutorial 4).mission_viewer– fly a mission track on a Cesium globe (tutorial 7).graph_viewer– the RDF projection as an interactive 3D force graph (tutorial 8).Clock– the shared playhead for one linked group of time-aware views (the time-seam reference).Timebase– one recording, many views: a trace plus its optional mission binding, aligned on one axis (the time-seam reference).link_time– wire time-aware views to one clock, the temporallink_selection(the time-seam reference).time_scrubber– the standalone transport bar: play/pause, rate, the shared time axis (the time-seam reference).
Current resident modules:
longeron.widgets.app– the review workbench (ModelApp,open) and its Lab docking (replayextra).longeron.widgets.explorer– the model explorer: tree engine, diagram pane, Lab docking (replayextra).longeron.widgets.graph3d– the 3D graph widget’s home (rdf+vizextras).longeron.widgets.inspector– the item property sheet (replayextra).longeron.widgets.mission3d– the Cesium mission viewer (vizextra); its track/CZML synthesis stays inlongeron.analysis.mission3d.longeron.widgets.replay– the diagram replay widget (replayextra); the timeline recorders stay inlongeron.replay.longeron.widgets.time– the time seam’s home:Clock,Timebase,link_time, and the scrubber (replayextra for the scrubber only).longeron.widgets.viewer3d– the three.js mesh viewer (vizextra).
The pre-0.12 homes (longeron.explorer, longeron.inspector,
longeron.app, longeron.analysis.viewer3d, plus
replay_widget on longeron.replay and mission_viewer on
longeron.analysis.mission3d) remain importable as deprecated
aliases that warn once and will be removed in a future release.
- class longeron.widgets.Clock(span=(0.0, 0.0), *, step_mode=False, rate=1.0, t=None)[source]¶
Bases:
objectThe shared playhead for one linked group of views.
tis the playhead in axis units (sim seconds, or the step index whenstep_mode),playingsays someone is animating,rateis axis units per wall second (1.0 = real time; negative plays backwards, as Cesium’s shuttle ring does), andspanis the(t0, t1)window seeks clamp into. The clock owns no wall-clock timer: views animate, the clock holds state and fans it out through plain callbacks, so the core package stays dependency-free.The no-echo discipline is the selection seam’s, restated for floats: a
seek()within1e-3of the currenttdoes not fan out,playing/ratecoalesce on equality, and every subscriber applies the same rule before writing back, so each write settles at its first fixpoint. Linking is explicit and scoped (link_time()); two dashboards in one notebook keep two clocks.
- class longeron.widgets.Explorer(model, *, tree=None, layout='auto', mode='tab-after', structure_scope='package', height='600px')[source]¶
Bases:
HBoxTree navigator (left) + applicable-kind diagram pane (right).
Build one with
explore(). The public knobs:tree– the tree engine, anyTreeView(defaultModelTree; itsselected/querytraits are the headless automation surface);kind_switcher– the toggle buttons offering the applicable diagram kinds for the current selection;diagram– the currently displayed diagram widget;select()– programmatic selection by qualified name or element;save_view()(and the header’ssave_button) – save the current diagram as a SysML v2 view usage plus sidecar entry (longeron.views);layout_strategy– the resolved layout ("inline"or"lab"; seeexplore());dock_mode– how thelablayout docks into the shell (default"tab-after"; seeexplore()).
The panes are built ONCE; the layout strategy only composes them:
inlineputs them side by side in this HBox (28%/72%),labdocks them as a resizable JupyterLab split panel (lab_panel) and leaves a small placeholder in the cell output.Diagrams are cached per (scope, kind): re-selecting inside the same package reuses the SAME widget (the browser keeps its layout), so a selection change costs one trait write, not a diagram rebuild.
- property kind: Literal['structure', 'state', 'action', 'requirements']¶
The active diagram kind (one of
DIAGRAM_KINDS).
- property diagram: Any¶
The diagram widget currently SHOWN in the right pane.
Built widgets stay in the diagram box as persistent children – their browser views must survive re-shows (see
_show()) – so the shown one is the child whosedisplayis notnone.
- save_view(name=None, *, sidecar=None)[source]¶
Save the current pane’s diagram as a SysML v2 view usage.
The chrome affordance behind the header’s save button. The currently shown diagram widget carries its own root and kind (the
longeron.viewsseam), so this is a thin capture:longeron.views.save_view()appends the typed view usage – with a recursive expose of the shown scope and the standardrenderreference – to the scope’s owning package, and the live presentation (direction, routing, collapse state) lands in the sidecar entry.sidecaris a path,Noneto auto-discover the workspace sidecar next to the model’s sources (skipped silently for in-memory models), orFalseto skip the sidecar write. The model text itself is NOT rewritten here: export the model (longeron.save()/to_sysml) or push it over the API to materialize the change. Returns the view usage element; the tree refreshes so the saved view appears.- Return type:
- refresh()[source]¶
Re-read the model after an edit: rebuild the tree, re-render.
The app calls this on its launched explorers after a
longeron.editrename or value edit (the tree’s labels, qualified-name node ids, and the diagrams’ drawn labels may all be stale). The tree payload rebuilds wholesale (_tree_data()+set_nodes– the cheapest correct hook); the resolver is rebuilt too, because renames invalidate its resolution caches. Every CACHED diagram is dropped and the current selection re-rendered through the normal_showpath; the previously built widgets stay in the diagram box as hidden children – removing them would zombie their live browser views (see_show()) – but the cache drop guarantees a stale diagram is never shown again. Selection is preserved by element IDENTITY (its qualified name may have changed); an element that left the tree falls back to the root.- Return type:
- class longeron.widgets.Inspector(app, *, layout=None, activate=False)[source]¶
Bases:
VBoxThe property sheet widget (module docstring for the full tour).
Built for one
ModelApp(which exposes it asapp.inspector); attaches to the app’s selection seam in the constructor and never detaches – it lives exactly as long as the app.layoutdefaults to the app’s own resolved strategy;activatereveals the right sidebar on dock (default False: the tab is one click away, and auto-expanding a collapsed sidebar reshapes the user’s layout uninvited).The automation surface (tests, notebooks):
elementis what the sheet shows;_name_field/_doc_field/_value_fieldcommit like the user’s Enter/blur when assigned.- reveal()[source]¶
Expand the right sidebar and select the Inspector tab.
The sweeper clicks the inspector’s own sidebar tab (the same gesture a user makes; JupyterLab’s
shell.expand_rightalone would expand whatever tab was last current). A no-op inline. The APP calls this once per session, on the first app-launched selection (longeron.widgets.app.ModelApp._reveal_inspector_once();open(reveal_inspector=False)disables it) – every LATER selection deliberately leaves the user’s layout alone.- Return type:
- class longeron.widgets.ModelApp(*, layout='auto', activate=True, inspector=True, reveal_inspector=True)[source]¶
Bases:
VBoxThe sidebar workbench widget. Build one with
open().The programmatic surface mirrors every UI affordance (the notebook and test automation path):
load_path(),add_model(),connect_api()/fetch_api_model(),explore_model(),scoreboard_model(),save_model(),push_model(),close_model()– plus the inspector seam documented in the module docstring (current_model/on_model_selected(),current_element/on_element_selected()).- property current_element: Element | None¶
The most recently selected element in any app-launched tab.
- on_model_selected(callback)[source]¶
Call
callback(model_or_none)on every current-model change.- Return type:
- on_element_selected(callback)[source]¶
Call
callback(element)on every current-element change.- Return type:
- property explorers: tuple[Explorer, ...]¶
Every explorer this app launched or adopted (in wiring order).
Launched:
explore_model()(a row’s Explore button, a notebook cell). Adopted: a DIRECTlongeron.widgets.explorer. explore()call made while this app was the kernel’s most recent one (_adopt_explorer()) – its tree selections feed the inspector seam exactly like a launched tab’s.
- load_path(path=None)[source]¶
Load the file or directory at
path(default: the path field).Files go through
longeron.load()(.sysml/.kermlparse,.jsonimport); directories merge every.sysmlunder them (load_dir()). The loaded model joins the list (replacing a previous load of the same source) and becomescurrent_model.- Return type:
- load_selected()[source]¶
Load every FILE selected in the browse listing, one entry each.
The multi-select path (ctrl/cmd-click in the listing, then the Load selected button): every picked
file:row loads throughload_path()and becomes its own models-list entry; directory rows in the selection are ignored (descending is a single-pick gesture). One busy strip covers the whole batch.
- add_model(model, *, source=None)[source]¶
Adopt an in-memory model (origin
"text"; Save disabled).- Return type:
- close_model(model)[source]¶
Drop the model’s row (launched tabs stay; they own their views).
- Return type:
- refresh_explorers(model)[source]¶
Refresh every explorer in
explorersshowingmodel.The bounded blast radius of a model edit (module docstring): launched AND adopted explorers rebuild their tree payload and the selection’s diagram (
longeron.widgets.explorer.Explorer. refresh()); explorers the app never saw (constructed before it opened) and scoreboard tabs are left alone.- Return type:
- select_element(element)[source]¶
Select
elementprogrammatically (the seam’s write half).Routes through the most recently launched explorer of the element’s model when one exists – the tree reveals the element and the diagram highlights it, and the explorer’s own selection hook feeds the seam back. Without an explorer the seam updates directly, so the inspector still follows.
- Return type:
- explore_model(model)[source]¶
Launch an explorer tab (inline widget headless) for the model.
The explorer docks through its own idempotent identity (one tab per model, replaced on relaunch). Its tree selection feeds the inspector seam (
_adopt_explorer()): every selection in the tab updatescurrent_element(andcurrent_model).- Return type:
- scoreboard_model(model)[source]¶
Launch a requirements scoreboard tab; returns the widget.
Raises
AnalysisErrorwhen the model has no requirement usages (the row button is pre-disabled by the same test). Cell clicks in the tab feed the inspector seam through the widget’sselectedtrait.- Return type:
- save_model(model, path=None)[source]¶
Write the model back to its source (or
path: save-as).Single-file models write back to their file. Directory-loaded models write back FILE BY FILE: every tracked edit (
longeron.edit) maps to the source file its top-level member was loaded from, and only files whose regenerated content differs from disk are rewritten (longeron.export.save_workspace()); an edit that cannot be mapped – or a top-level member with no recorded source file – refuses the save with nothing written. In-memory and API models need an explicitpath(one merged file). API models push instead:push_model().- Return type:
- push_model(model, message='')[source]¶
Push an API-loaded model back as a commit (
client.push_commit).
- connect_api(url=None, token=None, *, client=None)[source]¶
Connect to a Systems Modeling API server and list its projects.
url/tokendefault to the fold’s fields;clientinjects a pre-builtClient-compatible object (the in-process test idiom). A bearertokenrides anAuthorizationheader (the Flexo JWT convention). Returns the connected client; the project picker fills on success.- Return type:
- class longeron.widgets.ModelTree(nodes=(), **kwargs)[source]¶
Bases:
AnyWidgetThe built-in
TreeViewengine (a self-contained anywidget).Disclosure rows, kind badges, filter, keyboard navigation. Pure presentation over
TreeNodedicts – it holds no model references, only ids (qualified names).selectedis the two-way selection trait (at most one id); setting it from Python reveals the node in the browser (ancestors expand, the row scrolls into view).querylive-filters the tree exactly like the diagram toolbar’s search (case-insensitive substring over label and qualified name);match_count/total_countmirror itsmatches/totalcounter and are computed kernel-side too, so headless tests see the same numbers the browser shows.show_relationships(default True) is the tree-toolbar toggle’s trait: False hides everykind='relationship'row – they drop out of the rendered tree AND out of both counts – so kernels and notebooks can drive the toggle programmatically.- selected¶
selected node ids; [] = no selection
- query¶
live filter text; empty shows the whole tree
- show_relationships¶
whether relationship rows are shown (and counted)
- match_count¶
how many visible nodes match the query
- total_count¶
how many nodes the tree shows
- set_nodes(nodes)[source]¶
Replace the tree’s contents (the browser re-indexes and re-renders).
- Return type:
- on_select(callback)[source]¶
Call
callbackwith the selected ids on every selection change.- Return type:
- class longeron.widgets.Timebase(timeline, track=None, seconds_per_step=None)[source]¶
Bases:
objectOne recording, many views: a trace plus its optional mission binding, aligned on one axis.
timelineis the recorded truth (longeron.replay);trackis the optional globe binding, built FROM that timeline (longeron.analysis.mission3d.track_from_timeline()), so the two views replay one execution. The shared axis is the timeline’s own: sim seconds for a timed trace (track seconds are then the same numbers, the 1:1 mapping the design verified), or the step index in step mode.Step-only traces have no time axis, so a step-mode timebase refuses a
trackunlessseconds_per_stepstates one (a scalar, or a per-step sequence/mapping – seestep_seconds()); the same value must then have built the track. Stated durations count as first-class; only the synthesized gaps show up insynthetic_intervals(), which is what the scrubber labels.- property span: tuple[float, float]¶
The shared axis window:
(t_start, t_end), or(0, n_steps - 1)in step mode.
- env_at(t)[source]¶
The telemetry row at
t: the last scalar-env snapshot at or before it (step semantics, like the tracks);{}before the first.
- longeron.widgets.action_diagram(action, *, lanes=None, toolbar=True, routing='orthogonal', direction='right', max_label_width=480.0, height=None)[source]¶
The succession control-flow graph the interpreter executes.
Successions render dashed with open-V arrows and the behavior nodes use the spec glyphs (spec 8.2.3 printed p.227-228; figures pp.90-92): start = filled dot, done = bullseye, terminate = circle-X, fork/join = thick filled bar, decision/merge = empty rhombus, accept/send = the standard rounded action box with a filled top-left badge. Control glyphs carry single convergence anchors: every incoming edge joins at one point and every outgoing edge leaves from one point (fork/join bars excepted – their edges distribute along the bar, which is the bar’s semantic).
lanes(default off) partitions the flow into dashed-boundary «performer» swim lanes (spec “Perform Actions Swimlanes”, printed p.90): pass a mapping of lane title -> step names, orTrueto derive lanes fromperformtargets (perform part1.action1lands in lanepart1). Lanes are content-sized dashed containers ordered left-to-right via ELK layer partitioning – an honest approximation of the spec’s full-height, shared-boundary lanes. Steps in no lane stay outside (like the spec’s start/done markers).toolbar=Falsekeeps ipyelk’s stock toolbar;routingpicks the edge routing style (orthogonal / polyline / splines);directionthe layout flow ("right", the flow-reading default, or"down").max_label_widthcaps compartment-row display width exactly likestructure_diagram()(behavior boxes carry no rows today, so the cap is future-proofing);heightpins the widget’s rendered height to a CSS length exactly likestructure_diagram()(default: the 400px-floor bare-cell behavior).- Return type:
Diagram
- longeron.widgets.diagram(element, **kwargs)[source]¶
Pick a view by element kind: state machines, actions, else structure.
- Return type:
Diagram
- longeron.widgets.explore(model, *, tree=None, layout='auto', mode='tab-after', structure_scope='package', height='600px')[source]¶
Explore
model: a tree navigator beside a diagram pane.Keyword arguments reach
Explorer(spelled out here so the vocabularies typecheck at the call site):layout–"auto"(the default: dock into JupyterLab when ipylab is installed and a Lab frontend is detected, else render inline),"inline"(a plain side-by-side HBox; works everywhere – nbclient, VS Code, docs), or"lab"(require the ipylab docking; raisesMissingExtraErrorunless theexplorerextra is installed);mode– how thelablayout docks into the shell, passed straight through to JupyterLab ("tab-after","tab-before","split-right","split-left","split-top","split-bottom", …). The default"tab-after"opens the explorer as its own full-width main-area tab WITHOUT stealing focus or width from the notebook; choose asplit-*mode (or drag the tab) to see both at once. Ignored by theinlinelayout. Re-running the cell – or restarting the kernel and running all cells – REPLACES the model’s docked panel instead of accumulating copies (see the module docstring);tree– a customTreeViewengine (defaultModelTree);structure_scope–"package"(the default) scopes the structure view to the selection’s owning package so relationship edges to siblings stay visible;"element"scopes it to the selected namespace itself;height– the explorer’s inline CSS height: the tree pane and the diagram pane both honor it (default"600px"; the diagram area takes the pane minus the kind-switcher header). Thelablayout ignores it – the docked panel fills its tab, and the dock’s split handles own the sizing.
- Return type:
- longeron.widgets.grand_dashboard(model, sizing=None, *, assembly='Rotorcraft::QuadCopter', states='DeepScout::FlightStates', sizer='ScoutSizing::IsrPrime', station_requirement='ScoutSizing::IsrStation', station_var='stationMinutes', loiter_var='loiterSpeed', what_if_station=420.0, values=None, waypoints=((33.7813, -84.3833, 350.0), (33.7885, -84.3785, 390.0), (33.79, -84.3695, 380.0), (33.7838, -84.369, 360.0), (33.777, -84.3825, 350.0)), events=(2.0, 'launch', 6.0, 'airborne', 150.0, 'low_battery', 10.0), ground_alt=300.0, imagery='satellite')[source]¶
The grand-tour dashboard (an ipywidgets
VBox) – one call.modelcarries the drone: its structure feeds the diagram, its interpreted M0 population sizes the 3D mesh, its requirement hierarchy is the scoreboard, and itsstatesmachine flies the Cesium mission overwaypoints.sizing(default:modelitself) carries the continuous side:sizerbecomes the OpenMDAO problem behind the loiter slider, andstation_requirementthe Z3 consistency cards – the what-if card demandsstation_var >= what_if_stationwithloiter_varfreed, an impossible floor whose UNSAT core names the binding constraints.valuesinjects extra measured scoreboard bindings (e.g. performance measures computed through the interpreter); the live occlusion and disc-overlap measures are merged on top.See the module docstring for the pane list and the wiring map. The returned layout exposes every piece for scripting and tests:
.diagram,.viewer,.board(+.scoreboard, the currentScoreboard),.elevation/.azimuth/.readout/.report,.loiter/.optimize/.problem/.optimum,.smt_sat/.smt_what_if,.mission/.track,.mesh/.part_map/.camera,.header,.config_view(theConfigViewBindingbehind the config-keyed 3D pane), and.unlink(drops the diagram <-> 3D binding).- Return type:
- longeron.widgets.graph_viewer(model_or_graph, *, namespaces=None, families=None, literals=False, external=False, isolated=True, seed=7, iterations=60, node_cap=5000, width_px=760, height_px=520)[source]¶
Explore a model’s RDF projection as an interactive 3D graph.
model_or_graphis aModelor a graph already built withlongeron.rdf.to_graph()(pass the latter to keepevaluated=Trueliterals in the hover payloads).namespaces/families/literals/external/isolatedselect the initial view exactly as ingraph_view(); the in-scene panel (orwidget.filter(...)) changes them later, re-layouting kernel-side on every change.seedanditerationssteer the deterministicspring_layout()embedding; the layereddag_layout()embedding ships alongside it and the in-scene slider morphs between the two without kernel round trips.Views larger than
node_capnodes keep thenode_caphighest-degree nodes and say so in an in-scene notice: rendering is instanced and stays fluid into five figures, but the exact O(n^2) layout is the honest ceiling, so the cap protects the kernel rather than the GPU.The widget’s
selectedtrait (qualified names, two-way) pluson_select(callback)form the same selection contract the explorer’s tree exposes: clicks land in the kernel, kernel assignments drive the in-scene emphasis (and an eased camera fly-to), andcounts/layout_secondsreport the current view’s size and layout cost.focus(id, k=...)/unfocus()isolate a neighborhood kernel-side, andexport_html(path)writes the current view as a self-contained standalone page.- Return type:
AnyWidget
- longeron.widgets.link_time(clock, *views, seconds_per_step=None)[source]¶
Wire time-aware views to one clock (the temporal
link_selection).Each
viewis any widget with atimetrait on the clock’s axis: the replay player, the mission viewer, the scrubber, or a future subscriber. The adapter observes the trait intoClock.seek()and fans clock changes back, both sides under the1e-3coalescing tolerance, so scrubbing one view scrubs them all and no write echoes. Views that also carryplayingandratetraits (the scrubber; the mission viewer’s Cesium bridge) get those wired the same way, and the clock’s current state fans out to every view at link time.The one non-identity mapping is the globe under a step-mode clock: steps are not seconds, so the binding is REFUSED unless
seconds_per_stepopts in (a scalar, or a per-step sequence/mapping matching the track’s own build – seestep_seconds()); the adapter then maps step positions through the stated durations, scalesrateto track seconds per wall second, and sizes the viewer’s drift tolerance to match.A view holds ONE time link; linking it again replaces the previous adapter. Returns an idempotent
unlink()that detaches every adapter, mirroringlink_selection.
- longeron.widgets.mesh_viewer(mesh, mesh_b=None, *, label='', label_b='', width_px=760, height_px=430)[source]¶
View one baked mesh dict, or two side by side at true scale.
mesh/mesh_bcome fromlongeron.analysis.geometry(or any producer of the same schema). The canvas fills the notebook cell’s width;width_px/height_pxset its aspect ratio (and the fallback width when the host width cannot be measured). Drag to orbit, shift-drag or right-drag to pan, scroll to zoom, double-click to re-fit. Assign a new JSON string to the returned widget’smesh_jsonto swap the scene in place – e.g. from anobservehandler on another widget.Linked selection:
widget.highlight(keys)pops the parts whose identity key (thekeystamped bylongeron.analysis.geometry.tag_parts(), else the partname) is inkeysand dims the rest;widget.highlight()clears. A plain click on a part reports its key on thepicked_jsontraitlet. Seelongeron.analysis.link.link_selection()for wiring both to a diagram.- Return type:
AnyWidget
- longeron.widgets.mission_dashboard(source, *, missions=None, width_px=None)[source]¶
The linked mission-compromise dashboard (an ipywidgets
VBox).sourceis either a loaded model (the candidate table is baked viamission_dashboard_data(), a half-minute of interpreter time) or an already-prepared data dict from that function. By default the layout is FLUID: rows and plots stretch to the container width in their design proportions while the row heights hold the one-screen FLOOR, so the dashboard fills any screen without ever needing vertical scroll at 1080p. The floor is not a cap: docked in a height-constrained host (JupyterLab’s “Create New View for Output”) the dashboard grows to fill the host’s height and the two widget rows share the surplus. Draggable gutters between the major sections re-balance them (double-click resets); their ratios are persisted widget traits. Passwidth_pxto pin a fixed total width instead (see the module docstring for the layout).The returned layout exposes its pieces for scripting and tests:
.sliders(mission -> priority IntSlider),.requirements(mission -> key -> threshold FloatSlider),.top_n,.pareto_toggle(dominated-candidate filter),.pareto_hint(the one-line all-non-dominated hint beside the pressed toggle),.tabs(summary + one tab per mission),.parcoords(itsbrushestrait carries the live brush intervals by axis name;tracedthe selected line),.scatter,.viewer,.cards,.summary,.lineup(the pick cards;hovercarries the transient parcoords line index, mirrored to.parcoords.highlight;selectedthe sticky selected card line),.splitters(the section gutters by name:rowsbetween the plot and control rows – it also fills a height-constrained host –plotsbetween parcoords and scatter,tabsbetween the tab set and the 3D side; each holds itsratiotrait, clamped to[lo, hi], withratio0the double-click reset),.data,.live(the currentapply_thresholds()table),.front(per-candidate non-dominated flags),.pool(the candidate indices currently in view – EMPTY when the toggle is on and nothing is eligible),.picks(the current top-N candidate indices),.scores(the MOE per candidate),.selected(the selected candidate index, orNone), and.select(index)(drive the linked selection from Python;Noneclears).- Return type:
- longeron.widgets.mission_viewer(track, *, mesh=None, model_scale=1.0, label=None, height_px=480, imagery='satellite', ion_token='')[source]¶
Fly
trackon a Cesium globe in the notebook.The viewer starts paused at the track epoch with the camera tracking the drone; Cesium’s native timeline and animation dial play, pause, scrub, and re-speed the mission. Pass
mesh(alongeron.analysis.geometrymesh dict) to fly the airframe’s own geometry as a glTF model atmodel_scaletimes true size, flown with the multirotor attitude (yaw along the track heading, props level in vertical phases, the track’stilt_degforward tilt in cruise); without a mesh the drone is a point.imagerypicks the tokenless base:'satellite'(Esri World Imagery, the default),'plain'(a neutral dark globe, no tiles), or'osm'(OpenStreetMap streets);ion_tokenupgrades to Cesium World Terrain + imagery regardless. Click the drone (or a waypoint pin) to report its CZML id onpicked_json; drive or observe the playhead through the bidirectionaltimetrait. Assign a new JSON string toczml_jsonto swap the mission in place.- Return type:
AnyWidget
- longeron.widgets.open(*, layout='auto', activate=True, inspector=True, reveal_inspector=True)[source]¶
Open the longeron model app (module docstring for the full tour).
layout–"auto"(the default: dock into the JupyterLab LEFT sidebar when ipylab is installed and a Lab frontend is detected, else render inline),"inline"(the same widget in the cell output; works everywhere), or"lab"(require the sidebar docking; raisesMissingExtraErrorunless theexplorerextra is installed);activate– reveal the sidebar panel once it attaches (the sweeper clicks the app’s own tab; JupyterLab does not activate left-area additions itself);inspector– also build the item inspector (longeron.widgets.inspector), docked into the RIGHT sidebar under thelablayout (collapsed until clicked) and exposed asapp.inspectoreverywhere.Falseskips it;reveal_inspector– reveal the docked inspector ONCE, on the first element selection an app-launched tab feeds through the seam (so users see where selections land); every later selection leaves the layout alone.Falsekeeps the inspector fully collapsed until its tab is clicked.
Re-running
open()– or restarting the kernel and re-running – REPLACES the docked panel instead of stacking a second one; the fresh app starts with an empty model list (the returned handle owns the models).- Return type:
- longeron.widgets.replay_widget(interpreter, element, events=None, *, inputs=None, width_px=760, kind=None, timeline=None)[source]¶
Simulate
elementand replay it over its diagram.kindpicks the view and recorder:"state"(record_timeline()over the state diagram) or"action"(record_action_timeline()over the action diagram). The default (None) auto-detects: elements whosekindis"action"replay as actions, everything else as a state machine.timelineskips the recording and replays a PREBUILTTimelineinstead, so one recording can feed this widget, the mission globe, and the time seam’s scrubber (seelongeron.widgets.time); it excludesevents/inputs. The widget’s bidirectionaltimetrait is its seam surface: a kernel-side write seeks the playhead (stopping any front-end playback first), and the front-end reports the playhead at ~4 Hz while playing –longeron.widgets.link_time()subscribes it to a shared clock.Needs the
replayextra (anywidget) plus the diagram toolchain (vendored ipyelk and anodeexecutable, as forrender.to_svg).- Return type:
AnyWidget
- longeron.widgets.scoreboard(model_or_element, values=None, aggregation='saw', *, weights=None, utilities=None, value_format='percent')[source]¶
MAUT-score the requirement hierarchy under
model_or_element.The scope’s root requirement usages become the top level (several roots aggregate under one synthetic root; requirement definitions contribute their attributes through typing – pass a definition itself to score it directly).
valuesinjects raw measurements: by requirement qualified name, by requirement name, or – for plain identifiers – as evaluation-frame bindings overriding the free references insidemeasureexpressions and constraint bodies (seearchitecture_values()for the trade-study bridge).aggregationis a name fromAGGREGATORSor anyAggregator;weights/utilitiesare exploration-time overrides keyed likevalues.value_formatpicks ONE consistent rendering for utilities/aggregates everywhere they display (str()’s table, the widget’s cell labels and tooltips):"percent"(the default; one decimal,61.1%) or"float"(three decimals,0.611).Scoreboard.table()always carries the raw floats.- Return type:
- longeron.widgets.state_diagram(machine, *, submachine_depth=None, toolbar=True, routing='orthogonal', direction='right', max_label_width=480.0, height=None)[source]¶
A hierarchical state machine: states, entry markers, transitions.
A state usage typed by a state def (
state swap : ToteSwap;) is expanded into the definition’s full submachine – states, entry marker, transitions – the same member view the interpreter executes (StateMachinedescends throughmembers_of). Expansion is recursive and cycle-safe: a definition reached again through its own submachine draws as a collapsed leaf.submachine_depthbounds how many typing hops to expand:None(the default) is unlimited,0draws typed states as plain leaves (the pre-0.8 behavior). Plain nested states are always shown.toolbar=Falsekeeps ipyelk’s stock toolbar;routingpicks the edge routing style (orthogonal / polyline / splines);directionthe layout flow ("right"or"down");max_label_widthcaps compartment-row display width exactly likestructure_diagram()(state boxes carry no rows today, so the cap is future-proofing);heightpins the widget’s rendered height to a CSS length exactly likestructure_diagram()(default: the 400px-floor bare-cell behavior).Expanded substate ids are instance-qualified (
…::swapSource::swap::evaluating) so they stay unique per expansion site, selectable in the browser (the resolver walks typing hops), and exactly whatlongeron.replayrecords: two usages of one definition never share a replay key.- Return type:
Diagram
- longeron.widgets.structure_diagram(element, *, show_attributes=True, show_relationships=True, composition='defs', membership='nested', annotations=False, actor_style='figure', parts='nested', levels=None, folded=None, toolbar=True, routing='orthogonal', direction='right', max_label_width=480.0, height=None)[source]¶
Containment structure with specialization/typing/connection edges.
composition="defs"(the default) draws definition-level membership edges – a filled diamond at the whole end for composite part/item members, a hollow diamond for referential (ref) members, role name on the line, multiplicity at the part end – per the SysML v2 Parts notation;composition="none"suppresses them. Flow / binding / dependency / satisfy / alias / portion notation is always drawn when both ends resolve to drawn nodes (see the module docstring).membership="nested"(the default) draws each package’s owned members NESTED inside its box – the spec’s primary presentation and exactly the pre-0.8 output.membership="edges"draws the spec’s ALTERNATIVE presentation instead (printed p.26, errata E18): packages do not swallow their drawn members – every member becomes a SIBLING node and a solid owned-membership edge runs from the owning package, carrying a true circle-plus at the owning end. (Siblings keep ELK’s layered layout stable: an edge between a package and a node nested inside it is the ancestor<->descendant case the layout mishandles.) Membership edges are containment presentation, not relationship edges, soshow_relationships=Falsekeeps them.Port usages owned by a drawn definition/usage box render as the spec’s boundary squares (10x10, straddling the border,
name : Typelabel INSIDE the box next to the square – where the spec’s part figures write it – direction arrow inside the square when the port definition’s directed features agree on one); interface / connection / binding / flow ends then attach square-to-square, and connector ends naming UNDRAWN nested features draw the spec’s proxy dot on the shallowest drawn ancestor (printed p.67). Only nodes that own drawn ports opt into ELK port handling – everything else keeps the exact pre-port layout path.annotations=True(default off, to keep existing diagrams uncluttered) additionally draws comment/documentation notes – the folded-corner box with a dashed anchor line (no endpoint glyph) to each annotated element (spec printed pp.20-21) – and «@Type» / «#keyword» metadata adornments on annotated nodes.actor_style="figure"(the default) draws actor usages as the spec’s stick figure (BNF printed p.244) – head, body, arms, legs in the usage palette, name below the figure, no «actor» stereotype (the figure IS the stereotype);actor_style="box"keeps the «actor» keyword-box alternative (errata N17), which also shows compartments. Stakeholders always draw the «stakeholder» box – the spec reserves the figure for actors.Textual members group into the spec’s LABELED compartments (8.2.3.6 printed p.199): every compartment opens with a full-width separator rule and its italic name – ‘attributes’ (printed p.46), ‘enums’ (p.48), ‘directed features’ (p.62; ‘parameters’ on action/calc boxes, p.91), the constraint compartments (p.127), ‘subject’, and so on – replacing the earlier unlabeled row blob. Every row is a first-class SELECTABLE projection of its model element: it carries the element’s qualified name as its id, clicking it in the browser feeds
on_select()exactly like a node click, and kernel-side selection writes light it up.partspicks the presentation of nested usages (both are legal spec notation; the option only chooses):"nested"(the default) draws them as nested boxes – the graphical compartment, required where children anchor edges (connections, flows, proxies) – while"rows"is the COLLAPSED presentation: parts, items, the occurrence family, actions, states, requirements, named satisfies and allocations, actors, stakeholders and views render as textualname : Typerows in their spec compartments (‘parts’ printed p.60, ‘items’ p.57, ‘actions’ p.89, ‘states’ p.117, …). Edges that would anchor on the collapsed children are not drawn – the textual presentation trades them for compactness.levelsnames individual nodes (qualified names, or elements ->"partial"/"collapsed") whose rendition starts below the expanded default – the state behind the toolbar’s collapse button (which CYCLES the selected node: expanded -> partial -> collapsed -> expanded, each click one step less detail) and thelevel()kernel API (seeCollapseTool)."partial"is the per-node version ofparts="rows": the node’s rowable members become textual rows."collapsed"is the smallest legal rendition: the name compartment alone – kind chip + name, no compartment stack, no drawn children (boundary port squares stay: they are border interface points, the classic black-box view); a collapsed PACKAGE likewise draws its folder box alone, whatever themembershipmode.foldednames per-node FOLDED compartments (qualified name -> compartment names): a folded compartment keeps its header – with the closed twist – and drops its rows while the node stays at its level (the header row’s click affordance in the browser; thefold()kernel API).How collapse composes, level x presentation:
edges – connector-family edges (connections, bindings, interfaces, flows, allocates) that anchored on a shrunken node’s children re-anchor as the spec’s proxy dots on the node itself (printed p.67) at BOTH shrunken levels; connectors living entirely inside one shrunken node are part of the collapsed content and are not drawn; the specialization/typing family from undrawn children is not drawn (at partial, the rows’
: Typetext carries it) – all exactly as under the diagram-wideparts="rows";parts="rows"– every node is already textual, so"partial"changes nothing there and the toolbar cycle skips it (expanded -> collapsed -> expanded);"collapsed"andfoldedwork unchanged;folds – independent of the level: they apply to whatever compartments the node currently shows (attributes at expanded, parts rows at partial, none at collapsed) and are remembered through level changes.
toolbar=Falsekeeps ipyelk’s stock text-button toolbar instead of the compact icon+search one (longeron.toolbar).routingpicks the ELK edge routing style –"orthogonal"(the default),"polyline"or"splines"– for headless renders and the initial widget; the toolbar’s routing button cycles it live.directionpicks the layout flow –"right"(left-to-right, the default) or"down"(top-to-bottom); the toolbar’s orientation button toggles it live.max_label_widthcaps how wide a compartment row may draw, in px (default 480): longer rows – calculation/expression attributes are the usual offenders – are end-ellipsized with the FULL text on the row’s hover tooltip, so one absurd expression no longer makes the whole node absurd.Nonelifts the cap (every row at full width).heightpins the widget’s rendered height to a CSS length (e.g."480px") so inline compositions can match a neighbor exactly – tutorial 7 sits a diagram beside a 650px 3D viewer in an HBox. The defaultNonekeeps the bare-cell behavior: content-driven height with a 400px minimum floor. An explicit height always wins, even below that floor.- Return type:
Diagram
- longeron.widgets.time_scrubber(timebase, *, width_px=760)[source]¶
The standalone transport bar for a recording.
A play/pause button, a rate select, a slim slider over the timebase’s span with tick marks at the recorded transition instants (a density band above ~100 events), the mission phase bands where a track binding exists, a readout clock, and the scalar-telemetry line that follows the playhead. Step-mode recordings read
step k / N; where a seconds axis was stated per step the stated seconds show plainly and the synthesized segments carry an explicit(xN s)tag plus a striped band – a fabricated second is always displayed as fabricated.The scrubber is one subscriber among equals: pass it to
link_time()beside the replay player and the mission viewer. While playing it animates locally at the shared rate and syncs itstimetrait at ~4 Hz, exactly like its peers.Needs the
replayextra (anywidget).- Return type:
AnyWidget