Linked selection

Requires the viz extra plus the vendored ipyelk for the diagram side (pip install "longeron[viz]").

link_selection() composes two existing public seams – the diagrams’ click-selection callback (longeron.diagrams.on_select(), whose node ids are qualified names) and the mesh viewer’s highlight traitlet (longeron.widgets.viewer3d) – so clicking a part in a structure diagram pops the corresponding geometry in the three.js scene, and clicking a mesh selects the diagram node.

bind_config_view() promotes the link from highlight to scene: the selection also decides WHICH craft the viewer shows. Clicking any element resolves the configuration that owns it (owning_config(); a variant usage resolves to the definition that types it), bakes that craft’s scene (longeron.analysis.grand.scene_for() dispatches both DeepScout families), and swaps the viewer – tutorial 7’s inline handler as one reusable call, and the grand tour’s 3D pane.

The bridge between the two worlds is the mesh part key stamped by longeron.analysis.geometry.tag_parts(): the qualified name of the model part a mesh component renders, or – for per-instance parts – the M0 individual id from longeron.m0.interpret(). Selections resolve to keys with containment-and-typing semantics (see selection_keys()):

  • a usage matches every key equal to its qualified name or nested under it, so selecting an assembly highlights all of its rendered children;

  • a definition additionally matches every usage directly typed by it (part motors : Motor lights up for Motor) – one def, all its occurrences; specializations of the def do not count;

  • an M0 individual id key (Rotorcraft::QuadCopter#0.motors#2) belongs to the usage its dotted path derives from (individual_qname() – here Rotorcraft::QuadCopter::motors), so selecting the one M1 usage lights up every rendered individual;

  • a selection that touches nothing in the scene clears the highlight rather than dimming the whole craft – only affirmative matches dim the rest.

Everything runs in Python via traitlets observers (the house pattern of longeron.analysis.dashboard), so the wiring works headless; only the pixel-level effects (emissive pop, raycast picking) need a browser.

class longeron.analysis.link.ConfigViewBinding(viewer)[source]

Bases: object

The disposable handle bind_config_view() returns.

current is the qualified name of the configuration the viewer shows (the showing hint until the first swap, else None). scenes is the bake cache: configuration qualified name -> (mesh, part_map), with None caching a definition the scene baker rejected, so failing bakes are not retried on every click. unbind() is idempotent, and a binding replaced by a newer bind_config_view() call on the same viewer is already inactive.

unbind()[source]

Deactivate both directions and clear the highlight.

The scene stays as last rendered.

Return type:

None

longeron.analysis.link.bind_config_view(source, viewer, model, *, showing=None, scene=None, decorate=None, bidirectional=True)[source]

Selections drive WHICH craft the 3D viewer shows, not just the highlight.

source is anything with the house selection surface: an interactive diagram from longeron.diagrams (selections arrive through longeron.diagrams.on_select()), or any widget with the explorer protocol’s on_select(callback) / selected pair (the model explorer’s tree, the scoreboard widget) delivering qualified names. Every selection resolves to its craft: owning_config() climbs from the clicked element to the outermost definition, and – the one extension – a variant usage whose owning definition is not bakeable resolves to the definition that types it, so the mission catalog’s teardropQuad variant renders the TeardropQuad shell. The resolved configuration bakes through scene (default: longeron.analysis.grand.scene_for(), which dispatches both the MultiRotor build family and the fleet airframe shells), the viewer swaps to it, the viewer label takes the qualified name, and selection_keys() lights the selected parts – per M0 individual where the scene carries individual ids.

The scene only swaps when the resolved configuration CHANGES: re-selecting inside the shown craft never rewrites mesh_json, and elements that resolve to no bakeable configuration keep the current scene (their highlight semantics are unchanged – affirmative matches pop, no match clears). showing names the configuration the viewer’s initial mesh renders so re-selecting it is such a no-op too. decorate maps (qname, mesh) -> mesh immediately before each swap is written – the grand dashboard re-appends its translucent view cone to its home craft here – so a swap is always ONE traitlet write.

With bidirectional (the default), a mesh pick selects the matching source node exactly as in link_selection() (a picked M0 individual id selects the usage it derives; background picks clear), driving source.view.selection.ids on a diagram and source.selected on an explorer-protocol source.

Rebinding is idempotent: a viewer holds ONE binding, and binding again (any source) unbinds the previous one first. Returns a ConfigViewBinding; neither diagram on_select nor the explorer protocol expose observer disposal, so unbind leaves those callbacks attached but inert (the link_selection() caveat).

Return type:

ConfigViewBinding

longeron.analysis.link.individual_qname(key)[source]

The M1 usage qualified name an M0 individual id derives from.

longeron.m0.interpret() ids are dotted feature paths whose segments optionally carry a #indexRotorcraft::QuadCopter#0. motors#2 is the third motor individual of the root QuadCopter. The derivation strips each segment’s instance index and joins the segments with :: (the first segment is already a qualified name), so that id derives Rotorcraft::QuadCopter::motors – the one usage all four motor individuals belong to. Returns None for a key that carries no instance index (a plain qualified name or a bare part name), so pure-qname keys keep their exact semantics.

Return type:

str | None

Wire diagram clicks to 3D highlights (and mesh picks back).

diagram is an interactive diagram from longeron.diagrams (node ids are qualified names), viewer a widget from longeron.widgets.viewer3d.mesh_viewer(). Every browser (or programmatic) selection on the diagram resolves through selection_keys() and lands on the viewer’s highlight_json – affirmative matches pop and dim the rest, no match clears. A convenience part_map (mesh part name -> qualified name, see longeron.analysis.geometry.tag_parts()) tags the viewer’s current mesh(es) in place at link time.

With bidirectional (the default), a plain click on a mesh (reported by the viewer’s raycaster on picked_json) selects the matching diagram node by qualified name – a picked M0 individual id selects the usage it derives (individual_qname()): the diagram has no individual nodes, so M0 -> M1 is a many-to-one projection; picks that resolve to nothing in the model – the background, or an untagged part – clear the diagram selection. on_pick preserves what the projection discards: it is called on every pick report with the raw key list exactly as the raycaster wrote it (the individual id for a per-instance part, [] for a background click), before the diagram selection is driven, and it fires even with bidirectional=False. One traitlets caveat: repeating the identical pick twice in a row (same part, or background twice) does not re-fire – equal traitlet values coalesce – so the second click is a no-op until something else changes the pick.

Returns an unlink() callable that deactivates both directions and clears the highlight. (longeron.diagrams.on_select() exposes no disposal handle, so its observer stays attached but inert after unlink – the one seam this glue cannot close.)

Return type:

Callable[[], None]

longeron.analysis.link.owning_config(model, element)[source]

The top-level configuration definition that owns element.

Climbs the ownership chain from element (an element or a qualified name) to the outermost Definition whose owner is a package – for the DeepScout program, Rotorcraft::TriCopter::tailMotor (or any attribute, connection, or nested part of the tricopter) resolves to the TriCopter definition itself, and a configuration resolves to itself. Pair with longeron.analysis.grand.drone_scene() so a diagram selection anywhere inside a configuration renders THAT configuration’s geometry (the tri boom, the hexa’s six arms, the coax stacks), not a hardcoded build. The outermost definition of ANY kind is returned (a calc def resolves to itself); callers that render hand the result to drone_scene, which rejects non-assembly shapes loudly. Returns None for elements owned by no definition (a package, the model root).

Return type:

Definition | None

longeron.analysis.link.selection_keys(model, elements, keys, *, interpreter=None)[source]

The mesh identity keys a diagram selection resolves to.

keys are the identities present in a scene (each part’s tagged key or bare name); elements are the selected model elements as delivered by longeron.diagrams.on_select(). A key matches a selected element’s qualified name exactly or nested under it (A::b matches selecting A); a selected Definition also matches through every usage directly typed by it. A key that is an M0 individual id additionally matches through the usage qualified name it derives (see individual_qname()), so selecting the one motors usage matches every motors#i individual key. Untagged keys (bare part names) only ever match themselves, so an untagged scene stays inert.

Return type:

set[str]