Skip to content

Commit 8df74aa

Browse files
authored
Merge pull request #10618 from The-OpenROAD-Project-staging/web-show-dbu
web: add Show DBU toggle to View menu
2 parents 49a9fb0 + 9c9c179 commit 8df74aa

10 files changed

Lines changed: 282 additions & 63 deletions

File tree

src/web/src/hierarchy-browser.js

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ export class HierarchyBrowser {
339339
fmtInt(node.insts),
340340
fmtInt(node.macros),
341341
fmtInt(node.modules),
342-
fmtArea(node.area),
342+
this._fmtArea(node.area),
343343
fmtInt(node.local_insts),
344344
fmtInt(node.local_macros),
345345
fmtInt(node.local_modules),
@@ -396,14 +396,19 @@ export class HierarchyBrowser {
396396
this._render();
397397
this._sendModuleColors();
398398
}
399+
400+
_fmtArea(v) {
401+
if (v == null) return '';
402+
if (this._app.showDbu) {
403+
// Convert μm² back to DBU².
404+
const dbu = this._app.getDbuPerMicron();
405+
return String(Math.round(v * dbu * dbu));
406+
}
407+
if (v >= 1e6) return (v / 1e6).toFixed(3) + ' mm²';
408+
return v.toFixed(3) + ' μm²';
409+
}
399410
}
400411

401412
function fmtInt(v) {
402413
return v != null ? String(v) : '';
403414
}
404-
405-
function fmtArea(v) {
406-
if (v == null) return '';
407-
if (v >= 1e6) return (v / 1e6).toFixed(3) + ' mm²';
408-
return v.toFixed(3) + ' μm²';
409-
}

src/web/src/inspector.js

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ export function createInspectorPanel(app, redrawAllLayers, refreshOverlay) {
236236
pendingInspectId = null;
237237
}
238238
showLoading();
239-
const promise = app.websocketManager.request({ type: reqType });
239+
const promise = app.websocketManager.request({ type: reqType, use_dbu: app.showDbu });
240240
pendingInspectId = promise.requestId;
241241
promise
242242
.then(data => {
@@ -290,7 +290,7 @@ export function createInspectorPanel(app, redrawAllLayers, refreshOverlay) {
290290
showLoading();
291291

292292
const promise = app.websocketManager.request(
293-
{ type: 'inspect', select_id: selectId });
293+
{ type: 'inspect', select_id: selectId, use_dbu: app.showDbu });
294294
pendingInspectId = promise.requestId;
295295

296296
promise
@@ -334,7 +334,7 @@ export function createInspectorPanel(app, redrawAllLayers, refreshOverlay) {
334334

335335
showLoading();
336336

337-
const promise = app.websocketManager.request({ type: 'inspect_back' });
337+
const promise = app.websocketManager.request({ type: 'inspect_back', use_dbu: app.showDbu });
338338
pendingInspectId = promise.requestId;
339339

340340
promise
@@ -627,5 +627,20 @@ export function createInspectorPanel(app, redrawAllLayers, refreshOverlay) {
627627
updateInspector(null);
628628
}
629629

630-
return { createInspector, updateInspector, highlightBBox, pulseHighlight, navigateInspector };
630+
// Re-request the currently inspected object from the server
631+
// (e.g. after toggling show-DBU so property formatting changes).
632+
// Rulers are client-side so we re-select instead of re-requesting.
633+
function refreshInspector() {
634+
if (!lastInspectData) return;
635+
if (lastInspectData.type === 'Ruler') {
636+
if (app.rulerManager && app.rulerManager._selectedRulerId !== null) {
637+
app.rulerManager._selectRuler(app.rulerManager._selectedRulerId);
638+
}
639+
return;
640+
}
641+
if (!app.websocketManager) return;
642+
navigateInspector(-1);
643+
}
644+
645+
return { createInspector, updateInspector, highlightBBox, pulseHighlight, navigateInspector, refreshInspector };
631646
}

src/web/src/main.js

Lines changed: 64 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ const app = {
104104
// "render every chiplet" (single-chip designs).
105105
visibleChiplets: null,
106106
useTrueZ: getCookie('or_use_true_z') === '1',
107+
showDbu: getCookie('or_show_dbu') === '1',
107108
selectableLayers: new Set(),
108109
heatMapData: null,
109110
activeHeatMap: '',
@@ -114,6 +115,24 @@ const app = {
114115
getDbuPerMicron() {
115116
return this.techData?.dbu_per_micron || 1000;
116117
},
118+
// Format a DBU value as a display string, respecting the showDbu setting.
119+
// Mirrors Qt GUI's MainWindow::convertDBUToString.
120+
formatDbu(value, addUnits = false) {
121+
if (this.showDbu) return String(Math.round(value));
122+
const dbuPerUm = this.getDbuPerMicron();
123+
const precision = Math.ceil(Math.log10(dbuPerUm));
124+
const um = (value / dbuPerUm).toFixed(precision);
125+
return addUnits ? um + ' \u00b5m' : um;
126+
},
127+
// Format a distance (always positive) with auto-scaling units.
128+
formatDistance(dbuLength) {
129+
if (this.showDbu) return String(Math.round(dbuLength));
130+
const dbuPerUm = this.getDbuPerMicron();
131+
const um = dbuLength / dbuPerUm;
132+
if (um >= 1000) return (um / 1000).toFixed(3) + ' mm';
133+
if (um >= 1) return um.toFixed(3) + ' um';
134+
return (um * 1000).toFixed(1) + ' nm';
135+
},
117136
};
118137

119138
const visibility = {
@@ -469,11 +488,7 @@ function createLayoutViewer(container) {
469488
const { dbuX, dbuY } = latLngToDbu(
470489
e.latlng.lat, e.latlng.lng, app.designScale, app.designMaxDXDY,
471490
app.designOriginX, app.designOriginY);
472-
const dbuPerUm = app.getDbuPerMicron();
473-
const precision = Math.ceil(Math.log10(dbuPerUm));
474-
const xUm = (dbuX / dbuPerUm).toFixed(precision);
475-
const yUm = (dbuY / dbuPerUm).toFixed(precision);
476-
coordBar.textContent = `X: ${xUm} Y: ${yUm}`;
491+
coordBar.textContent = `X: ${app.formatDbu(dbuX)} Y: ${app.formatDbu(dbuY)}`;
477492
});
478493
app.map.on('mouseout', () => { app.lastMouseLatLng = null; });
479494

@@ -488,41 +503,49 @@ function createLayoutViewer(container) {
488503
scaleBarLabel.className = 'scale-bar-label';
489504
scaleBar.appendChild(scaleBarLabel);
490505

506+
// Round to the nearest 1/2/5 × 10^n value (e.g. 1, 2, 5, 10, 20, …).
507+
function niceRound(value) {
508+
const mag = Math.pow(10, Math.floor(Math.log10(value)));
509+
const residual = value / mag;
510+
if (residual < 1.5) return 1 * mag;
511+
if (residual < 3.5) return 2 * mag;
512+
if (residual < 7.5) return 5 * mag;
513+
return 10 * mag;
514+
}
515+
491516
function updateScaleBar() {
492517
if (!app.designScale || !visibility.scale_bar) {
493518
scaleBar.style.display = 'none';
494519
return;
495520
}
496521
scaleBar.style.display = '';
497522

498-
const dbuPerUm = app.techData?.dbu_per_micron || 1000;
499523
// Pixels per DBU at current zoom: designScale * 2^zoom.
500524
const zoom = app.map.getZoom();
501525
const pxPerDbu = app.designScale * Math.pow(2, zoom);
502-
const pxPerUm = pxPerDbu * dbuPerUm;
503526

504527
// Target bar width: ~15% of the map container width.
505528
const containerWidth = app.map.getContainer().clientWidth || 400;
506529
const targetPx = containerWidth * 0.15;
507-
const targetUm = targetPx / pxPerUm;
508-
509-
// Pick a nice round number: 1, 2, 5, 10, 20, 50, ...
510-
const mag = Math.pow(10, Math.floor(Math.log10(targetUm)));
511-
const residual = targetUm / mag;
512-
let niceUm;
513-
if (residual < 1.5) niceUm = 1 * mag;
514-
else if (residual < 3.5) niceUm = 2 * mag;
515-
else if (residual < 7.5) niceUm = 5 * mag;
516-
else niceUm = 10 * mag;
517-
518-
const barPx = Math.round(niceUm * pxPerUm);
519-
520-
// Format with appropriate units.
521-
let label;
522-
if (niceUm >= 1000) label = (niceUm / 1000) + ' mm';
523-
else if (niceUm >= 1) label = niceUm + ' \u00b5m';
524-
else if (niceUm >= 0.001) label = (niceUm * 1000) + ' nm';
525-
else label = (niceUm * 1e6) + ' pm';
530+
531+
let barPx, label;
532+
if (app.showDbu) {
533+
const niceDbu = Math.max(1, niceRound(targetPx / pxPerDbu));
534+
barPx = Math.round(niceDbu * pxPerDbu);
535+
label = String(Math.round(niceDbu));
536+
} else {
537+
const dbuPerUm = app.techData?.dbu_per_micron || 1000;
538+
const pxPerUm = pxPerDbu * dbuPerUm;
539+
const niceUm = niceRound(targetPx / pxPerUm);
540+
541+
barPx = Math.round(niceUm * pxPerUm);
542+
543+
// Format with appropriate units.
544+
if (niceUm >= 1000) label = (niceUm / 1000) + ' mm';
545+
else if (niceUm >= 1) label = niceUm + ' \u00b5m';
546+
else if (niceUm >= 0.001) label = (niceUm * 1000) + ' nm';
547+
else label = (niceUm * 1e6) + ' pm';
548+
}
526549

527550
scaleBarLine.style.width = barPx + 'px';
528551
scaleBarLabel.textContent = label;
@@ -654,6 +677,7 @@ const highlightBBox = inspector.highlightBBox;
654677
const pulseHighlight = inspector.pulseHighlight;
655678
app.updateInspector = updateInspector;
656679
app.navigateInspector = inspector.navigateInspector;
680+
app.refreshInspector = inspector.refreshInspector;
657681

658682
function createBrowser(container) {
659683
new HierarchyBrowser(container, app, redrawAllLayers);
@@ -924,6 +948,19 @@ app.toggleTheme = function() {
924948
if (app.clockTreeWidget) app.clockTreeWidget.render();
925949
};
926950

951+
app.toggleShowDbu = function() {
952+
app.showDbu = !app.showDbu;
953+
setCookie('or_show_dbu', app.showDbu ? '1' : '0');
954+
// Re-render rulers so their labels update.
955+
if (app.rulerManager) app.rulerManager._rerenderAll();
956+
// Re-render hierarchy browser if present.
957+
if (app.hierarchyBrowser) app.hierarchyBrowser._render();
958+
// Update scale bar.
959+
if (app.updateScaleBar) app.updateScaleBar();
960+
// Re-request inspector properties with new formatting.
961+
if (app.refreshInspector) app.refreshInspector();
962+
};
963+
927964
// ─── Menu Bar ────────────────────────────────────────────────────────────────
928965

929966
createMenuBar(app);
@@ -1099,6 +1136,7 @@ app.websocketManager.readyPromise.then(async () => {
10991136
zoom: Math.round(app.map.getZoom()),
11001137
visible_layers: [...app.visibleLayerNames],
11011138
selectable_layers: [...app.selectableLayers],
1139+
use_dbu: app.showDbu,
11021140
...vf,
11031141
};
11041142
if (e.originalEvent && e.originalEvent.shiftKey) {

src/web/src/menu-bar.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ export function createMenuBar(app) {
1616
{ label: 'Zoom Out', shortcut: 'Shift+Z', action: () => app.map.zoomOut() },
1717
{ type: 'separator' },
1818
{ label: 'Toggle Theme', shortcut: 'T', action: () => app.toggleTheme() },
19+
{ label: 'Show DBU', action: () => app.toggleShowDbu(),
20+
checked: () => app.showDbu },
1921
{ type: 'separator' },
2022
{ label: 'Find...', shortcut: 'Ctrl+F', disabled: true },
2123
{ label: 'Go to Position...', shortcut: 'Shift+G', disabled: true },
@@ -76,6 +78,16 @@ export function createMenuBar(app) {
7678
// Track items with dynamic enabled state for refresh on open.
7779
if (item.enabledWhen) row._enabledWhen = item.enabledWhen;
7880

81+
// Checkmark indicator for toggle items.
82+
let checkEl = null;
83+
if (item.checked) {
84+
checkEl = document.createElement('span');
85+
checkEl.className = 'menu-check';
86+
checkEl.textContent = item.checked() ? '\u2713' : '';
87+
row.appendChild(checkEl);
88+
row._checkedFn = item.checked;
89+
}
90+
7991
const text = document.createElement('span');
8092
text.textContent = item.label;
8193
row.appendChild(text);
@@ -106,6 +118,10 @@ export function createMenuBar(app) {
106118
if (row._enabledWhen) {
107119
row.classList.toggle('disabled', !row._enabledWhen());
108120
}
121+
if (row._checkedFn) {
122+
const check = row.querySelector('.menu-check');
123+
if (check) check.textContent = row._checkedFn() ? '\u2713' : '';
124+
}
109125
}
110126
}
111127

0 commit comments

Comments
 (0)