Adding and removing components
The components of a System in live_view and its sources can be added and removed at runtime, from the window and from code, e.g. to build a setup from an empty System(). This page shows the example from code, describes the catalog and how to extend it with own components. The user-facing description of the window is in the section "Adding and removing components" of live_view.
All code blocks assume using GLMakie, BeamletOptics, BeamletOpticsGUI, need a Makie backend with a window (GLMakie) and are therefore not run when the docs are built.
From code
An empty system and a source are enough to start. The component is placed before it is added, e.g. via translate_to3d!, and add_component! pushes it to the system, renders it, registers it with the controls and solves the systems again:
beam = Beam([0.0, 0, 0], [0.0, 1, 0])
gui = live_view(System() => beam)
lens = ThinLens(50e-3, -50e-3, 25.4e-3, 1.5)
translate_to3d!(lens, [0, 0.1, 0])
add_component!(gui, lens; label = "lens")
remove_component!(gui, lens)All beams that are paired with the target system are traced through the new component. With several systems, the keyword system of add_component! selects the target.
Sources are added and removed with the same two functions. A view may start without one, as an empty table, and every source can be removed, also the last one:
gui = live_view(System())
laser = Beam([0.0, 0, 0], [0.0, 1, 0], 632.8e-9)
add_component!(gui, laser; label = "laser")
add_component!(gui, lens)
remove_component!(gui, laser)A source is traced through one system of the view (keyword system, also a StaticSystem) and gets a marker, with which it is selected and moved like the sources the view started with. It is drawn in the color of its wavelength: violet to red between 380 nm and 780 nm, a dark violet for ultraviolet and a dark red for infrared light, like the sources that the view started with. The keyword beam_kwargs takes the keywords of its rendering, e.g. (; render_every = 10) or another color.
The catalog and the placement
The catalog "Components" is a widget like the cards: it can be pinned, minimized and, in the app layout, docked.
- In the compact layout it is a window over the 3D view, closed at first. The key
Insertopens it with its top left corner at the mouse, the toggle "Components" of the tool rail opens and closes it. A drag at its head moves it. - In the app layout it is docked in the section "Components" of the left sidebar, below the object tree, where its entries are icons. The buttons at the title of the section move it into its window over the 3D view and minimize it; the dock button of the window brings it back.
Insertshows the window at the mouse.
A window that is not pinned is a popup: it closes (in the app layout: it is docked again) as soon as the component was dropped. The pin in its head keeps it open at its place, e.g. to place several components; the chevron minimizes it to its head.
The built-in entries are the sources of BeamletOptics and all its components whose constructor takes numbers and glasses:
| group | entries |
|---|---|
| Sources | Beam, Gaussian beamlet, Collimated source, Uniform disc source, Point source, Uniform point source, Astigmatic Gaussian beamlet |
| Lenses | Thin lens, Singlet (SphericalLens), Doublet, Triplet |
| Mirrors | Round mirror, Square mirror, Rectangular mirror, Thin mirror (SquarePlanoMirror2D), Prism mirror, Retroreflector |
| Curved mirrors | Spherical, Parabolic, Conic, Ellipsoidal and Hyperbolic mirror, the last four also off-axis |
| Beamsplitters | Thin beamsplitter, Round thin beamsplitter, Plate beamsplitter, Round plate beamsplitter, Cube beamsplitter, Compensator plate |
| Prisms | Right-angle prism |
| Polarizers | Polarization filter, Round filter, Linear polarizer |
| Detectors | Detector |
The icons at the top of the catalog select the group, the tiles below the entry. The boxes of the form take its numbers; the other arguments of the constructor keep their defaults, e.g. the thickness and the hole_diameter of a curved mirror. An entry with a refractive index has a menu of glasses instead of a number: N-BK7, fused silica, CaF2, N-SF11, N-SF10, N-SF6HT, N-SF5, N-F2, N-BAF10 and N-LAK22 with their dispersion (see catalog_glasses), and "constant" with a box for a constant refractive index. The line "into" at the top names the system that gets the component or source: the system of the selected or inspected object, else the first one. In a view with several systems it is a menu, which chooses another system; the choice holds until another object is selected, which sets the system again. A source can be traced through any system, a component is added to a System only, hence the menu lists the systems that can get the chosen entry. "Place" attaches the component to the mouse, which moves it in the plane of the table (perpendicular to the rotation axis) through the first source of its system: beside the beams it lies at the height of the beam, also in an oblique view; seen from the front or the side, it moves in the plane of the view at the depth of the source. The first component or source of an empty view is placed at the origin, where the camera of an empty view looks: it does not follow the mouse. A left click drops it, Esc cancels. With the snapping onto beams switched on (the chip "Snap" or the key Tab, also while placing; off by default, see the keyword snap), it snaps onto a rendered beam within 12 px; of a beam group only onto its central beam, of a Gaussian beamlet onto its chief ray. With "position + rotation" its optical axis also turns along the beam. The component that is being placed is not traced until it is dropped. See the section "Adding and removing components" of live_view for the details of the placement.
A source of the group "Sources" is placed the same way: its marker follows the mouse, a click drops it, and its beam is traced through the system that the catalog names as "into". It points along +y, as it is constructed; turn it with the controls afterwards (rotate mode, m). A source does not snap onto beams. Its form takes the wavelength [nm], the diameter or the half angle of the cone [°], and the number of rings or rays. If the markers of the sources are hidden, placing a source shows them.
The button "remove" at the end of the page "Pose" of the card of a component or a source and the key Delete remove it again. The added and removed components are part of the code of export_changes, which writes a glass as its SellmeierEquation and a constant refractive index as λ -> n, such that the code runs without BeamletOpticsGUI. An added source is its constructor at the origin along +y, the rotate3d! and translate_to3d! to its pose and the solve_system! that traces it; a removed source that the view started with is a comment.
Aligning on the table and to the beams
The toggle "Table" among the tools shows an optical table below the setup, like the keyword table = true: a grid of holes at a distance of 25 mm, perpendicular to the rotation axis of the controls, at the lowest point of the components. While it is shown and the snapping is switched on (the chip "Snap" or Tab, the same switch as for the beams), a component or source that is dragged or placed with the mouse sits on the hole closest to it, at its own height, and in the rotate mode its optical axis snaps to the multiples of 45° to the rows of the holes. A beam within reach comes first, such that a component is put on the grid beside the beams and on the beam near it. With the snapping off, nothing snaps and the table is only shown. The table grows with the setup.
gui = live_view(System(); table = true)
# an imperial table 50 mm below the beams, shown without snapping
gui = live_view(system, beam; table = (; pitch = 25.4e-3, height = -50e-3, snap = false))The card of a component aligns it to the nearest beam, i.e. the central beam of a source as far as it does not depend on the component: "onto beam" moves it to the point of the beam closest to it, "face beam" turns its optical axis along the beam, e.g. a lens straight in the beam or a mirror that sends it back. The card of a source has "aim": a dashed line then follows the mouse, and a click turns the source such that it points at the center of the component under the mouse, or at the point of the plane of the mouse elsewhere, which is a hole of the table while it is shown. Esc cancels it. Each of these is one step of the undo history.
Changing, undoing and the whole script
A component or source that was placed from the catalog has the page "Edit" on its card: the form of its entry with the values it was built with. "Apply", or Enter in a box, builds it again with the new values in the same pose, in the same system and under the same label, e.g. a lens with other radii or a source with another wavelength; a source that is drawn in the color of its wavelength follows the new wavelength. An invalid input changes nothing and is reported in the status line. Objects that the view started with have no page "Edit": the view does not know how they were constructed.
Ctrl+C copies the selected component or source from the catalog, Ctrl+V builds it again with the same values and attaches it to the mouse like "Place": in the orientation of the original, a source also with the look of its beam (color, opacity, line width). A click drops it into the system of the selection, else the first one, Esc cancels, and each further Ctrl+V pastes another one, also in another window. Objects that the view started with can not be copied, for the same reason; the status line says so.
Adding, removing and changing are part of the undo history of the controls, together with the moves: Ctrl+Z takes the last step back, Ctrl+Y (or Ctrl+Shift+Z) does it again, e.g. a removed component comes back in its pose, with its label. How a source is drawn (color, opacity, line width, on and off) is not part of the history.
The tool "Script" next to "Export" prints the whole setup as a script and copies it to the clipboard, like export_script: the constructors and poses of all components and sources, the systems, the solve_system! calls and the live_view call. A view that was built from an empty System() with the catalog gives a script that runs as it is. An object that the view started with is a comment with its type and its pose, since its constructor is not known; fill it in there.
gui = live_view(System())
# place sources and components from the catalog, then
code = export_script(gui)Limits of this version:
- A
StaticSystemcan not be changed: a view without aSystemonly offers the sources of the catalog. Objects inside a group can not be added or removed, remove the group instead. - Only components and sources from the catalog can be changed on the page "Edit" and are written with their constructors by
export_script. - The script does not contain how the sources are drawn, the extras, the clip planes and the other keywords of
live_view.
Own catalog entries
A CatalogEntry is data: a name, a constructor and the parameters that are passed to it, numbers (CatalogParam) and glasses (CatalogGlass). The same entry builds the object and the constructor call in the code of export_changes, hence the constructor should be a function or type that a script can call by name. Its group is one of the built-in groups or a new one; its icon is the name of an icon of the live view, as for add_tool!, or an own Makie.BezierPath, and the icon of its group without one. An entry with source = true is a source: its constructor gets the position [0, 0, 0] and the direction [0, 1, 0] before the values of its parameters and returns a beam or a beam group; a CatalogParam with integer = true passes an Int, e.g. a number of rays, and one with presets (["532 nm" => 532e-9, ...]) has a menu of these values next to its box, like the laser lines at the wavelength of the built-in sources. A package with own components adds its entries to component_catalog and its glasses to catalog_glasses, e.g. in the __init__ of its package extension on BeamletOpticsGUI (see Cards and widgets); a single view gets other entries via the keyword catalog:
# `MyLens(f, n)` builds the component, `f` is shown in mm, `n` is chosen among the glasses
push!(component_catalog(), CatalogEntry("My lens", MyLens; group = "Lenses", icon = :singlet,
params = [CatalogParam("f", 100e-3; unit = "mm", scale = 1e-3), CatalogGlass()]))
# an own glass in the menu of every entry with a glass
push!(catalog_glasses(), "My glass" => SellmeierEquation(1.04, 0.23, 1.01, 0.006, 0.02, 103.6))
# only these entries in one view; `catalog = CatalogEntry[]` shows no catalog
entries = [CatalogEntry("Round mirror", RoundPlanoMirror; group = "Mirrors", params = [
CatalogParam("diameter", 25.4e-3; unit = "mm", scale = 1e-3),
CatalogParam("thickness", 6e-3; unit = "mm", scale = 1e-3)])]
gui = live_view(System() => beam; catalog = entries)Reference
BeamletOpticsGUI.add_component! — Function
add_component!(gui, obj; system = nothing, select = true, label = 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. A StaticSystem can not be changed: it throws an ArgumentError, like a system that is not shown in the gui and 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.
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 = (;)) -> 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 in the color of its 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 another color) 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. It throws an ArgumentError for a source that the gui shows already. A view may start without a source, see live_view(system).
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) -> objRemoves the object obj from its system in the live_view window gui, like "remove" on its card or the key Delete while it is selected: obj is deleted from the system, 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!.
obj is a top-level object (or object group) of a System of the gui. 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, an extra and an object that is not shown in the gui.
remove_component!(gui, source) -> sourceRemoves the source (a beam or a beam group) from the gui: it is no longer traced through any system, 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.
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))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 GLMakie, BeamletOpticsGUIand the callgui = live_view(…)with the pairssystem => source, the systems without a source and thelabels. The lines above it run without a window.
An object or source of the catalog (see component_catalog) 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, 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 extras and the other keyword arguments of live_view.
gui = live_view(System())
# add components and sources from the catalog, move them, then
code = export_script(gui)