9. The grand tour: one dashboard, every seam

A capability demo, not an explanation. analysis.grand_dashboard composes the structure diagram, the to-scale 3D airframe, the requirements scoreboard, the generated OpenMDAO sizing problem, the Z3 consistency verdicts, and the Cesium mission replay into ONE reactive surface – every pane a house widget from earlier tutorials (3: views, 4: trades, 6: scoreboard, 7: linked CAD + occlusion), every reaction kernel-side traitlets, so the whole thing also runs (and self-verifies) headless. Extras: viz, mdao, smt.

import json

import longeron
from longeron.analysis.grand import grand_dashboard

program = longeron.load("../examples/deepscout")  # the whole DeepScout program: one workspace

A performance branch, grafted and measured

The drone model ships geometric requirements (clearView, propClearance); give its scoreboard a performance branch too – parsed from SysML text, grafted into the loaded model, measured through the model’s own calcs.

program.find("DeepScout").add(
    longeron.loads("""
package _Perf {
    requirement performance {
        attribute hoverMinutes : Real;      // measured: HoverTime(battery.capacity)
        attribute thrustToWeight : Real;    // measured: ThrustToWeight(4 rotors, MTOW)
        requirement endurance {
            attribute weight : Real = 2.0;
            attribute utility : String = "larger-is-better";
            attribute ramp0 : Real = 10.0;
            attribute ramp1 : Real = 30.0;
            attribute measure : Real = hoverMinutes;
            attribute unit : String = "min";
        }
        requirement agility {
            attribute utility : String = "larger-is-better";
            attribute ramp0 : Real = 1.0;
            attribute ramp1 : Real = 3.0;
            attribute measure : Real = thrustToWeight;
            attribute unit : String = "T/W";
            require constraint hoverMargin { thrustToWeight >= 1.8 }
        }
    }
}""").find("_Perf::performance")
)

interp = longeron.Interpreter(program)
quad = interp.instantiate("Rotorcraft::QuadCopter")
scope = program.find("DeepScout")
capacity = quad.slots["battery"].slots["capacity"]
thrust = 4.0 * quad.slots["thrustPerRotor"]
mass = quad.slots["totalMass"]
measured = {
    "hoverMinutes": interp.evaluate(
        longeron.parse_expression(f"HoverTime(capacity = {capacity})"), scope
    ),
    "thrustToWeight": interp.evaluate(
        longeron.parse_expression(f"ThrustToWeight(thrust = {thrust}, mass = {mass})"), scope
    ),
}
print({key: round(value, 2) for key, value in measured.items()})
{'hoverMinutes': 23.28, 'thrustToWeight': 2.41}

Build the dashboard

One call. The wiring map (all kernel-side):

  • diagram click -> 3D highlight (and mesh pick -> diagram selection); clicking a requirement node also selects its scoreboard cell (and a scoreboard cell click selects the diagram node);

  • diagram click on another craft -> the 3D pane shows THAT craft (tutorial 7’s link.bind_config_view): a build configuration bakes from its own M0 population, a fleet airframe shell from its own attributes, and the catalog’s variant usages resolve to the definitions that type them; the camera what-if keeps measuring the home assembly;

  • camera sliders -> occlusion: every move re-runs the mesh-engine view-cone quadrature, swings the translucent cone in the 3D pane, highlights the obstructing parts, lists them in the readout, recolors the scoreboard, and updates the header score;

  • loiter slider -> OpenMDAO run_model; maximize station time runs the driver and snaps the slider to the optimum;

  • the Z3 strip pins the design point’s SAT witness beside an impossible what-if’s UNSAT conflict core;

  • the Cesium pane replays FlightStates over satellite Atlanta with the drone’s own geometry (offline it degrades to a printed note), flown with the model’s own attitude physics: props level in climb/descent, the cruiseTilt forward tilt (arccos thrust ceiling, ops-capped at 25 deg) on the route – and the same physics prices missionTime onto the scoreboard.

Try in JupyterLab: click motors in the diagram; click TeardropQuad (or the catalog’s hexLifter variant) and watch the 3D pane swap to that craft; drag azimuth to 180 and watch clearView go red while the battery lights up in 3D; drag the loiter slider, then click maximize station time; press play on the Cesium timeline.

from longeron.analysis import mission3d
from longeron.analysis.grand import ATLANTA_LOOP

# mission time is analysis: the route legs measured kernel-side, priced by
# the model's own physics (MissionTime at cruiseTilt's achievable speed)
measured |= mission3d.mission_values(interp, ATLANTA_LOOP, ground_alt=300.0)
dash = grand_dashboard(program, values=measured)

Headless proof that the wiring holds – the same traitlets a browser click writes:

# M1 -> M0 fan-out through the linked panes (tutorial 7's seam)
dash.diagram.view.selection.ids = ["Rotorcraft::QuadCopter::motors"]
assert json.loads(dash.viewer.highlight_json) == sorted(
    dash.part_map[f"motor{i}"] for i in (1, 2, 3, 4)
)

# a requirement click lands on the scoreboard
dash.diagram.view.selection.ids = ["DeepScout::installation::clearView"]
assert list(dash.board.selected) == ["DeepScout::installation::clearView"]

# swing the camera into the airframe: occlusion goes positive, clearView flips red
dash.azimuth.value, dash.elevation.value = 180.0, -20.0
assert dash.report["occludedFraction"] > 0.0
assert "battery" in dash.report["obstructions"]


def leaf(node, name):
    if node["label"] == name:
        return node
    found = (leaf(c, name) for c in node.get("children", ()))
    return next((f for f in found if f), None)


assert leaf(json.loads(dash.board.nodes_json), "clearView")["utility"] == 0.0

# the Cesium pane flies the MODEL's attitude, and the mission budget holds
assert dash.track.tilt_deg == 25.0  # Rotorcraft::QuadCopter::cruiseTilt
assert leaf(json.loads(dash.board.nodes_json), "missionTime")["utility"] > 0.7

# the sizing strip runs the generated OpenMDAO problem live
dash.loiter.value = 21.0
at_21 = float(dash.problem.problem.get_val("stationMinutes")[0])
dash.optimize.click()  # driver run; the slider snaps to the optimum
assert float(dash.problem.problem.get_val("stationMinutes")[0]) > at_21

# the Z3 strip: consistent at the design point; the what-if names its blockers
assert dash.smt_sat.status == "sat" and dash.smt_what_if.status == "unsat"
assert "IsrPrime::aboveStall" in dash.smt_what_if.core

# reset to the model's design point for the live view
dash.azimuth.value, dash.elevation.value = 0.0, -15.0
dash.loiter.value = 15.0
dash.diagram.view.selection.ids = []
print("grand tour wiring: all assertions passed")
grand tour wiring: all assertions passed

Click a craft, see that craft

The 3D pane is config-keyed. The structure diagram shows the whole program, so it holds every craft: the five build configurations, the twelve fleet airframe shells, and the mission catalog’s variants. Clicking any of them renders that craft in the pane. A fleet shell renders with its equipment as elements: the battery, the flight controller, and the camera carry their own part-usage keys, so a canvas pick of the battery selects the battery (tutorial 7 owns that story). The binding rides on dash.config_view, and the same traitlets prove it headless:

# a fleet shell: the teardrop's lathed body replaces the quad
dash.diagram.view.selection.ids = ["Rotorcraft::TeardropQuad"]
assert dash.config_view.current == "Rotorcraft::TeardropQuad"
teardrop = json.loads(dash.viewer.mesh_json)
keys = {part["name"]: part["key"] for part in teardrop["parts"]}
assert keys["frame"] == keys["bay"] == "Rotorcraft::TeardropQuad"  # the craft...
assert keys["battery"] == "Rotorcraft::TeardropQuad::battery"  # ...and its elements
assert keys["fc"] == "Rotorcraft::TeardropQuad::flightController"
assert keys["camera"] == "Rotorcraft::TeardropQuad::camera"

# a build configuration: the hexa bakes from its own M0 population
dash.diagram.view.selection.ids = ["Rotorcraft::HexaCopter::motors"]
assert dash.config_view.current == "Rotorcraft::HexaCopter"
assert len(json.loads(dash.viewer.mesh_json)["discs"]) == 6

# the catalog's variant usage resolves to the definition that types it
dash.diagram.view.selection.ids = ["ScoutMissions::Catalog::AirframeChoice::hexLifter"]
assert dash.config_view.current == "Rotorcraft::HexLifter"

# home again: the quad returns, view cone and all
dash.diagram.view.selection.ids = ["Rotorcraft::QuadCopter"]
assert json.loads(dash.viewer.mesh_json)["parts"][-1]["name"] == "viewCone"
dash.diagram.view.selection.ids = []
print("config-keyed 3D: teardrop, hexa, variant, and home -- all rendered")
config-keyed 3D: teardrop, hexa, variant, and home -- all rendered

The dashboard:

dash
VBox (static snapshot of the interactive widget)

Static snapshot -- run the notebook in JupyterLab (pixi run lab) to interact.