API
The exported names of BeamletOpticsGUI. The functions and types of BeamletOptics that the GUI builds on (render!, live_render!, update_render!, remove_render!, pick_object and the render handle protocol) are documented on the BeamletOptics site.
BeamletOpticsGUI — Module
BeamletOpticsGUIThe interactive GUI of BeamletOptics: live_view, kinematic_controls!, view_cube! and the cards and widgets of the live view. The components and beams are drawn by the live rendering of BeamletOptics (live_render!), via its render handle protocol.
Live view functions
BeamletOpticsGUI.live_view — Function
live_view(system => beam, ...; kwargs...)
live_view(system, beam; kwargs...)
live_view(system, ...; kwargs...)Opens a complete interactive window for one or several pairs of system and beam, or for systems without a source, e.g. an empty table live_view(System()) that gets its sources in the window: a 3D view in which all components can be moved via kinematic_controls!, a status line and optional sliders. After each change, all systems are initialized (BeamletOptics.initialize!, which empties the detectors) and solved again, and the beams and the shown detector views are updated. Returns a LiveView, which can be shown via display.
A source is given once: live_view(sys1 => beam, sys2 => beam) throws an ArgumentError, since a source belongs to at most one system. Systems are added and removed in the window (add_system!, remove_system!), an object may belong to several systems or to none, and each system is traced on its own; see "Systems" and "Tracing per system" in the method below.
The card of a Detector has a page "Results" with its detector view (spot diagram, PSF or intensity, with metrics such as centroid, RMS or 1/e² radius and power), which collapses to a thumbnail and, as a field, has a colorbar and can be shown on a logarithmic color scale with profiles. A click on a beam shows its position, path length and, for Gaussian beamlets, its radius and curvature, the "measure" toggle measures distances and angles between components and beams. The key g zooms to the selection, "home", the "views" menu and "save view" set and store camera views. While moving, beam groups are solved only for their rendered beams (preview = true), the full group once the movement pauses.
Main keyword arguments: detectors (:auto or []: every detector has its page "Results", no card is pinned at start; a vector of pd, pd => kind or pd => (kind, kwargs) pins the cards of these detectors at start, with kind one of :auto, :spot, :psf and :intensity and the options n, colorscale, colorrange, colorbar, profiles and expanded), on_change = (gui, obj) -> nothing (called after full solves), sliders = ["label" => (range, callback)], system_kwargs, beam_kwargs, preview = true, views = ["name" => (eye, lookat, up)], lighting = :studio (see BeamletOptics.studio_lighting!), edges and size. extras = [obj => render_kwargs, ...] adds objects without a system that are rendered, selected, moved and hidden like the components, but never traced, e.g. a housing from an STL file (NonInteractableObject); the card of such mechanics has an opacity slider. auto_trace is the start value of the auto tracing of every system. sidebar_width and dock_height set the start sizes of the sidebars and the dock of layout = :app, which are resized with the mouse. background_card = obj (or gui -> obj, nothing for none) shows the card of an object without a place in the scene, e.g. an environment, after a click on the empty background while nothing is selected; its card_rows get obj itself. layout = :app arranges the window like an application, with a toolbar, collapsible sidebars (object tree with selection and visibility, sliders, the cards of the selection and of pinned objects), an analysis dock with a tab per add_panel! (collapsed until the first one exists) and a status bar, in the colors of theme = :light or :dark; the default layout = :compact fills the window with the 3D view, places the panels of add_panel! in a column on its right and shows the tools, the status and the help on demand over the 3D view. The component cards and the progress window have the colors of the theme in both layouts. Own panels, widgets and tools are added to either layout via add_panel!, add_controls! and add_tool!. All other keyword arguments are passed to kinematic_controls!. See the method below for details.
BeamletOpticsGUI.retrace! — Function
retrace!(gui)
retrace!(f, gui)Solves the systems of the live_view window gui again after a change from code, e.g. a widget of add_controls! or a tool of add_tool!, like after a slider of the sliders kwarg: the render of the objects is updated, then the systems are solved (with preview tracing and deferred solves of slow systems, as while moving), or the beams are marked as outdated if auto tracing is off. on_change and the update of the panels of add_panel! run after the full solve.
With f, f() makes the change first, after a solve running in the background is cancelled: objects must not change while they are traced, hence changes of objects belong into f.
BeamletOpticsGUI.export_changes — Function
export_changes(gui; io = stdout, clipboard = false)Returns the changes of the poses in the live_view window gui as Julia code, i.e. one rotate3d! about the position of the object and one translate_to3d! per moved object, relative to its initial pose. The code is printed to io and copied to the clipboard if clipboard is true. See the method below for details.
BeamletOpticsGUI.export_script — Function
export_script(gui::LiveView; io = stdout, clipboard = false) -> StringReturns the setup of the gui as a complete Julia script, which is printed to io and copied to the clipboard if clipboard is true and a clipboard is available. The "Script" button of the gui prints the script and copies it to the clipboard. Unlike export_changes, which lists the changes to apply in the script that created the view, the script stands on its own.
The script consists of, in this order:
using BeamletOptics- per system its top-level objects, then the system itself, e.g.
system = System([lens, m1]) - the sources
- a
solve_system!(system, source)per pair of the view - after the comment line
# Live view:using BeamletOpticsGUI, GLMakieand the callgui = live_view(…)with the pairssystem => source, the systems without a source and thelabels. The lines above it run without a window. add_component!(gui, name; system = :none)for each object and source without a system, which the call oflive_viewdoes not contain: it is shown, but not traced. A source without a system has nosolve_system!and no pair inlive_view.
An object that belongs to several systems is constructed once and listed in each of them.
An object or source of the catalog (see component_catalog), or one that was added with its code (see add_component!), is its constructor call followed by the rotate3d! about its position and the translate_to3d! from its pose as constructed to its current pose, with the full precision of Float64. The constructor of any other object, e.g. of one that the view started with, is not known: a comment with its variable, type and position [m] marks where to construct it, in its pose when the view got it, and the lines that use its variable are commented out (the change of its pose since then, its push! to its system, its solve_system!, its entries in live_view). A StaticSystem with such an object is a comment as a whole.
The variables are named after the labels of live_view if they are valid variable names that the code does not use otherwise, i.e. no name exported by BeamletOptics or this package, such as Detector or live_view, and not system or gui; then after the label in lowercase if that is free, e.g. detector, otherwise obj1, obj2, …, and the systems system, or system1, system2, … if the view shows several. Components that were removed are not part of the script. Neither are the looks (colors, opacity, hidden objects), the clip planes, the objects of the extras kwarg of live_view and its other keyword arguments. A component that was added without a system or taken out of its last one is part of the script (item 6), unlike an extra.
gui = live_view(System())
# add components and sources from the catalog, or with `add_component!(...; code)`, move them, then
code = export_script(gui)BeamletOpticsGUI.open_system — Function
open_system(gui, system; display = true, kwargs...) -> LiveViewOpens the system of the live_view window gui in a new window with its components and the sources that are traced through it, like the icon of the two windows in the head of the card of the system (shown after a click on the system in the object tree or in the component menu). Useful for a view of several systems, one of which is worked on in a window of its own. Returns the new live view, which is shown unless display is false.
The new window is a live view of its own: it takes over the layout, the theme, the catalog, the names and labels, how the system and the beams are drawn, the beams that are switched off, the tracing, the snapping, the table and the settings of the controls (e.g. the steps of the keys and the constraints) of the gui, but not its clip planes, sliders, panels, tools, pinned cards, hidden objects and camera. The kwargs are those of live_view and take precedence, e.g. layout = :app or size = (1000, 700).
Both windows are linked: they show the same objects, not copies. A component or source that is moved, added, removed or edited in one window changes in the other one as well, with the same name, and the variables of the script stay valid. Only the window in which something changed solves the systems, the other one shows the result; while the beams of one window are outdated, e.g. without auto tracing, those of the other one are dimmed as well, and tracing in either window brings both up to date. The switch of the auto tracing is one for the linked windows: switching it in one of them switches it in the other ones, also the auto_trace kwarg given here. Everything else is kept per window: the selection, the camera, colors, hidden objects, clip planes and the undo history, from which the entries on a component that the other window added or removed are dropped. The link ends when one of the windows is closed.
A system can be opened several times, and also from the new window. It throws an ArgumentError for a system that the gui does not show.
BeamletOpticsGUI.add_panel! — Function
add_panel!(f, gui, title::AbstractString; select = false) -> GridLayoutAdds an own analysis panel named title to the live_view window gui, placed by its layout: with layout = :compact in a column right of the 3D view (created by the first panel), with the title above it; with layout = :app as a new tab of the analysis dock, behind the tabs that exist (the active tab stays active unless select = true). The dock stays collapsed until the first panel exists.
f(layout) builds the content into the given GridLayout, e.g. an Axis with plots, and may return a function update(gui), which is called after each full solve (not after the preview solves while moving), like the on_change of live_view, and once right away. In the app layout, update only runs while the tab is shown; a hidden panel is updated when its tab is opened or the dock is expanded. Any other return value of f, e.g. the last plot of a do block, means no update. Errors in update are logged once and do not interrupt the interaction. Returns the layout.
gui = live_view(system, beam)
add_panel!(gui, "Power") do layout
ax = Axis(layout[1, 1]; xlabel = "Update", ylabel = "P [mW]")
pts = Observable(Point2f[])
lines!(ax, pts)
return gui -> (push!(pts[], Point2f(length(pts[]) + 1, 1e3 * optical_power(pd))); notify(pts))
endBeamletOpticsGUI.add_controls! — Function
add_controls!(f, gui, title::AbstractString) -> GridLayoutAdds own widgets to the live_view window gui, placed by its layout: with layout = :compact as an entry title of the tool rail (the button "⋯"), which opens the widgets in a popover next to the rail; with layout = :app as a section title of the left sidebar, below "Parameters". f(layout) builds the widgets (e.g. Button, Toggle, Menu, Textbox, or an own widget type, see card_input) into the given GridLayout and connects them. Returns the layout.
Controls are meant for parameters without an object in the scene, e.g. a setting of the whole setup; the widgets of an object belong on its card, see card_rows.
Textboxes and Menus in the layout take the keyboard like those of the live view: while a box is focused or a menu is open, the keys of the 3D view and the camera are ignored. They are collected once f returns, widgets added later are not. An input that changes the optics solves the systems again via retrace!, which makes the change after a running solve is cancelled.
Like the builder of add_panel!, f may return a function update(gui), which is called once right away and after each full solve (not after the preview solves while moving), e.g. to show the current value of a parameter after an undo; update must not change objects. Any other return value of f, e.g. the last listener of a do block, means no update. Errors in update are logged once and do not interrupt the interaction.
add_controls!(gui, "Laser") do layout
box = Textbox(layout[1, 1]; placeholder = "power [mW]", width = 100)
on(box.stored_string) do s
retrace!(() -> set_power!(laser, 1e-3 * parse(Float64, s)), gui)
end
return gui -> card_show!(box, 1e3 * power(laser))
endBeamletOpticsGUI.add_tool! — Function
add_tool!(f, gui, name::AbstractString; icon = :object, key = nothing, toggle = false, tooltip = name)Adds a tool to the live_view window gui: a button that calls f(gui), or with toggle = true a toggle that calls f(gui, active::Bool) whenever it is switched. With layout = :app, an icon button (or toggle) at the end of the toolbar, with the icon and the tooltip; with layout = :compact, an entry (icon and name) of the tool rail opened by the button "⋯". A tool with a key is listed in the help card under "Own tools".
icon is a name of the icon set of the app layout, e.g. :measure, :export or :chart (an unknown name throws an ArgumentError that lists the valid ones, in any layout), or an own Makie.BezierPath, drawn like a scatter marker: in the unit square centered at the origin, e.g. BezierPath("M -0.3 -0.3 L 0.3 -0.3 L 0 0.3 Z") for a triangle.
key, a free Makie.Keyboard.Button such as Keyboard._2 or Keyboard.f5, presses the button (or switches the toggle) from the 3D view; it is shown in the tooltip or label. Keys taken by the live view, the kinematic controls, Makie's Camera3D (which binds almost all letters) or another tool throw an ArgumentError that names the existing binding. The key is ignored while a textbox of the window is focused. Errors in f are logged once. Returns the widget, with clicks (button) or active (toggle).
Scripting the live view
The gestures of the user from code: the verbs of BeamletOptics with the live view as first argument.
BeamletOptics.translate3d! — Method
translate3d!(gui::LiveView, obj, offset)
translate_to3d!(gui::LiveView, obj, position)Moves the object obj of the live_view window gui by offset [m], or to position [m], like a gesture of the user: the drawing of obj (also of the parts of a group), the selection box and the cards follow, the systems are solved like after a drag (with preview tracing and deferred solves of slow systems, or marked as outdated if auto tracing is off, see wait_solve to wait for the result), on_change is called after the full solve, and the move is one entry of the undo history (Ctrl+Z in the window). Unlike translate3d!(obj, offset), which changes the object only, the window is not told about it.
The constraints of obj apply as for the mouse: the offset is projected onto its allowed move axes. obj can be any object, group, source or extra of the view, selected or not. Throws an ArgumentError if obj is static, is not part of the view, or if all its move axes are locked.
translate3d!(gui, lens, [0, 1e-3, 0]) # 1 mm along the optical axis
translate_to3d!(gui, lens, [0, 0.12, 0])
wait_solve(gui)BeamletOptics.rotate3d! — Method
rotate3d!(gui::LiveView, obj, axis, θ)
rotate3d!(gui::LiveView, obj, R)Rotates the object obj of the live_view window gui about its position, by the angle θ [rad] about axis (counter-clockwise, global frame) or by the rotation matrix R, like a gesture of the user, see translate3d!(::BeamletOpticsGUI.LiveView, ::Any, ::AbstractVector) for what follows and for the errors.
The constraints of obj apply as for the mouse: if some rotation axes are locked, axis must be parallel to an allowed one (the local x or y axis of obj or the rotation axis of the controls), otherwise an ArgumentError is thrown. For R, this is its rotation axis.
BeamletOpticsGUI.select! — Function
select!(gui::LiveView, obj)
select!(gui::LiveView, objs::AbstractVector)
select!(gui::LiveView, ::Nothing)Selects the object obj of the live_view window gui like a click on it: its selection box and gizmo are shown, and its card opens or the inspector shows it. nothing clears the selection, like a click on the background. A group is selected as a whole. A vector objs selects all its objects, like Ctrl+click (Cmd on macOS) on each: they are moved, rotated, hidden and removed as one; a vector of one object selects it like obj, an empty one clears the selection. Throws an ArgumentError if an object is not part of the view, if one of objs is a part of another one or a clip plane, or if the spectator mode is on, see spectator!, in which nothing can be selected.
BeamletOpticsGUI.spectator! — Function
spectator!(gui::LiveView, on::Bool = true; help = true, background = nothing, cards = false, view_cube = false)Switches the spectator mode of the live_view window gui on or off, like the key v: on, the selection is cleared, the tools (the button "⋯" and the tool rail of the compact layout, the toolbar and the sidebars of the app layout), the status line, the floating cards, the view cube, the markers of the sources, the selection box and the gizmo are hidden, the 3D view fills the window and all mouse and keyboard input goes to the camera. The objects can still be moved from code, see translate3d!(::BeamletOpticsGUI.LiveView, ::Any, ::AbstractVector). Off, everything comes back as it was, e.g. a sidebar that was collapsed stays collapsed, and the object that was selected before is selected again, if it is still in the view.
Keyword arguments
They apply while the mode is on and are reset when it is left, also by the key v:
help = true: the help pill with the chip of the mode stays, which tells how to leave the mode.falsehides it as well, such that only the 3D view is left, for screenshots and videos (in the window:Shift+V)background = nothing: the color of the background of the 3D view and the window, e.g.:blackorRGBf(0.1, 0.1, 0.12)(anythingMakie.to_coloraccepts), by default the one of the themecards = false: keep the pinned cards, e.g. of the detectors of thedetectorskwarg oflive_view: floating in the 3D view withlayout = :compact, in the inspector sidebar (which stays) withlayout = :appview_cube = false: keep the view cube
Calling it again with on = true applies the new keyword arguments, relative to the state before the mode. Use Makie.record with a gui to record a video.
spectator!(gui; help = false, background = :black)
wait_solve(gui)
save("setup.png", gui.fig; px_per_unit = 2)
spectator!(gui, false)BeamletOpticsGUI.wait_solve — Function
wait_solve(gui::LiveView; timeout = Inf) -> BoolWaits until the live_view window gui is up to date: a solve running in the background (see progress_delay of live_view) is shown, and a solve that was deferred while moving (trace_budget, idle_delay), the full solve after a preview and the refinement of coarse detector views are done at once instead of when the movement pauses. Returns true when it is settled and false after timeout [s], with the solve still running. With auto tracing off, the beams stay outdated, see retrace!. Call it after moves from code before reading detectors or saving an image.
The windows that show a system of the gui (see open_system) are waited for as well: a solve of one of them traces the beams of the gui, which follows when it is done.
Makie.record — Method
record(f, gui::LiveView, path, iter; spectator = true, framerate = 30, px_per_unit = 1, kwargs...)Records a video of the live_view window gui to the file path (its extension is the format, e.g. .mp4 or .gif), like Makie.record of a figure, but made for a live view: for each element i of iter, f(i) changes the window, e.g. moves objects with translate3d!(::BeamletOpticsGUI.LiveView, ::Any, ::AbstractVector) (the plain verbs of BeamletOptics would not redraw), then the solve is finished (see wait_solve), the window is told a frame has passed (tick: moves the cards, camera transitions) and one frame of framerate frames per second is rendered offscreen, with px_per_unit pixels per unit of the size of the window (px_per_unit = 2 doubles the resolution). Returns path. The video encoder is the one of Makie (a ffmpeg of the Makie installation).
With spectator = true, the window is in the spectator mode without its help while recording, i.e. only the 3D view is recorded, see spectator!, and as before afterwards; a mode that is on already keeps its options, e.g. a background or the pinned cards. Other kwargs go to Makie.record, e.g. compression. Recording to a window that is open at the same time is not supported.
Makie.record(gui, "slide.mp4", range(0, 5e-3, 60); framerate = 30, px_per_unit = 2) do d
translate_to3d!(gui, lens, [0, 0.1 + d, 0])
endAdding and removing components
BeamletOpticsGUI.add_component! — Function
add_component!(gui, obj; system = nothing, select = true, label = nothing, code = nothing) -> objAdds the object obj (an AbstractObject or an object group) to a system of the live_view window gui at runtime: obj is pushed to the system, rendered and registered with the controls, i.e. it is selected, moved, hidden and exported like the components the view started with. Then the systems are solved again, or the beams are marked as outdated without auto tracing. Place obj before adding it, e.g. via translate_to3d!.
system is the System of the gui that gets obj: by default the system of the selected or inspected object (or the inspected system itself), otherwise the first System of the view. All beams paired with that system are traced through obj. system = :none adds obj without a system: it is shown, moved and exported like any component, but not traced, until it becomes a member of a system. A StaticSystem can not be changed: it throws an ArgumentError, like a system that is not shown in the gui and, without a system, an obj that the gui shows already. select = true selects obj afterwards (or shows its card if it is not movable), label names it like an entry of the labels kwarg of live_view.
An object belongs to any number of systems of the view: for an obj that the gui shows already (at its top level, i.e. not an object of a group), add_component!(gui, obj; system = sys2) makes it a member of sys2 as well, like "+" of that system in the window. It stays one object, with one pose, one card and one set of plots, and the beams of all its systems are traced through it, e.g. a mirror that the transmitter and the receiver of a lidar share.
code is the constructor call of obj as Julia code, e.g. "ThinLens(0.05, -0.05, 0.0254, 1.5)", which constructs obj in the pose that it has when it is added. export_script then writes this call followed by the change of the pose of obj since it was added, like for a component of the catalog; without code the constructor is not known and the script marks where to construct obj. Give code for an object built from code, e.g. by a tool or an agent, before you move it, and move it afterwards (e.g. in retrace!): an object that was moved before it was added with code is exported in a wrong pose, which is not checked. The page "Edit" and copying stay for the components of the catalog, whose parameters are known.
code must be one Julia expression that runs on its own in the script, i.e. after using BeamletOptics only: write the values out and do not refer to variables or functions of the session, e.g. a glass nbk7, which the script does not define. An ArgumentError is thrown, and nothing is added, if code is empty, does not parse or has several statements; what it evaluates to is not checked.
The components of the catalog are added this way once they are placed, see component_catalog. remove_component! removes a component again, export_changes lists the added and removed components.
add_component!(gui, source; system = nothing, select = true, label = nothing, beam_kwargs = (;), code = nothing) -> sourceAdds the source (a beam or a beam group, e.g. a Beam, a GaussianBeamlet or a CollimatedSource) to the gui: it is traced through the system with every solve, rendered with the beam_kwargs (those of live_render! of the beam, as an entry of the beam_kwargs of live_view; by default each ray in the color of its wavelength, i.e. color = :wavelength, e.g. red for 632.8 nm and a dark red for infrared light, and a beam group with render_every = 5; beam_kwargs = (; color = :blue) sets one color for all rays) and gets a marker, with which it is selected and moved like the sources the view started with, also in a view with movable_sources = false. system is any system of the gui, also a StaticSystem, which a source does not change; by default the system that gets a component, otherwise the first system of the view. A source belongs to at most one system: system = :none adds it without one, which shows its marker, but neither traces nor draws it. For a source that the gui shows already, a system moves it to that system (or to none), and without a system it throws an ArgumentError. A view may start without a source, see live_view(system). code is the constructor call of the source in its current pose, as for a component.
gui = live_view(System())
add_component!(gui, Beam([0.0, 0, 0], [0.0, 1, 0], 632.8e-9); label = "laser")
lens = ThinLens(50e-3, -50e-3, 25.4e-3, 1.5)
translate_to3d!(lens, [0, 0.1, 0])
add_component!(gui, lens; label = "lens")BeamletOpticsGUI.remove_component! — Function
remove_component!(gui, obj; system = nothing) -> objRemoves the object obj from the live_view window gui, like "remove" on its card or the key Delete while it is selected: obj is deleted from all its systems, its plots and its cards are removed, and the systems are solved again, or the beams are marked as outdated without auto tracing. Adding and removing are entries of the undo history of the controls: Ctrl+Z in the window brings a removed object back, as does add_component!.
With a system, obj is only taken out of that System, like "−" of the system in the window: it stays in the view, as a member of its other systems or, after its last one, without a system, i.e. shown but not traced.
obj is a top-level object (or object group) of the gui: of its Systems, or one without a system, e.g. an extra. An object of a group can not be removed on its own, remove the group instead. It throws an ArgumentError, like an object of a StaticSystem and an object that is not shown in the gui.
remove_component!(gui, source; system = nothing) -> sourceRemoves the source (a beam or a beam group) from the gui: it is no longer traced, and its beam, its marker and its cards are removed. Every source can be removed, also the last one, which leaves a view without a source; it throws an ArgumentError for a beam that is no source of the gui. With the system that it is traced through, the source only loses its system: it keeps its marker, but is neither traced nor drawn.
BeamletOpticsGUI.add_system! — Function
add_system!(gui; label = nothing, select = true) -> SystemAdds a new, empty System to the live_view window gui at runtime, like "System" among its tools, and returns it. label names it, by default "System n". With select, its card is shown and it gets the next component of the catalog. Its components and sources are added with add_component!, e.g. an object that another system holds already:
rx = add_system!(gui; label = "Receiver")
add_component!(gui, mirror; system = rx) # the mirror of the transmitter, in both systems
add_component!(gui, Beam([0.0, 0.1, 0], [0.0, -1, 0], 905e-9); system = rx)Each system is traced on its own, see "Tracing per system" of live_view. remove_system! removes a system again; both are entries of the undo history.
BeamletOpticsGUI.remove_system! — Function
remove_system!(gui, system) -> systemRemoves the system from the live_view window gui, like "Remove system" on its card. Nothing is deleted: its sources and its objects that are in no other system of the view stay in the view without a system, i.e. shown but not traced, and the objects of the system itself are not changed. It throws an ArgumentError for the last system of the view, for a system that the gui does not show and for one that is open in another window, see open_system.
BeamletOpticsGUI.CatalogEntry — Type
CatalogEntry(name, constructor; group = "Components", params = [], code_name = string(nameof(constructor)), icon = nothing, source = false)An entry of the component catalog of live_view, see component_catalog: a component that the user picks, parametrizes and places in the 3D view. constructor is called with the values of the params (numbers, see CatalogParam, and glasses, see CatalogGlass) and returns the AbstractObject (or object group) to add, in the pose in which it is constructed; the catalog then moves it to where it is placed.
An entry with source = true is a source: constructor is called as constructor([0, 0, 0], [0, 1, 0], values...; keywords...), i.e. with the position and the direction of the source before the values of the params, and returns the beam or the beam group to add, e.g. Beam or CollimatedSource. A constructor that returns an object for an entry with source = true, or a beam for one without, throws an ArgumentError when the entry is placed.
The catalog shows the entries by their group, each as a tile with its name and its icon: the name of an icon of the live view as for add_tool! (e.g. :lens, an unknown name throws an ArgumentError) or an own Makie.BezierPath. An entry without an icon shows the icon of its group; a group that is not one of the built-in ones shows the icon of its first entry that has one.
export_changes prints the component as the call code_name(values...; keywords...), a source as code_name([0.0, 0.0, 0.0], [0.0, 1.0, 0.0], values...; keywords...), each followed by the calls that move it to its pose; hence constructor should be a function or type that the user's script can call by that name, e.g. ThinLens, not an anonymous wrapper.
entry = CatalogEntry("Thin lens", ThinLens; group = "Lenses", icon = :lens, params = [
CatalogParam("R1", 50e-3; unit = "mm", scale = 1e-3),
CatalogParam("R2", -50e-3; unit = "mm", scale = 1e-3),
CatalogParam("diameter", 25.4e-3; unit = "mm", scale = 1e-3),
CatalogGlass()])BeamletOpticsGUI.CatalogParam — Type
CatalogParam(name, default; unit = "", scale = 1.0, keyword = nothing, integer = false,
presets = ())A numeric parameter of a CatalogEntry, passed to its constructor: the default value in the units of the constructor (SI in BeamletOptics, e.g. [m]), shown and entered in the catalog as value / scale with the unit, e.g. CatalogParam("diameter", 25.4e-3; unit = "mm", scale = 1e-3) for a box that shows 25.4 mm. A parameter with keyword = :name is passed as that keyword argument, the other parameters are passed as positional arguments in their order.
A parameter with integer = true is passed as an Int, e.g. the num_rays of a source: its default must be a whole number, otherwise an ArgumentError is thrown, and the catalog only takes whole numbers for it.
presets are named values of the parameter, as pairs "name" => value in the units of the constructor, e.g. presets = ["532 nm" => 532e-9, "632.8 nm" => 632.8e-9] for the laser lines of a wavelength. The catalog shows them in a menu next to the box of the parameter: choosing one writes its value into the box, and the menu shows "custom" for any other value of the box.
BeamletOpticsGUI.CatalogGlass — Type
CatalogGlass(name = "glass"; default = "N-BK7", n = 1.5, keyword = nothing)The glass of a CatalogEntry, passed to its constructor as the refractive index n(λ): the catalog shows a menu of the glasses of catalog_glasses and "constant", for which a box takes a constant refractive index with the default n. default is the name of the glass that is chosen at first, or "constant"; a name that catalog_glasses does not hold throws an ArgumentError. name and keyword as for CatalogParam.
The constructor gets the glass as it is stored in catalog_glasses, e.g. a SellmeierEquation, and for "constant" a function λ -> n, which every constructor of BeamletOptics with a refractive index takes.
BeamletOpticsGUI.component_catalog — Function
component_catalog() -> Vector{CatalogEntry}The catalog of components that a live_view window offers by default (its catalog kwarg): the entries of BeamletOpticsGUI for the sources and the components of BeamletOptics (sources, lenses, mirrors, beamsplitters, prisms, polarizers and the detector) and the entries that packages added. The catalog is the widget "Components", a window over the 3D view or, in the app layout, a section of the left sidebar: the entry is chosen by its group and its tile, its parameters are typed into boxes, its glass is chosen in a menu, and "Place" attaches the component to the mouse, see "Adding and removing components" of live_view.
A package with own components adds its entries (see CatalogEntry) to the returned vector, e.g. in the __init__ of its package extension on BeamletOpticsGUI; views opened afterwards show them. A single view gets other entries via live_view(...; catalog = entries), none with catalog = CatalogEntry[].
push!(component_catalog(), CatalogEntry("My lens", MyLens; group = "Lenses",
params = [CatalogParam("f", 100e-3; unit = "mm", scale = 1e-3)]))BeamletOpticsGUI.catalog_glasses — Function
catalog_glasses() -> Vector{Pair{String, Any}}The glasses that the component catalog of live_view offers for the entries with a CatalogGlass, as name => n, where n(λ) is the refractive index at the wavelength λ [m] as BeamletOptics takes it, e.g. a SellmeierEquation. The built-in glasses are common optical glasses and crystals with the dispersion formulas of the database refractiveindex.info: N-BK7, fused silica, CaF2, N-SF11, N-SF10, N-SF6HT, N-SF5, N-F2, N-BAF10 and N-LAK22.
A package adds its glasses to the returned vector; views opened afterwards show them. export_changes prints a SellmeierEquation and a DiscreteRefractiveIndex as their constructor call and any other glass via repr, hence an own glass should be one of the two or a named function.
push!(catalog_glasses(), "My glass" => SellmeierEquation(1.04, 0.23, 1.01, 0.006, 0.02, 103.6))Interactive helpers
BeamletOpticsGUI.kinematic_controls! — Function
kinematic_controls!(axis, handle; kwargs...)Enables mouse and keyboard controls for moving and rotating the objects of a live-rendered system handle within the axis. Returns a controller that can be removed via close.
BeamletOpticsGUI.view_cube! — Function
view_cube!(ls::LScene; size = 110, corner = :top_right, duration = 0.3)Adds a CAD-style view cube to a corner of the 3D view ls. The cube rotates with the camera, and a left click on a face, an edge or a corner of the cube moves the camera to the corresponding standard view, keeping the lookat point and the distance of the camera. Returns a ViewCube, which can be removed via close. The view cube is shown by default in live_view.
The camera looks at the clicked side of the system, i.e. it is placed on the side of the clicked face:
| face | camera at | up |
|---|---|---|
Top | +z | +y |
Bottom | -z | +y |
Front | -y | +z |
Back | +y | +z |
Right | +x | +z |
Left | -x | +z |
Edges and corners give the diagonal views between the adjacent faces, with +z as the up direction. The region under the cursor is highlighted.
Keyword args
size = 110: [px] edge length of the square viewport of the cubecorner = :top_right: one of:top_right,:top_left,:bottom_rightand:bottom_leftduration = 0.3: [s] duration of the animated transition,0switches the view instantly
Card API
BeamletOpticsGUI.card_rows — Function
card_rows(obj)Rows of the card of obj in live_view, a tuple of CardRows, chosen by multiple dispatch. By default, the rows of the pose, see pose_card_rows; beams, beam groups and sources add the rows of beam_card_rows (toggles and the drawn length of the final rays), a source whose rays can be regenerated (see BeamletOptics.set_num_rays!) a slider for the number of rays, a Detector its signal (the power or the number of rays of its detector view while a view is shown, else the number of hits), mechanics (NonInteractableObject, e.g. a NonInteractableObject(loadpath) of an STL file, and IntersectableObject) a slider for their opacity. A system (AbstractSystem) has no rows by default: its card (shown after a click on its entry in the component menu or the object tree) is the system widget of the window (its tracing, its members and "Remove system", see "Systems" of live_view), which is not part of this method; the rows of a method for an own system type are shown below the list of its members. Add a method for an own type to show its properties or controls on its card, e.g.
BeamletOpticsGUI.card_rows(l::MyLens) = (pose_card_rows(l)...,
CardRow("f", CardWidget(Label; value = (gui, l) -> "$(1e3 * focal_length(l)) mm")))A card exists only for things in the scene (objects, groups, sources, systems, clip planes); a parameter without an object belongs into add_controls!. The card may be rebuilt at any time and shows other objects of the same declarations, hence the widgets keep no state: value and on get the gui and the object. value runs after every move, solve and input and must be cheap. The rows of a system type extend the default ones via invoke(BeamletOpticsGUI.card_rows, Tuple{BeamletOptics.AbstractSystem}, sys).
BeamletOpticsGUI has the methods for the types of BeamletOptics.
BeamletOpticsGUI.pose_card_rows — Function
pose_card_rows(obj)The rows of the pose of obj on its card in live_view, see card_rows: the position x, y, z [mm], where Enter moves obj to the typed coordinate, and the rotations rx, ry, rv [mrad] about the red, green and blue axis of the controls.
BeamletOpticsGUI.beam_card_rows — Function
beam_card_rows(beam)The rows of the beam on the card of a beam, beam group or source beam in live_view, see card_rows. The row beam has the toggle "on", which switches the beam off and on. A beam that is off is neither solved nor drawn, its rays are removed, such that detectors and measurements ignore it, and only its source marker stays; with auto_trace = false the switch marks the beams as outdated instead of solving.
Gaussian beamlets (GaussianBeamlet, AstigmaticGaussianBeamlet and groups of them) add the toggle "beams", which draws the generating beams (chief, divergence and waist rays) as the static render! with show_beams = true does. Polarized beams (their rays are PolarizedRays, e.g. an AstigmaticGaussianBeamlet) add the toggle "polarization", which draws the polarization along the beam, and the rows pol λ and pol amp with sliders for the wavelength [mm] of the drawn curve and its amplitude ([mm], of astigmatic beamlets as a multiple of the beam radius), on logarithmic scales over ranges given by the size of the scene. Of a beam group, the generating beams and the polarization are drawn for its central beam, the one starting nearest the axis of the group. These change the display only. The initial states are set by the live_view keywords beams_off and beam_kwargs (show_beams, show_polarization, pol_λ, pol_amplitude, pol_scale).
The last row, length, has a box with the length [mm] with which the final rays of the beam, i.e. those that hit nothing, are drawn (flen of live_render!: by default 1 m, of Gaussian beamlets 0.1 m, or as given by the beam_kwargs). An input changes the drawn length of the beam and of its overlays in place, nothing is solved; an input that is no positive number is reported in the status line.
The card of an own beam type adds the rows after its own rows, e.g.
BeamletOpticsGUI.card_rows(b::MyBeam) = (pose_card_rows(b)..., beam_card_rows(b)...)BeamletOpticsGUI.card_actions — Function
card_actions(obj)Widgets in the head of the card of obj in live_view, a tuple of CardWidgets, chosen by multiple dispatch like card_rows: by default the action named :hide, an eye icon that hides the object and, crossed out for a hidden object, shows it again (tooltips "Hide" and "Show"); for clip planes the buttons "flip" and "remove"; for systems the eye and :open, an icon of two windows that opens the system in a window of its own (see open_system). The card of an object with parts (a group or a MultiShape object) gets the button "parts ›" after them, which opens its selection card.
A method for an own type extends the default ones, e.g. (invoke(card_actions, Tuple{Any}, obj)..., CardWidget(Button; label = "block", on = ...)).
BeamletOpticsGUI.CardRow — Type
CardRow(cells...)A line of a card of live_view, see card_rows: its cells from left to right, each a text or a CardWidget.
BeamletOpticsGUI.CardWidget — Type
CardWidget(T; name = nothing, value = nothing, on = nothing, solve = false, attributes...)A widget on a card of live_view, see card_rows: a Makie block of the type T, e.g. Label, Slider, Toggle, Textbox, Button or Menu, created as T(position; attributes...) with the attributes, e.g. the range of a Slider or the width. The card places the widget, hides it with the card and keeps clicks on it from the 3D view, and its textboxes take the keyboard. T may be an own widget type with methods of card_input and card_show!, e.g. a Makie.@Block.
value(gui, obj): the value the widget shows for the objectobjof the card, passed tocard_show!: the text of aLabelorButton, the value of aSlider,ToggleorTextbox. Refreshed when the card gets its object, after moves, solves and inputs.on(gui, obj, v): applies an inputvof the widget toobj, a new value of itscard_input: the value of aSlider, the state of aToggle, the entered text of aTextbox, the click count of aButtonor the selection of aMenu. Inputs of a widget withoutonare ignored.solve = true: the input changes the optics, e.g. a parameter ofobj: a running solve is cancelled beforeon, and the systems are solved again afterwards (or the beams are marked as outdated without auto tracing), like after a move, via theon_changeof the controls.name: finds the widget on a card, e.g. in tests.
gui is the LiveView, e.g. for the beams of the last solve or the status line.
BeamletOpticsGUI.card_input — Function
card_input(w) -> Union{Observable, Nothing}The input of the widget w on a card of live_view (see CardWidget): an Observable whose updates are the inputs of the user, each passed as v to the on(gui, obj, v) of the declaration. nothing means that the widget takes no input, e.g. a Label, and is the default for any type. BeamletOpticsGUI has methods for the blocks of Makie: the value of a Slider, active of a Toggle, stored_string of a Textbox, clicks of a Button and selection of a Menu.
An own widget type, e.g. a Makie.@Block or any type constructed as T(position; attributes...) that places itself at a GridPosition, adds a method of card_input and of card_show! and can then be declared as CardWidget(T; ...) on a card. Widgets built in add_controls! may use both functions as well, the controls do not call them. An own type that consists of several blocks deletes them in Base.delete!, which the card calls when it rebuilds its widgets (a Makie.@Block with @forwarded_layout does so by itself).
BeamletOpticsGUI.card_show! — Function
card_show!(w, v)Shows the value v in the widget w on a card of live_view, where v is the result of the value(gui, obj) of its CardWidget. The card calls it when it gets its object and after moves, solves and inputs. BeamletOpticsGUI has methods for the blocks of Makie: the text of a Label or the label of a Button (string(v)), the text of a Textbox, the value of a Slider (the closest step of its range), the state of a Toggle and the selected option of a Menu (the option with the value v; an unknown value keeps the selection). The default for any other type shows nothing.
Showing a value is not an input: the card ignores the updates of card_input while it shows values, and a method should not change the observable of card_input if it can avoid it, since outside of a card, e.g. in add_controls!, nothing ignores them. A method should also keep what the user is typing: the method for a Textbox does not change a focused box, unless it is called with force = true, which the card does after an input was applied.
Agent skill
BeamletOpticsGUI.install_agent_skill — Function
install_agent_skill(dest = joinpath(pwd(), ".claude", "skills"))Copies the agent skill that ships with the installed BeamletOpticsGUI version into joinpath(dest, "beamletopticsgui") and returns that path. The skill teaches AI coding assistants (e.g. Claude Code) how to use the live view of BeamletOpticsGUI. It builds on the skill of BeamletOptics, which teaches the optics and is installed by BeamletOptics.install_agent_skill().
The default dest is the project-level skill directory of Claude Code in the current working directory. Use e.g. joinpath(homedir(), ".claude", "skills") for a personal installation.
An existing beamletopticsgui skill in dest is replaced, so calling this function again after Pkg.update keeps the skill in sync with the installed package version. Local edits to the copy are lost.