Mission 3D tracks¶
Requires the viz extra (pip install "longeron[viz]").
The Cesium viewer widget that plays these tracks lives in longeron.widgets.mission3d; this module is the kernel-side track/CZML synthesis.
Mission flight tracks for the CesiumJS globe: synthesis and CZML baking.
Turns MODEL-level mission data into a timestamped geodetic track and
animates a drone flying it over a real globe. Two builders produce the
same MissionTrack:
mission_track()– explicit waypoints(lat, lon, alt[, t])(degrees, meters above the ellipsoid, seconds). Missing times derive from a cruise speed over the 3D leg lengths.model_waypoints()reads the same tuples off a mission part’s children (attributes namedlat/lon/alt, optionalt) through the interpreter, so the waypoints can live in the model itself.from_replay()– the state-machine timeline: the interpreter executes the machine throughlongeron.replay.record_timeline()(the same recorder the diagram replay widget uses) and each leaf state’s activation interval becomes one motion segment.track_from_timeline()is the same synthesis over an EXISTINGTimeline, so one recording can feed the diagram replay, the globe, and the time seam’s scrubber without re-simulating (seelongeron.widgets.time). The mapping is deliberately simple and name-driven – the point is model-driven animation (the globe shows the ACTUAL executed behavior: state durations, interleavings, reentries), not flight-sim fidelity. A state’s phase comes from substring hints on its name, overridable per state name viaphases=:ground(idle, standby, parked, …)hold the current position; the mission starts at the first waypoint at
ground_alt.takeoff(takingOff, launch, climb, …)vertical climb, in place, to the route altitude at the current route position.
route(flying, cruise, loiter, hover, survey, …)advance along the waypoint polyline; each segment covers a distance proportional to its share of the total route-phase time, so several flying/loiter states spend the one route between them and the drone lands wherever the machine stopped flying.
landing(landing, descend, …)vertical descent, in place, to
ground_alt.hold(anything unrecognized)hover in place.
Pure event cascades (no clock advance) record in step mode; each step then counts as
seconds_per_stepseconds of flight – a scalar, or a per-step sequence/mapping when steps take unequal durations (longeron.widgets.time.step_seconds()states the exact ladder).
The trace-to-mission binding can ride the model itself:
model_waypoints() reads the route off a mission part’s children,
and model_epoch() reads the mission epoch off an attribute typed
Time::Iso8601DateTime (vendored; resolves), so the model states
WHERE and WHEN the sortie flies. Both fall back honestly: no epoch
attribute means the deterministic default epoch.
MissionTrack.to_czml() bakes the track as a CZML document: a
grey planned-route polyline, small waypoint pins, and a drone entity
that flies the samples with an orange trail, its label following the
ACTIVE STATE name through the mission (CZML interval text – the state
machine is visibly driving the animation). Pass the
drone’s own analysis mesh (mesh=, the dict
longeron.analysis.geometry.drone_geometry() and its siblings
build) and the ACTUAL airframe geometry flies the route: the mesh
exports to a self-contained binary glTF through mesh_to_glb()
(in-house, stdlib-only – see longeron.analysis._glb for the
exact container), embeds in the CZML as a data: URI (tens of kB for
the quad), flies with a MULTIROTOR ATTITUDE, and never shrinks below a
legible pixel size however far the camera sits; model_scale blows it
up beyond true scale when the route dwarfs the airframe. Without a mesh
the drone stays the point entity.
Attitude: a multirotor moves vertically props-up and cruises with a
small forward tilt – Cesium’s VelocityOrientationProperty (which
points the nose along the velocity vector, so a climb renders the quad
VERTICAL) is deliberately NOT used. Instead the track bakes a sampled
orientation into the CZML (unitQuaternion keyframes, computed by
MissionTrack.attitude()): yaw follows the track heading, pitch is
0 wherever there is no horizontal motion (climb, descent, hover,
ground) and the tilt_deg forward tilt while the drone advances
along the route, roll stays 0, and orientation changes blend over a few
seconds (Cesium slerps between adjacent quaternion samples). A
CZML-sampled property is chosen over a front-end CallbackProperty
because the module’s whole design bakes the payload kernel-side: the
samples are deterministic, testable without a browser, and need no
front-end code. The tilt itself should come FROM THE MODEL:
model_tilt() evaluates the airframe’s own physics (e.g.
the DeepScout MultiRotor’s cruiseTilt: the arccos altitude-hold
ceiling at continuous thrust, capped by the operational comfort limit)
and feeds mission_track / from_replay tilt_deg=, a plain
float override. mission_values() completes the loop: it measures
the route’s waypoint legs and evaluates the model’s MissionTime
calc at the model’s own achievable cruise speed, producing the
scoreboard values= bindings for the mission-time requirement.
The viewer widget itself –
mission_viewer(), which plays the
baked CZML on a Cesium Viewer – lives in
longeron.widgets.mission3d: this module is the kernel-side
synthesis, deterministic and testable without a browser. Importing
mission_viewer (or the CESIUM_* CDN pins) from here still
works but is deprecated.
- class longeron.analysis.mission3d.MissionTrack(name, epoch, samples, waypoints, phases, tilt_deg=0.0)[source]¶
Bases:
objectA timestamped geodetic flight path synthesized from the model.
samplesare(t, lat, lon, alt)keyframes (seconds pastepoch, degrees, meters above the ellipsoid) with strictly increasing times;waypointsis the planned route the samples fly;phasesrecords the motion segments as(t0, t1, phase, qname)– for replay-built tracksqnameis the instance-qualified name of the driving leaf state (empty for plain waypoint tracks);tilt_degis the forward cruise tilt the airframe holds while it moves along the route (degrees nose-down; ideally the MODEL’s own number – seemodel_tilt()).- to_czml(*, mesh=None, model_scale=1.0)[source]¶
The CZML document the widget plays.
Packets: a document packet whose clock spans the mission (CLAMPED, multiplier sized so playback takes ~40 s of wall clock), the planned-route polyline, one pin per waypoint, and the
mission-droneentity – sampled positions with linear interpolation, an orange trail, aviewFromcamera offset sized from the route span, and a label whose text follows the active state name through the phases. Withmesh(a geometry-module mesh dict) the drone entity is the airframe’s own glTF model –mesh_to_glb()output on adata:URI, nose steered along the track heading with the multirotor attitude (seeattitude()), scaled bymodel_scaleand clamped to a legible minimum pixel size – instead of the fallback point.
- attitude(*, blend_s=3.0)[source]¶
The orientation keyframes:
(t, heading_deg, pitch_deg).Heading follows the track’s direction of travel (great-circle initial bearing per motion segment, degrees clockwise from true north); hover/vertical segments hold the last heading flown (or face the first leg before anything has moved). Pitch is 0 wherever the drone has no horizontal motion – a multirotor climbs, descends, and hovers props-up – and
-tilt_deg(nose-down forward tilt) while it advances along the route. Roll is always 0. Orientation changes blend overblend_sseconds, half on each side of the motion change, clamped to the neighboring segments’ midpoints so keyframe times stay strictly increasing.
- longeron.analysis.mission3d.from_replay(interpreter, state_machine, events=None, *, waypoints, inputs=None, phases=None, ground_alt=0.0, tilt_deg=0.0, seconds_per_step=10.0, epoch=None, name=None)[source]¶
A track driven by the state machine’s ACTUAL execution.
Simulates
state_machinewithevents(theInterpreter.simulateprotocol: event names or(name, payload)tuples, plain numbers advance the clock) vialongeron.replay.record_timeline(), then synthesizes the track withtrack_from_timeline()– see there forwaypoints,phases,ground_alt,seconds_per_step, andepoch.tilt_degis the forward cruise tilt the airframe holds while it advances along the route (degrees nose-down; derive it from the model withmodel_tilt(), or pass any plain float).namedefaults to the machine’s own name. To share one recording across the diagram replay and the globe, record once and calltrack_from_timeline()instead.- Return type:
- longeron.analysis.mission3d.mesh_to_glb(mesh)[source]¶
A viewer3d-style mesh dict as one binary glTF 2.0 blob.
meshis the dict thelongeron.analysis.geometrybuilders produce –partswithname/color/opacityand flatvertices/facesarrays. Every part becomes its own node + mesh + material under a single rotated root (see the module docstring for the exact container and scene shape), so per-part colors and translucency survive the export. RaisesAnalysisErroron a mesh with no parts, malformed vertex/face arrays, or out-of-range indices.- Return type:
- longeron.analysis.mission3d.mission_track(waypoints, *, speed_mps=12.0, tilt_deg=0.0, epoch=None, name='mission')[source]¶
A track that flies explicit waypoints, one route phase.
waypointsare(lat, lon, alt)or(lat, lon, alt, t)tuples (degrees, meters above the ellipsoid, seconds pastepoch); either every waypoint carries a time or none does, in which case times derive fromspeed_mpsover the 3D leg lengths.tilt_degis the forward cruise tilt the airframe holds along the route (degrees nose-down; derive it from the model withmodel_tilt(), or pass any plain float).epochdefaults to a fixed instant so the CZML is deterministic.- Return type:
- longeron.analysis.mission3d.mission_values(interpreter, waypoints, *, ground_alt, assembly='Rotorcraft::QuadCopter', mission_calc='DeepScout::MissionTime', payload_mass=None)[source]¶
Scoreboard
values=bindings for the mission-time requirement.The kernel measures the GEOMETRY – the waypoint legs’ great-circle lengths, the climb from
ground_altto the first waypoint, and the descent home from the last (the same phase mappingfrom_replay()flies) – and the MODEL computes the physics: anassemblyinstance supplies the achievable cruise speed (maxCruiseSpeedatcruiseTilt) and themission_calccalc def turns distances and speed into minutes. The returned dict (missionMinutesplus thecruiseTiltDeg/cruiseSpeedMps/routeMit derives from) injects straight intoscoreboard(model, values=...), exactly like the geometry module’s occlusion measures.payload_massoverrides the instance’spayloadMass– the one-line what-if: a heavier payload eats the continuous-thrust margin, the tilt ceiling and cruise speed collapse, and the mission budget busts. An airframe that cannot hold altitude at continuous thrust at all (cruise speed 0) reports an INFINITE mission time.
- longeron.analysis.mission3d.model_epoch(interpreter, mission, *, attribute='epoch')[source]¶
The mission’s stated epoch, read off the model.
Evaluates
attributeonmission– an attribute typedTime::Iso8601DateTime(the vendored standard time package’s UTC instant, carried as an ISO 8601 string) – and returns it as an aware UTCdatetime, ready for the track builders’epoch=. ReturnsNonewhen the mission states no such attribute, so callers fall back to the deterministic default epoch honestly; a stated value that is not ISO 8601 is a loudAnalysisError.
- longeron.analysis.mission3d.model_tilt(interpreter, assembly, *, attribute='cruiseTilt')[source]¶
The airframe’s own cruise tilt, degrees, read off the model.
Instantiates
assemblyand evaluatesattribute– for the DeepScout MultiRotor family that iscruiseTilt, the CruiseTilt calc: the altitude-hold ceilingarccos(m g / T)at continuous thrust, capped by the operational comfort limit. Feed the result tomission_track()/from_replay()tilt_deg=so the globe animation flies the MODEL’s physics (any plain float still works as an override).- Return type:
- longeron.analysis.mission3d.model_waypoints(interpreter, mission, *, lat='lat', lon='lon', alt='alt', time='t')[source]¶
Waypoints read off the model:
mission’s child parts that carrylat/lonattributes, evaluated through the interpreter in declaration order (altdefaults to 0 where absent; an optionaltimeattribute becomes the explicit timestamp).
- longeron.analysis.mission3d.track_from_timeline(timeline, *, waypoints, phases=None, ground_alt=0.0, tilt_deg=0.0, seconds_per_step=10.0, epoch=None, name='mission')[source]¶
A track synthesized from an EXISTING recording.
The synthesis half of
from_replay(): it maps every leaf state’s activation interval intimeline(alongeron.replay.record_timeline()product) onto a motion segment alongwaypoints– see the module docstring for the phase table andphases=for per-state-name overrides. Because the timeline arrives prebuilt, the diagram replay widget and the globe can play the SAME recording (the time seam’s timebase discipline,longeron.widgets.time.Timebase).waypointsare(lat, lon, alt)tuples (no times – timing comes from the recording); the mission starts at the first waypoint atground_alt. Timed timelines keep their instants 1:1 (track seconds ARE trace seconds). Step-mode timelines have no time axis, soseconds_per_stepstates one: a scalar, or a per-step sequence/mapping when steps take unequal durations (longeron.widgets.time.step_seconds()).epochanchors the track in UTC (model_epoch()reads it off the model;Nonekeeps the deterministic default).- Return type: