Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ describe it as available in docs or the skill.
- `src/LiveCard.jl`, `src/LiveCards.jl`, `src/LiveCardRows.jl`: cards and the card API.
`src/LiveCardPages.jl`: the pages of a card per type ("Pose", "Color", "Results", "Properties").
`src/LiveBeamColor.jl`: the page "Color" of the sources (color, opacity and line width of their
beams). `src/LiveEdit.jl`: the page "Edit" of the components and sources from the catalog, which
beams). `src/LiveDetectorStop.jl`: the switch "stop beams" on the card of a detector, which
switches it in place (`BeamletOptics.stop!`) and writes the state into its code.
`src/LiveEdit.jl`: the page "Edit" of the components and sources from the catalog, which
builds them again with other parameters. `src/LiveCopy.jl`: `Ctrl+C` and `Ctrl+V`, which build
one of them again as it is and place it with the mouse. Adding, removing and changing are actions of the undo
history of the controls (`_push_action!` in `src/LiveInteraction.jl`, `_record_added!` in
Expand Down
2 changes: 1 addition & 1 deletion docs/src/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ takes numbers and glasses:
| 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, with the switch "stop beams": off, the beams pass it unchanged (`Detector(edge_length, false)`) and reach the components behind it |
| Detectors | Detector, with the switch "stop beams": off, the beams pass it unchanged (`Detector(edge_length, false)`) and reach the components behind it; the card of any detector switches it later in place (page "Pose") |

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
Expand Down
3 changes: 2 additions & 1 deletion docs/src/widgets.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ BeamletOpticsGUI.card_actions(a::MyAttenuator) = (
```

The types of BeamletOptics have such methods as well, e.g. the ray count slider of a source or the
signal of a `Detector`.
signal of a `Detector` and its switch "stop beams", which switches the detector itself
(`BeamletOptics.stop!`): it stays the same object, and undo switches it back.

## Recipe: a card for your system type

Expand Down
21 changes: 19 additions & 2 deletions ext/BeamletOpticsGUIGLMakieExt/session.jl
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,22 @@ function _session(step, layout::Symbol, fixture::_Fixture = _fixture())
deselect!()
end

# The switch "stop beams" on the page "Pose" of the card of the detector: off and on again, such
# that the later steps still see its hits
step("card: stop beams of the detector (off, on)") do
_click!(gui, _px(gui, _pos(fx.detector)))
frame!()
_selected(gui, fx.detector)
GUI._set_page!(gui, card_of(fx.detector, :hide), :pose)
frame!()
for stop in (false, true)
_click!(gui, _rect_center(GUI._card_widget(card_of(fx.detector, :stop), :stop)))
settle!()
_check(BMO.stop(fx.detector) == stop, "the switch did not set stop = $stop")
end
deselect!()
end

# The pencil in the head of the card of a component: a typed name and Enter
step("card: rename a component in the title") do
_click!(gui, _px(gui, _pos(fx.mirror)))
Expand Down Expand Up @@ -1264,9 +1280,10 @@ function _catalog_session(step, entries = component_catalog())
settle!()
end
end
# a switch on the page "Edit", e.g. the detector that lets the beams pass, then "Apply"
# a switch on the page "Edit", then "Apply"; not of a detector, which its card switches in
# place (see the step "card: stop beams of the detector")
s = findfirst(p -> p isa GUI.CatalogToggle, entry.params)
if !isnothing(s)
if !isnothing(s) && !isempty(GUI._edit_param_rows(gui, obj, entry.params[s], s))
step("catalog, $(entry.name): edit switch + apply") do
gui.controls.selected[] === obj || select!(gui, obj)
frame!()
Expand Down
4 changes: 3 additions & 1 deletion skills/beamletopticsgui/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,9 @@ lens with an asphere is built and exported as the `Lens`, `DoubletLens` or `Trip
surfaces, e.g. `Lens(SphericalSurface(r, d), EvenAsphericalSurface(r, d, k, [0.0, A4, ...]),
thickness, n)`; a cemented surface belongs to both elements that it joins. The detector has a
switch "stop beams" (on by default); off, the beams pass it unchanged (`Detector(edge_length,
false)`) and reach the components behind it. "Place"
false)`) and reach the components behind it. The card of any detector switches it later in place
(page "Pose"; the page "Edit" has no switch), and `export_changes` writes
`BeamletOptics.stop!(name, false)` for a detector without code of its own. "Place"
attaches the chosen component to the mouse, a click drops it, `Esc` cancels, `Delete` removes the
selected component or source. It moves in the plane of the table (perpendicular to `rotation_axis`,
at the height of the source of its system) and snaps onto beams while `snap` is on; the first component or source of
Expand Down
3 changes: 2 additions & 1 deletion skills/beamletopticsgui/VISUALIZATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ wavelength in that mode; a color or length change while a long solve runs starts
opens on "Results" for a detector, else on "Pose"; a pinned card keeps its page. Rows of the
type: rays hitting it and their angle of incidence (last solve), `n` of lenses, R/T of
beamsplitters, the polarizer axis, detector `signal` (power or number of rays of its view while a
view is shown, else number of hits), the ray count slider of sources
view is shown, else number of hits) and its switch "stop beams" (in place: the same detector,
`BeamletOptics.stop!`, with undo), the ray count slider of sources
(`num_rays!`), and on every beam, beam group, source and beamlet the toggle "on" (off: not
solved, not drawn, rays removed from detectors; only the source marker stays), for Gaussian
beamlets the toggle "beams" (generating beams as `render!(...; show_beams = true)`), for polarized
Expand Down
1 change: 1 addition & 0 deletions src/BeamletOpticsGUI.jl
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ include("LiveDetectors.jl")
include("LiveResultsView.jl")
include("LiveTrace.jl")
include("LiveBeamSwitch.jl")
include("LiveDetectorStop.jl")
include("LivePolarization.jl")
include("LiveBeamOverlays.jl")
include("LiveClip.jl")
Expand Down
3 changes: 2 additions & 1 deletion src/LiveCardRows.jl
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ card_rows(@nospecialize(p::Union{BMO.LinearPolarizer, BMO.PolarizationFilter}))
(pose_card_rows(p)..., _beam_row(), _text_row("axis", :axis, _axis_text))
# Detectors: the hits of the last solve, the power, or the number of rays, of the view of the
# detector; the view itself is on the page "Results" of the card, see `_has_view`
card_rows(@nospecialize(pd::BMO.Detector)) = (pose_card_rows(pd)..., _beam_row(), _text_row("signal", :signal, _signal_text))
card_rows(@nospecialize(pd::BMO.Detector)) = (pose_card_rows(pd)..., _beam_row(), _stop_row(),
_text_row("signal", :signal, _signal_text))
# Beams and beam groups: switched on and off, their polarization, see `beam_card_rows`
card_rows(@nospecialize(b::Union{BMO.AbstractBeam, BMO.AbstractBeamGroup})) = (pose_card_rows(b)..., beam_card_rows(b)...)
# Sources whose rays can be regenerated: wavelength, size and a slider for the number of rays
Expand Down
93 changes: 93 additions & 0 deletions src/LiveDetectorStop.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#=
The switch "stop beams" on the card of a detector, which changes the detector itself, see `_set_stop!`
=#

# The row of the switch on the page "Pose" of the card of a `Detector`, see `card_rows`
_stop_row() = CardRow("stop", CardWidget(Toggle; name = :stop, value = (gui, d) -> BMO.stop(d),
on = (gui, d, v) -> _set_stop!(gui, d, v)), "beams")

"""
_set_stop!(gui, d, stop::Bool)

Switches whether the detector `d` of the `gui` stops the beams that hit it, in place
(`BeamletOptics.stop!`): it stays the same object, in its systems, with its label and its cards. Like
a move, a running solve is cancelled first, then the systems are solved again, or marked as outdated
without auto tracing, via the `on_change` of the controls. The code of `d` (of the catalog, of a
project or added with `code`) gets the new state in every window that shows `d`, see `_stop_code!`,
such that export, save and copy keep it; `export_changes` writes a `BeamletOptics.stop!` for a
detector without code. An entry of the undo history. Nothing happens if the state does not change.
"""
function _set_stop!(gui::LiveView, @nospecialize(d), stop::Bool)
BMO.stop(d) == stop && return nothing
_change!(() -> BMO.stop!(d, stop), gui.controls, d)
comp = gui.components
haskey(comp.stops0, d) || (comp.stops0[d] = !stop)
# every window of the links that shows `d`, also this one
foreach(v -> _stop_code!(v, d, stop), unique(objectid, Any[gui, gui.links.views...]))
if comp.recording
_push_action!(gui.controls, d, () -> _unrecorded(() -> _set_stop!(gui, d, !stop), gui),
() -> _unrecorded(() -> _set_stop!(gui, d, stop), gui))
end
_update_cards!(gui)
_update_inspector!(gui)
gui.controls.on_change(d)
return nothing
end

"""
_stop_code!(gui, d, stop::Bool)

Writes the state `stop` of the detector `d` into its origin in the `gui` (see `_ComponentState`): for
an entry of the catalog its input of the switch, with which the code is built again (and a pending
input of the page "Edit"); for other code the second argument of its call `Detector(…)`, see
`_stop_call_code`. Code that is no such call keeps its state, and the status line says that the
switch is not saved. Nothing for a detector without code, e.g. one of the user's script.
"""
function _stop_code!(gui::LiveView, @nospecialize(d), stop::Bool)
comp = gui.components
origin = get(comp.origin, d, nothing)
isnothing(origin) && return nothing
i = haskey(origin, :entry) && origin.entry.constructor === BMO.Detector ?
findfirst(_is_toggle, origin.entry.params) : nothing
if !isnothing(i) && haskey(origin, :strings)
strings = copy(origin.strings)
strings[i] = string(stop)
values = Any[_catalog_value(p, s) for (p, s) in zip(origin.entry.params, strings)]
comp.origin[d] = merge(origin, (; code = _catalog_code(origin.entry, values), strings))
haskey(comp.edits, d) && (comp.edits[d][i] = string(stop))
return nothing
end
code = _stop_call_code(origin.code, stop)
if isnothing(code)
gui.status.text[] = "the switch of $(_label(gui, d)) is not saved: its code is no call of `Detector`"
return nothing
end
comp.origin[d] = merge(origin, (; code))
return nothing
end

_is_toggle(::CatalogToggle) = true
_is_toggle(_) = false

"""
_stop_call_code(code, stop) -> Union{String, Nothing}

The `code` of a call `Detector(edge_length)` or `Detector(edge_length, stop)` (also qualified, e.g.
`BeamletOptics.Detector(…)`) with the second argument `stop`, or `nothing` for any other code.
"""
function _stop_call_code(code::AbstractString, stop::Bool)
ex = try
Meta.parse(code)
catch
return nothing
end
(ex isa Expr && ex.head === :call && _is_detector_name(ex.args[1])) || return nothing
# the positional arguments, after the name and before keywords
args = ex.args[2:end]
any(a -> a isa Expr && a.head in (:parameters, :kw), args) && return nothing
length(args) in (1, 2) || return nothing
return string(Expr(:call, ex.args[1], args[1], stop))
end

_is_detector_name(f) = f === :Detector ||
(f isa Expr && f.head === :. && f.args[end] == QuoteNode(:Detector))
3 changes: 3 additions & 0 deletions src/LiveEdit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,9 @@ _edit_param_rows(::LiveView, _, p::CatalogToggle, i::Int) = (CardRow(_edit_label
CardWidget(Toggle; name = _edit_toggle(i),
value = (gui, o) -> _catalog_value(p, _edit_strings(gui, o)[i]),
on = (gui, o, v) -> _toggle_edit!(gui, o, i, v))),)
# A detector is switched on its card in place, see `_set_stop!`: "Apply" keeps its current state,
# which the switch writes into the inputs as well
_edit_param_rows(::LiveView, ::BMO.Detector, ::CatalogToggle, ::Int) = ()

_edit_param_rows(::LiveView, _, p::CatalogGlass, i::Int) = (
CardRow(_edit_label(p.name), CardWidget(Menu; name = _edit_menu(i),
Expand Down
29 changes: 29 additions & 0 deletions src/LiveExport.jl
Original file line number Diff line number Diff line change
Expand Up @@ -558,10 +558,37 @@ function _export_code(gui::LiveView)
(top isa LiveClipPlane || top in added || top in cx.written || haskey(inside, top)) && continue
n += _export_pose_lines!(lines, gui, names, top)
end
n += _export_stop_lines!(lines, gui, names, added)
n == 0 && push!(lines, "", "# no changes")
return join(lines, "\n") * "\n", n
end

"""
_export_stop_lines!(lines, gui, names, added) -> n

Appends a `BeamletOptics.stop!(name, stop)` to the `lines` of `export_changes` for each detector of
the `gui` that was switched on its card (see `_set_stop!`) and stops the beams now or not unlike at
the start of the view, except for one that was `added` in the window, whose code has the state.
Returns the number `n` of such detectors.
"""
function _export_stop_lines!(lines, gui::LiveView, names, added)
comp = gui.components
switched = String[]
for (d, stop0) in comp.stops0
(d in added || _has(comp.removed, d) || BMO.stop(d) == stop0) && continue
name = get(names, d, nothing)
isnothing(name) || push!(switched, "BeamletOptics.stop!($name, $(BMO.stop(d)))")
end
isempty(switched) && return 0
push!(lines, "", "# Detectors switched on their cards")
append!(lines, sort!(switched))
return length(switched)
end

# Whether the detector `obj` of the `gui` was switched on its card since the start, see `_set_stop!`
_stop_switched(gui::LiveView, @nospecialize(obj)) =
haskey(gui.components.stops0, obj) && BMO.stop(obj) != gui.components.stops0[obj]

"""Copies `code` to the clipboard, returns `false` if there is no clipboard, e.g. headless."""
function _copy_to_clipboard(code::String)
try
Expand Down Expand Up @@ -738,6 +765,8 @@ function _export_construct_lines!(lines, gui::LiveView, names, @nospecialize(obj
pose = String[]
_export_pose_lines!(pose, gui, names, obj; heading = false, current)
append!(lines, (_commented(line) for line in pose))
# a detector of the script that was switched on its card, see `_set_stop!`
_stop_switched(gui, obj) && push!(lines, "# BeamletOptics.stop!($name, $(BMO.stop(obj)))")
return false
end
push!(lines, _export_heading(gui, obj))
Expand Down
3 changes: 3 additions & 0 deletions src/LiveView.jl
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,9 @@ Base.@kwdef mutable struct _ComponentState
recording::Bool = true
# the inputs of the page "Edit" of an object that are not applied yet, see `_edit_strings`
const edits::IdDict{Any, Vector{String}} = IdDict{Any, Vector{String}}()
# whether a detector stopped the beams before its first switch on its card, for
# `export_changes`, see `_set_stop!`
const stops0::IdDict{Any, Bool} = IdDict{Any, Bool}()
placement::Any = nothing
# the source that is being aimed with the mouse, see `_start_aim!`
aim::Any = nothing
Expand Down
Loading
Loading