2828)
2929from cellier .gui ._render_controls import (
3030 AMBIENT_OCCLUSION_CHOICES ,
31+ DEPTH_RANGE_LABELS ,
32+ DEPTH_RANGE_MODES ,
33+ DEPTH_RANGE_STEP ,
3134 OUTLINE_MODE_CHOICES ,
3235 PLACEMENT_CHOICES ,
3336 SLOT_IS_COLOUR_MODES ,
3437 VISUAL_RENDER_CONTROLS ,
3538 VISUAL_RENDER_TITLES ,
39+ coerce_depth_range ,
3640 visual_path ,
3741 with_api_path ,
3842)
@@ -132,6 +136,15 @@ def _describe_controls(self, values: dict[str, Any]) -> list[dict]:
132136 entry ["options" ] = _choice_options (OUTLINE_MODE_CHOICES )
133137 elif control .kind == "label_selection" :
134138 entry ["max_rows" ] = MAX_LABEL_ROWS
139+ elif control .kind == "range" :
140+ # The first box runs from the minimum to 1, the second from
141+ # the first box's value to the maximum.
142+ entry ["labels" ] = list (DEPTH_RANGE_LABELS )
143+ entry ["minimum" ] = control .minimum
144+ entry ["maximum" ] = control .maximum
145+ entry ["first_maximum" ] = 1.0
146+ entry ["decimals" ] = control .decimals
147+ entry ["step" ] = DEPTH_RANGE_STEP
135148 described .append (self ._adjust_for_mode (entry , values ))
136149 return [entry for entry in described if entry is not None ]
137150
@@ -148,6 +161,9 @@ def _serialise(values: dict[str, Any]) -> dict:
148161 out ["outline_selected_labels" ] = {
149162 str (k ): int (v ) for k , v in selection .items ()
150163 }
164+ depth_range = out .get ("outline_depth_range" )
165+ if depth_range is not None :
166+ out ["outline_depth_range" ] = [float (v ) for v in depth_range ]
151167 return out
152168
153169 # ------------------------------------------------------------------
@@ -190,6 +206,10 @@ def _on_values_change(self, change) -> None:
190206 value = {int (k ): int (v ) for k , v in value .items ()}
191207 elif field == "outline.slot" :
192208 value = int (value )
209+ elif field == "outline_depth_range" :
210+ value = self ._depth_range_from_widget (value )
211+ if value is None :
212+ continue
193213 for visual_id in self ._visual_ids :
194214 self .changed .emit (
195215 VisualRenderUpdateEvent (
@@ -200,6 +220,28 @@ def _on_values_change(self, change) -> None:
200220 )
201221 )
202222
223+ def _depth_range_from_widget (self , value ) -> tuple [float , float ] | None :
224+ """The pair to send for a range the front end wrote, or ``None``.
225+
226+ The script keeps the pair in order itself; this is the same rule
227+ applied again, so that nothing a front end writes reaches the model
228+ as a pair it would refuse. A value that is not a pair of numbers
229+ is dropped.
230+ """
231+ try :
232+ start , solid = (float (part ) for part in value )
233+ except (TypeError , ValueError ):
234+ return None
235+ pair = coerce_depth_range (start , solid )
236+ if pair != (start , solid ):
237+ # Show what was sent.
238+ self ._applying = True
239+ try :
240+ self .values = {** self .values , "outline_depth_range" : list (pair )}
241+ finally :
242+ self ._applying = False
243+ return pair
244+
203245 # ------------------------------------------------------------------
204246 # model -> widget
205247 # ------------------------------------------------------------------
@@ -214,6 +256,8 @@ def _apply(self, field: str, value: Any) -> None:
214256 return # a field this panel does not display
215257 if field == "outline_selected_labels" :
216258 value = {str (k ): int (v ) for k , v in (value or {}).items ()}
259+ elif field == "outline_depth_range" :
260+ value = [float (v ) for v in value ]
217261 self ._applying = True
218262 try :
219263 self .values = {** self .values , field : value }
@@ -254,18 +298,29 @@ class AnywidgetLabelsOutlineControls(_PaletteFollowingPanel):
254298 """Outline mode, slot and per-label selection for a labels visual.
255299
256300 The panel changes shape with its mode, because ``outline.slot`` means
257- two different things. In whole-volume and all-boundaries mode it
258- chooses the colour, so it gets the swatch row every other visual has; in
259- per-label mode it only decides whether the volume participates, so it
260- gets a checkbox and the per-label rows appear. Re-describing the
261- controls is enough -- the ESM rebuilds on ``change:controls``.
301+ two different things. In whole-volume, all-boundaries and
302+ depth-threshold mode it chooses the colour, so it gets the swatch row
303+ every other visual has; in per-label mode it only decides whether the
304+ volume participates, so it gets a checkbox and the per-label rows
305+ appear. The depth range row is drawn in depth-threshold mode only.
306+ Re-describing the controls is enough -- the ESM rebuilds on
307+ ``change:controls``.
262308 """
263309
264310 section : ClassVar [str ] = "labels_outline"
265311 DEFAULT_TITLE : ClassVar [str ] = VISUAL_RENDER_TITLES ["labels_outline" ]
266312
313+ def __init__ (self , visual_ids , values , ** kwargs ) -> None :
314+ from cellier .visuals ._label_memory import DEFAULT_OUTLINE_DEPTH_RANGE
315+
316+ # A panel built with no range shows the model's default.
317+ values = {"outline_depth_range" : DEFAULT_OUTLINE_DEPTH_RANGE , ** values }
318+ super ().__init__ (visual_ids , values , ** kwargs )
319+
267320 def _adjust_for_mode (self , entry : dict , values : dict [str , Any ]) -> dict | None :
268321 mode = values .get ("outline_mode" ) or "per_label"
322+ if entry ["field" ] == "outline_depth_range" and mode not in DEPTH_RANGE_MODES :
323+ return None # no depth bands in this mode
269324 if mode in SLOT_IS_COLOUR_MODES :
270325 if entry ["field" ] == "outline_selected_labels" :
271326 return None # no per-label colours in these modes
@@ -277,6 +332,17 @@ def _adjust_for_mode(self, entry: dict, values: dict[str, Any]) -> dict | None:
277332 return entry
278333
279334 def _apply (self , field : str , value : Any ) -> None :
335+ if field == "outline_depth_range" and field not in {
336+ c ["field" ] for c in self .controls
337+ }:
338+ # The row is not drawn in this mode, but the value is what it
339+ # shows when the mode is next entered.
340+ self ._applying = True
341+ try :
342+ self .values = {** self .values , field : [float (v ) for v in value ]}
343+ finally :
344+ self ._applying = False
345+ return
280346 super ()._apply (field , value )
281347 if field == "outline_mode" :
282348 self .controls = self ._describe_controls (self .values )
0 commit comments