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
FlightStatesover 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, thecruiseTiltforward tilt (arccos thrust ceiling, ops-capped at 25 deg) on the route – and the same physics pricesmissionTimeonto 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
