@@ -52,7 +52,8 @@ const fallbackLayerPalette = [
5252] ;
5353
5454// Populate display controls with layer checkboxes and visibility tree.
55- export function populateDisplayControls ( app , visibility , WebSocketTileLayer ,
55+ export function populateDisplayControls ( app , visibility , selectability ,
56+ WebSocketTileLayer ,
5657 techData , redrawAllLayers ,
5758 HeatMapTileLayer ) {
5859 if ( ! app . displayControlsEl ) return ;
@@ -93,12 +94,20 @@ export function populateDisplayControls(app, visibility, WebSocketTileLayer,
9394 // has more than one entry).
9495 let chipletModel = null ;
9596
96- // Restore saved hidden-layers set from previous session .
97+ // Restore saved hidden-layers and non-selectable-layers sets .
9798 let savedHiddenLayers = new Set ( ) ;
99+ let savedNonSelectableLayers = new Set ( ) ;
98100 try {
99101 const raw = getCookie ( 'or_hidden_layers' ) ;
100102 if ( raw ) savedHiddenLayers = new Set ( JSON . parse ( decodeURIComponent ( raw ) ) ) ;
101103 } catch ( _ ) { /* ignore */ }
104+ try {
105+ const raw = getCookie ( 'or_nonselectable_layers' ) ;
106+ if ( raw ) {
107+ savedNonSelectableLayers
108+ = new Set ( JSON . parse ( decodeURIComponent ( raw ) ) ) ;
109+ }
110+ } catch ( _ ) { /* ignore */ }
102111
103112 // Global counter so each layer (across the whole hierarchy) gets a unique
104113 // z-index and palette slot regardless of which chiplet it belongs to.
@@ -187,6 +196,26 @@ export function populateDisplayControls(app, visibility, WebSocketTileLayer,
187196 } ;
188197 }
189198
199+ // Parallel selectability model with the same node ids as layerSpec so
200+ // syncLayerSelDom() and buildLayerDOM() can pair each visibility node
201+ // with its selectability peer.
202+ function mirrorForSelectability ( node ) {
203+ if ( ! node . children || node . children . length === 0 ) {
204+ const name = node . data && node . data . name ;
205+ const selectable = name ? ! savedNonSelectableLayers . has ( name ) : true ;
206+ if ( selectable && name ) {
207+ app . selectableLayers . add ( name ) ;
208+ }
209+ return { id : node . id , data : { name } , checked : selectable } ;
210+ }
211+ return {
212+ id : node . id ,
213+ data : { name : node . data && node . data . name } ,
214+ children : node . children . map ( mirrorForSelectability ) ,
215+ } ;
216+ }
217+ const layerSelSpec = mirrorForSelectability ( layerSpec ) ;
218+
190219 const layerModel = new CheckboxTreeModel ( ( ) => {
191220 // Single pass over the tree: rebuild visibleLayerNames in place
192221 // (the WebSocketTileLayer closure captured this Set by reference
@@ -250,7 +279,8 @@ export function populateDisplayControls(app, visibility, WebSocketTileLayer,
250279 }
251280 }
252281 } ) ;
253-
282+ // Visibility off ⇒ selectability disabled — refresh selectability DOM.
283+ syncLayerSelDom ( ) ;
254284 // Refresh pins layer so it filters by the updated visible_layers.
255285 if ( app . pinsLayer && app . map . hasLayer ( app . pinsLayer ) ) {
256286 app . pinsLayer . refreshTiles ( ) ;
@@ -295,6 +325,39 @@ export function populateDisplayControls(app, visibility, WebSocketTileLayer,
295325 app . layerModel = layerModel ; // expose it so other rendering mechanism can use it
296326 layerModel . addFromSpec ( layerSpec ) ;
297327
328+ // Parallel selectability model — picks gate on this set on the server.
329+ const layerSelModel = new CheckboxTreeModel ( ( ) => {
330+ layerSelModel . forEach ( node => {
331+ if ( ! node . data ) return ;
332+ if ( node . data . name ) {
333+ if ( node . checked ) {
334+ app . selectableLayers . add ( node . data . name ) ;
335+ } else {
336+ app . selectableLayers . delete ( node . data . name ) ;
337+ }
338+ }
339+ } ) ;
340+ syncLayerSelDom ( ) ;
341+ const nonSel
342+ = techData . layers . filter ( n => ! app . selectableLayers . has ( n ) ) ;
343+ setCookie ( 'or_nonselectable_layers' ,
344+ encodeURIComponent ( JSON . stringify ( nonSel ) ) ) ;
345+ } ) ;
346+ layerSelModel . addFromSpec ( layerSelSpec ) ;
347+
348+ // Sync layer selectability DOM: visibility off disables the sel checkbox.
349+ function syncLayerSelDom ( ) {
350+ layerSelModel . forEach ( node => {
351+ if ( ! node . selCb ) return ;
352+ node . selCb . checked = node . checked ;
353+ node . selCb . indeterminate = node . indeterminate ;
354+ const visNode = layerModel . get ( node . id ) ;
355+ const visOff
356+ = visNode && ! visNode . checked && ! visNode . indeterminate ;
357+ node . selCb . disabled = visOff ;
358+ } ) ;
359+ }
360+
298361 // --- Layer context menu (right-click) ---
299362 const contextMenu = document . createElement ( 'div' ) ;
300363 contextMenu . className = 'context-menu' ;
@@ -330,19 +393,34 @@ export function populateDisplayControls(app, visibility, WebSocketTileLayer,
330393 } ) ;
331394
332395 function buildLayerDOM ( node , isRoot = false ) {
396+ const selNode = layerSelModel . get ( node . id ) ;
333397 if ( ! node . children || node . children . length === 0 ) {
334398 // Leaf node (layer)
335399 const label = document . createElement ( 'label' ) ;
336400
337401 const checkbox = document . createElement ( 'input' ) ;
338402 checkbox . type = 'checkbox' ;
403+ checkbox . title = 'Visible' ;
339404 checkbox . checked = node . checked ;
340405 node . cb = checkbox ;
341406 checkbox . addEventListener ( 'change' , ( ) => {
342407 layerModel . check ( node . id , checkbox . checked ) ;
343408 } ) ;
344409 label . appendChild ( checkbox ) ;
345410
411+ if ( selNode ) {
412+ const selCheckbox = document . createElement ( 'input' ) ;
413+ selCheckbox . type = 'checkbox' ;
414+ selCheckbox . className = 'vis-sel-cb' ;
415+ selCheckbox . title = 'Selectable' ;
416+ selCheckbox . checked = selNode . checked ;
417+ selNode . selCb = selCheckbox ;
418+ selCheckbox . addEventListener ( 'change' , ( ) => {
419+ layerSelModel . check ( node . id , selCheckbox . checked ) ;
420+ } ) ;
421+ label . appendChild ( selCheckbox ) ;
422+ }
423+
346424 const index = node . data . colorIndex ;
347425 const name = node . data . name ;
348426
@@ -390,6 +468,7 @@ export function populateDisplayControls(app, visibility, WebSocketTileLayer,
390468
391469 const cb = document . createElement ( 'input' ) ;
392470 cb . type = 'checkbox' ;
471+ cb . title = 'Visible' ;
393472 cb . checked = node . checked ;
394473 cb . indeterminate = node . indeterminate ;
395474 node . cb = cb ;
@@ -398,6 +477,20 @@ export function populateDisplayControls(app, visibility, WebSocketTileLayer,
398477 } ) ;
399478 header . appendChild ( cb ) ;
400479
480+ if ( selNode ) {
481+ const selCb = document . createElement ( 'input' ) ;
482+ selCb . type = 'checkbox' ;
483+ selCb . className = 'vis-sel-cb' ;
484+ selCb . title = 'Selectable' ;
485+ selCb . checked = selNode . checked ;
486+ selCb . indeterminate = selNode . indeterminate ;
487+ selNode . selCb = selCb ;
488+ selCb . addEventListener ( 'change' , ( ) => {
489+ layerSelModel . check ( node . id , selCb . checked ) ;
490+ } ) ;
491+ header . appendChild ( selCb ) ;
492+ }
493+
401494 const name = isRoot ? 'Layers' : ( node . data . name || 'Group' ) ;
402495 header . appendChild ( document . createTextNode ( name ) ) ;
403496 group . appendChild ( header ) ;
@@ -429,6 +522,10 @@ export function populateDisplayControls(app, visibility, WebSocketTileLayer,
429522
430523 app . displayControlsEl . appendChild ( layerGroup ) ;
431524
525+ // Initial selectability DOM sync (esp. disabled state for layers whose
526+ // visibility was restored as false).
527+ syncLayerSelDom ( ) ;
528+
432529 // --- Chiplets group (multi-die / 3D-IC visibility) ---
433530 //
434531 // Web-only feature: the Qt GUI has no equivalent panel today —
@@ -731,8 +828,10 @@ export function populateDisplayControls(app, visibility, WebSocketTileLayer,
731828 }
732829
733830 // --- Visibility tree (ordered to match Qt GUI display controls) ---
734- const visTree = new VisTree ( visibility , redrawAllLayers ) ;
735- visTree . add ( { label : 'Nets' , children : [
831+ // Subtrees that opt into a second "selectable" checkbox column mirror
832+ // the Qt GUI's selectability column (see displayControls.cpp).
833+ const visTree = new VisTree ( visibility , selectability , redrawAllLayers ) ;
834+ visTree . add ( { label : 'Nets' , addSelectable : true , children : [
736835 { key : 'net_signal' , label : 'Signal' } ,
737836 { key : 'net_power' , label : 'Power' } ,
738837 { key : 'net_ground' , label : 'Ground' } ,
@@ -742,7 +841,7 @@ export function populateDisplayControls(app, visibility, WebSocketTileLayer,
742841 { key : 'net_scan' , label : 'Scan' } ,
743842 { key : 'net_analog' , label : 'Analog' } ,
744843 ] } ) ;
745- visTree . add ( { label : 'Instances' , children : [
844+ visTree . add ( { label : 'Instances' , addSelectable : true , children : [
746845 { label : 'Std Cells' , visKey : 'stdcells' , disabled : ! app . hasLiberty , children : [
747846 { label : 'Bufs/Invs' , children : [
748847 { key : 'std_bufinv_timing' , label : 'Timing opt.' } ,
@@ -777,13 +876,13 @@ export function populateDisplayControls(app, visibility, WebSocketTileLayer,
777876 { key : 'phys_other' , label : 'Other' } ,
778877 ] } ,
779878 ] } ) ;
780- visTree . add ( { label : 'Blockages' , children : [
879+ visTree . add ( { label : 'Blockages' , addSelectable : true , children : [
781880 { key : 'placement_blockages' , label : 'Placement' } ,
782881 { key : 'routing_obstructions' , label : 'Routing' } ,
783882 ] } ) ;
784883 if ( techData . sites && techData . sites . length > 0 ) {
785- visTree . add ( { label : 'Rows' , visKey : 'rows' , children :
786- techData . sites . map ( name => ( {
884+ visTree . add ( { label : 'Rows' , visKey : 'rows' , addSelectable : true ,
885+ children : techData . sites . map ( name => ( {
787886 key : 'site_' + name , label : name ,
788887 } ) ) ,
789888 } ) ;
@@ -801,13 +900,13 @@ export function populateDisplayControls(app, visibility, WebSocketTileLayer,
801900 { key : 'srouting_segments' , label : 'Segments' } ,
802901 { key : 'srouting_vias' , label : 'Vias' } ,
803902 ] } ,
804- { key : 'pins' , label : 'Pins' } ,
903+ { key : 'pins' , label : 'Pins' , selectable : true } ,
805904 { key : 'pin_names' , label : 'Pin Names' , disabledBy : 'pins' } ,
806905 ] } ) ;
807906 visTree . add ( { label : 'Misc' , children : [
808907 { label : 'Instances' , children : [
809908 { key : 'inst_names' , label : 'Names' } ,
810- { key : 'inst_pins' , label : 'Pins' } ,
909+ { key : 'inst_pins' , label : 'Pins' , selectable : true } ,
811910 { key : 'inst_pin_names' , label : 'Pin Names' , disabledBy : 'inst_pins' } ,
812911 { key : 'blockages' , label : 'Blockages' } ,
813912 ] } ,
0 commit comments