Skip to content

Commit 3f4caa6

Browse files
committed
web: add Show DBU toggle to View menu
Add a "Show DBU" option in the web UI's View menu, mirroring the Qt GUI's Options > Show DBU toggle. When enabled, coordinates and dimensions display as raw database units instead of microns. The setting defaults to off (microns) matching the Qt GUI and persists between sessions via the or_show_dbu cookie. Affected display areas: - Coordinate bar (mouse position) - Scale bar (zoom distance reference) - Ruler labels and inspector properties - Inspector BBox/Point properties for all selected objects - Hierarchy browser area column Server side: add ScopedDbuFormat RAII helper that temporarily sets Descriptor::Property::convert_dbu to a micron-aware formatter (matching the Qt GUI's convertDBUToString) for the scope of each inspect request. The client passes use_dbu in requests so the server formats property values accordingly. Signed-off-by: Matt Liberty <mliberty@precisioninno.com>
1 parent f509e0f commit 3f4caa6

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 = 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)