Linked selection¶
Requires the viz extra plus the vendored ipyelk for the diagram side
(pip install "longeron[viz]").
Linked selection between 2D diagrams and the 3D viewer.
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 : Motorlights up forMotor) – 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()– hereRotorcraft::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:
objectThe disposable handle
bind_config_view()returns.currentis the qualified name of the configuration the viewer shows (theshowinghint until the first swap, elseNone).scenesis the bake cache: configuration qualified name ->(mesh, part_map), withNonecaching a definition the scene baker rejected, so failing bakes are not retried on every click.unbind()is idempotent, and a binding replaced by a newerbind_config_view()call on the same viewer is already inactive.
- 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.
sourceis anything with the house selection surface: an interactive diagram fromlongeron.diagrams(selections arrive throughlongeron.diagrams.on_select()), or any widget with the explorer protocol’son_select(callback)/selectedpair (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’steardropQuadvariant renders theTeardropQuadshell. The resolved configuration bakes throughscene(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, andselection_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).showingnames the configuration the viewer’s initial mesh renders so re-selecting it is such a no-op too.decoratemaps(qname, mesh) -> meshimmediately 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 inlink_selection()(a picked M0 individual id selects the usage it derives; background picks clear), drivingsource.view.selection.idson a diagram andsource.selectedon 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 diagramon_selectnor the explorer protocol expose observer disposal, sounbindleaves those callbacks attached but inert (thelink_selection()caveat).- Return type:
- 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#index–Rotorcraft::QuadCopter#0. motors#2is the third motor individual of the rootQuadCopter. The derivation strips each segment’s instance index and joins the segments with::(the first segment is already a qualified name), so that id derivesRotorcraft::QuadCopter::motors– the one usage all four motor individuals belong to. ReturnsNonefor a key that carries no instance index (a plain qualified name or a bare part name), so pure-qname keys keep their exact semantics.
- longeron.analysis.link.link_selection(diagram, viewer, model, *, part_map=None, bidirectional=True, on_pick=None)[source]¶
Wire diagram clicks to 3D highlights (and mesh picks back).
diagramis an interactive diagram fromlongeron.diagrams(node ids are qualified names),viewera widget fromlongeron.widgets.viewer3d.mesh_viewer(). Every browser (or programmatic) selection on the diagram resolves throughselection_keys()and lands on the viewer’shighlight_json– affirmative matches pop and dim the rest, no match clears. A conveniencepart_map(mesh part name -> qualified name, seelongeron.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 onpicked_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_pickpreserves 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 withbidirectional=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 afterunlink– the one seam this glue cannot close.)
- 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 outermostDefinitionwhose owner is a package – for the DeepScout program,Rotorcraft::TriCopter::tailMotor(or any attribute, connection, or nested part of the tricopter) resolves to theTriCopterdefinition itself, and a configuration resolves to itself. Pair withlongeron.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 todrone_scene, which rejects non-assembly shapes loudly. ReturnsNonefor elements owned by no definition (a package, the model root).- Return type:
- longeron.analysis.link.selection_keys(model, elements, keys, *, interpreter=None)[source]¶
The mesh identity keys a diagram selection resolves to.
keysare the identities present in a scene (each part’s taggedkeyor barename);elementsare the selected model elements as delivered bylongeron.diagrams.on_select(). A key matches a selected element’s qualified name exactly or nested under it (A::bmatches selectingA); a selectedDefinitionalso 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 (seeindividual_qname()), so selecting the onemotorsusage matches everymotors#iindividual key. Untagged keys (bare part names) only ever match themselves, so an untagged scene stays inert.