From 73bee9ea6df58d99e829594f275a4e35dc7df6b7 Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Sun, 1 Feb 2026 16:52:43 +0000 Subject: [PATCH 01/20] upgrade Visualizer --- pylabrobot/visualizer/img/logo.png | Bin 0 -> 162290 bytes pylabrobot/visualizer/index.html | 250 +++- pylabrobot/visualizer/lib.js | 1699 ++++++++++++++++++++++++++- pylabrobot/visualizer/main.css | 664 ++++++++++- pylabrobot/visualizer/vis.js | 29 +- pylabrobot/visualizer/visualizer.py | 137 ++- 6 files changed, 2717 insertions(+), 62 deletions(-) create mode 100644 pylabrobot/visualizer/img/logo.png diff --git a/pylabrobot/visualizer/img/logo.png b/pylabrobot/visualizer/img/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..affe21787f7a6a573604e972c9fe52121875a721 GIT binary patch literal 162290 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­NZ;FuJ0$c0n>6(*T3B9HSg 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

%uOJh*_` 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ðDk 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%0DY71O&1cX#5&+#(D+ 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 literal 0 HcmV?d00001 diff --git a/pylabrobot/visualizer/index.html b/pylabrobot/visualizer/index.html index 8ba6ddea3ce..75fbdeffd74 100644 --- a/pylabrobot/visualizer/index.html +++ b/pylabrobot/visualizer/index.html @@ -5,6 +5,7 @@ PyLabRobot Visualizer + - +

+ +
+ +
+
+ + -
-
-
- -
- - Loading... +
+ + + +
+
+ + + + + x + + + + y + +
+
+
+
-
- - -
-
-
+ + +
@@ -94,8 +304,8 @@ - - + + diff --git a/pylabrobot/visualizer/lib.js b/pylabrobot/visualizer/lib.js index 8c463d8d5c2..061801d3a87 100644 --- a/pylabrobot/visualizer/lib.js +++ b/pylabrobot/visualizer/lib.js @@ -34,11 +34,150 @@ var stage; var selectedResource; var canvasWidth, canvasHeight; +var activeTool = "cursor"; // "cursor" or "coords" + +function buildWrtDropdown() { + var wrtSelect = document.getElementById("coords-wrt-ref"); + if (!wrtSelect) return; + var prevValue = wrtSelect.value; + wrtSelect.innerHTML = ''; + + // Collect all visible (not inside a collapsed container) resources from the sidebar tree. + var tree = document.getElementById("resource-tree"); + if (!tree) return; + + function collectVisible(container) { + var items = []; + var nodes = container.querySelectorAll(":scope > .tree-node"); + for (var i = 0; i < nodes.length; i++) { + var node = nodes[i]; + var name = node.dataset.resourceName; + if (name) items.push(name); + // Recurse into children if not collapsed + var childrenDiv = node.querySelector(":scope > .tree-node-children"); + if (childrenDiv && !childrenDiv.classList.contains("collapsed")) { + items = items.concat(collectVisible(childrenDiv)); + } + } + return items; + } + + var visibleNames = collectVisible(tree); + + for (var i = 0; i < visibleNames.length; i++) { + var opt = document.createElement("option"); + opt.value = visibleNames[i]; + opt.textContent = visibleNames[i]; + wrtSelect.appendChild(opt); + } + + // Restore previous selection if still valid + var allValues = Array.from(wrtSelect.options).map(function (o) { return o.value; }); + if (allValues.indexOf(prevValue) >= 0) { + wrtSelect.value = prevValue; + } else if (visibleNames.length > 0) { + wrtSelect.value = visibleNames[0]; + } +} + +function updateCoordsPanel(resource) { + // No-op; dropdown is built once via buildWrtDropdown. +} + +function getAncestorAtDepth(resource, depth) { + // Walk up from the resource to the sidebar root, skipping deck-like + // intermediaries (same flattening as the sidebar). + var rawChain = []; + var cur = resource; + while (cur) { + rawChain.unshift(cur); + if (sidebarRootResource && cur === sidebarRootResource) break; + cur = cur.parent; + } + var displayChain = []; + for (var i = 0; i < rawChain.length; i++) { + // Keep the root (index 0) and skip deck-like intermediaries + if (i > 0 && isDeckLike(rawChain[i])) continue; + // Also skip ResourceHolder intermediaries + if (i > 0 && isResourceHolder(rawChain[i])) continue; + displayChain.push(rawChain[i]); + } + if (depth < displayChain.length) return displayChain[depth]; + return null; +} + +function getWrtAnchorOffset(wrtResource) { + var xRef = document.getElementById("coords-wrt-x-ref"); + var yRef = document.getElementById("coords-wrt-y-ref"); + var zRef = document.getElementById("coords-wrt-z-ref"); + + var xOff = 0; + if (xRef && xRef.value === "center") xOff = wrtResource.size_x / 2; + else if (xRef && xRef.value === "right") xOff = wrtResource.size_x; + + var yOff = 0; + if (yRef && yRef.value === "center") yOff = wrtResource.size_y / 2; + else if (yRef && yRef.value === "back") yOff = wrtResource.size_y; + + var zOff = 0; + var zNA = false; + if (zRef) { + if (zRef.value === "center") zOff = wrtResource.size_z / 2; + else if (zRef.value === "top") zOff = wrtResource.size_z; + else if (zRef.value === "cavity_bottom") { + if (wrtResource instanceof Container && wrtResource.material_z_thickness != null) { + zOff = wrtResource.material_z_thickness; + } + } + } + + return { x: xOff, y: yOff, z: zOff }; +} + +function getLocationWrt(resource, wrtName) { + // If wrt is the sidebar root, return absolute location. + if (sidebarRootResource && wrtName === sidebarRootResource.name) { + return resource.getAbsoluteLocation(); + } + var wrtResource = resources[wrtName]; + if (!wrtResource) return resource.getAbsoluteLocation(); + var abs = resource.getAbsoluteLocation(); + var wrtAbs = wrtResource.getAbsoluteLocation(); + var wrtOff = getWrtAnchorOffset(wrtResource); + return { + x: abs.x - (wrtAbs.x + wrtOff.x), + y: abs.y - (wrtAbs.y + wrtOff.y), + z: (abs.z || 0) - ((wrtAbs.z || 0) + wrtOff.z), + }; +} var scaleX, scaleY; var resources = {}; // name -> Resource object +var homeView = null; // saved initial view {x, y, scaleX, scaleY} +var rootResource = null; // the root resource for fit-to-viewport + +function fitToViewport() { + if (!rootResource || !stage) return; + const padding = 40; + const stageW = stage.width(); + const stageH = stage.height(); + const viewW = stageW - padding * 2; + const viewH = stageH - padding * 2; + const fitScale = Math.min(viewW / rootResource.size_x, viewH / rootResource.size_y, 1); + + stage.scaleX(fitScale); + stage.scaleY(-fitScale); + + const centerX = (stageW - rootResource.size_x * fitScale) / 2; + const centerY = (stageH + rootResource.size_y * fitScale) / 2 - stageH * fitScale; + stage.x(centerX); + stage.y(centerY); + + if (typeof updateScaleBar === "function") updateScaleBar(); +} + let trash; let gif; @@ -222,13 +361,15 @@ class Resource { this.size_z = size_z; this.location = location; this.parent = parent; + this.resourceType = resourceData.type || this.constructor.name; + this.category = resourceData.category || ""; this.color = "#5B6D8F"; this.children = []; for (let i = 0; i < children.length; i++) { const child = children[i]; - const childClass = classForResourceType(child.type); + const childClass = classForResourceType(child.type, child.category); const childInstance = new childClass(child, this); this.assignChild(childInstance); @@ -286,6 +427,39 @@ class Resource { if (tooltip !== undefined) { tooltip.destroy(); } + var labelText; + if (activeTool === "coords") { + const xRef = document.getElementById("coords-x-ref"); + const yRef = document.getElementById("coords-y-ref"); + const zRef = document.getElementById("coords-z-ref"); + const wrtRef = document.getElementById("coords-wrt-ref"); + const wrtName = wrtRef ? wrtRef.value : "root"; + const base = getLocationWrt(this, wrtName); + const xOff = !xRef || xRef.value === "left" ? 0 : xRef.value === "center" ? this.size_x / 2 : this.size_x; + const yOff = !yRef || yRef.value === "front" ? 0 : yRef.value === "center" ? this.size_y / 2 : this.size_y; + var zOff = 0; + var zNA = false; + if (zRef) { + if (zRef.value === "center") zOff = this.size_z / 2; + else if (zRef.value === "top") zOff = this.size_z; + else if (zRef.value === "cavity_bottom") { + if (this instanceof Container && this.material_z_thickness != null) { + zOff = this.material_z_thickness; + } else { + zNA = true; + } + } + } + const cx = base.x + xOff; + const cy = base.y + yOff; + const cz = (base.z || 0) + zOff; + const czStr = zNA ? "na" : cz.toFixed(1); + const isAbsolute = sidebarRootResource && wrtName === sidebarRootResource.name; + const wrtLabel = isAbsolute ? "abs" : "wrt " + wrtName; + labelText = `${this.name}\n${wrtLabel}: (${cx.toFixed(1)}, ${cy.toFixed(1)}, ${czStr}) mm`; + } else { + labelText = this.tooltipLabel(); + } tooltip = new Konva.Label({ x: x + this.size_x / 2, y: y + this.size_y / 2, @@ -306,18 +480,30 @@ class Resource { ); tooltip.add( new Konva.Text({ - text: this.tooltipLabel(), + text: labelText, fontFamily: "Arial", - fontSize: 18, + fontSize: activeTool === "coords" ? 14 : 18, padding: 5, fill: "white", }) ); tooltip.scaleY(-1); layer.add(tooltip); + if (typeof highlightSidebarRow === "function") { + highlightSidebarRow(this.name); + } + if (activeTool === "coords") { + updateCoordsPanel(this); + } }); this.mainShape.on("mouseout", () => { tooltip.destroy(); + if (typeof clearSidebarHighlight === "function") { + clearSidebarHighlight(); + } + if (activeTool === "coords") { + updateCoordsPanel(null); + } }); } } @@ -708,9 +894,10 @@ class Plate extends Resource { class Container extends Resource { constructor(resourceData, parent) { super(resourceData, parent); - const { max_volume } = resourceData; + const { max_volume, material_z_thickness } = resourceData; this.maxVolume = max_volume; this.volume = resourceData.volume || 0; + this.material_z_thickness = material_z_thickness; } static colorForVolume(volume, maxVolume) { @@ -1019,7 +1206,7 @@ class LiquidHandler extends Resource { // Utility for mapping resource type strings to classes // =========================================================================== -function classForResourceType(type) { +function classForResourceType(type, category) { switch (type) { case "Deck": return Deck; @@ -1065,13 +1252,53 @@ function classForResourceType(type) { return TubeRack; case "Tube": return Tube; + default: + break; + } + + // Fall back to category for unrecognized type names (e.g. concrete carrier subclasses). + switch (category) { + case "tip_carrier": + return TipCarrier; + case "plate_carrier": + return PlateCarrier; + case "trough_carrier": + return TroughCarrier; + case "tube_carrier": + return TubeCarrier; + case "carrier": + case "mfx_carrier": + return Carrier; + case "deck": + return Deck; + case "liquid_handler": + return LiquidHandler; + case "tip_rack": + return TipRack; + case "plate": + return Plate; + case "well": + return Well; + case "tip_spot": + return TipSpot; + case "resource_holder": + return ResourceHolder; + case "plate_holder": + return PlateHolder; + case "tube_rack": + return TubeRack; + case "tube": + return Tube; + case "container": + case "trough": + return Container; default: return Resource; } } function loadResource(resourceData) { - const resourceClass = classForResourceType(resourceData.type); + const resourceClass = classForResourceType(resourceData.type, resourceData.category); const parentName = resourceData.parent_name; var parent = undefined; @@ -1082,6 +1309,12 @@ function loadResource(resourceData) { const resource = new resourceClass(resourceData, parent); resources[resource.name] = resource; + // If the resource has a parent, ensure it's registered in the parent's children list. + // The constructor sets this.parent but doesn't add to parent.children. + if (parent && !parent.children.includes(resource)) { + parent.assignChild(resource); + } + return resource; } @@ -1103,29 +1336,12 @@ window.addEventListener("load", function () { stage.scaleY(-1); stage.offsetY(canvasHeight); - let minX = -(1 / 2) * canvasWidth; - let minY = -(1 / 2) * canvasHeight; - let maxX = (1 / 2) * canvasWidth; - let maxY = (1 / 2) * canvasHeight; - - // limit draggable area to size of canvas - stage.dragBoundFunc(function (pos) { - // Set the bounds of the draggable area to 1/2 off the canvas. - let newX = Math.max(minX, Math.min(maxX, pos.x)); - let newY = Math.max(minY, Math.min(maxY, pos.y)); - - return { - x: newX, - y: newY, - }; - }); - - // add white background + // add white background (large enough to cover any pan position) var background = new Konva.Rect({ - x: minX, - y: minY, - width: canvasWidth - minX + maxX, - height: canvasHeight - minY + maxY, + x: -5000, + y: -5000, + width: 10000, + height: 10000, fill: "white", listening: false, }); @@ -1136,6 +1352,81 @@ window.addEventListener("load", function () { layer.add(background); + // Scale bar update: picks a round mm value that fits ~80-120px on screen. + function updateScaleBar() { + const scale = stage.scaleX(); // CSS pixels per mm + // Choose a nice round distance whose bar width falls near 100px + const niceSteps = [1, 2, 5, 10, 20, 50, 100, 200, 500, 1000]; + let bestMM = niceSteps[0]; + for (let i = 0; i < niceSteps.length; i++) { + if (niceSteps[i] * scale >= 60) { + bestMM = niceSteps[i]; + break; + } + bestMM = niceSteps[i]; + } + const barPx = bestMM * scale; + const barLine = document.getElementById("scale-bar-line"); + const barLabel = document.getElementById("scale-bar-label"); + if (barLine) barLine.style.width = barPx + "px"; + if (barLabel) barLabel.textContent = bestMM + " mm"; + } + + // Mouse wheel zoom + const scaleBy = 1.1; + stage.on("wheel", function (e) { + e.evt.preventDefault(); + const oldScale = stage.scaleX(); + const pointer = stage.getPointerPosition(); + + // scaleY is negative (flipped), so use absolute value for uniform zoom + const mousePointTo = { + x: (pointer.x - stage.x()) / oldScale, + y: (pointer.y - stage.y()) / stage.scaleY(), + }; + + const direction = e.evt.deltaY > 0 ? -1 : 1; + const newScale = direction > 0 ? oldScale * scaleBy : oldScale / scaleBy; + + // Clamp zoom level + const clampedScale = Math.max(0.1, Math.min(10, newScale)); + + stage.scaleX(clampedScale); + stage.scaleY(-clampedScale); // keep Y flipped + + const newPos = { + x: pointer.x - mousePointTo.x * clampedScale, + y: pointer.y - mousePointTo.y * (-clampedScale), + }; + stage.position(newPos); + updateScaleBar(); + }); + + updateScaleBar(); + + // Keep Konva stage sized to its container when the layout changes + // (e.g. sidebar expand/collapse/resize, window resize). + const resizeObserver = new ResizeObserver(function () { + const newWidth = canvas.offsetWidth; + const newHeight = canvas.offsetHeight; + if (newWidth > 0 && newHeight > 0) { + stage.width(newWidth); + stage.height(newHeight); + stage.offsetY(newHeight); + } + }); + resizeObserver.observe(canvas); + + // Home button: reset view to initial position and zoom + const homeBtn = document.getElementById("home-button"); + if (homeBtn) { + homeBtn.addEventListener("click", function () { + fitToViewport(); + homeBtn.classList.add("clicked"); + setTimeout(function () { homeBtn.classList.remove("clicked"); }, 400); + }); + } + // Check if there is an after stage setup callback, and if so, call it. if (typeof afterStageSetup === "function") { afterStageSetup(); @@ -1302,3 +1593,1355 @@ window.addEventListener("load", function () { gifResetUI(); gifShowStartUI(); }); + +// =========================================================================== +// Sidepanel Resource Tree +// =========================================================================== + +var sidepanelSelectedResource = null; +var sidebarRootResource = null; +var sidepanelHighlightRect = null; +var sidepanelHoverRect = null; +var sidepanelHoverGlow = null; + +function getResourceTypeName(resource) { + return resource.constructor.name; +} + +function getResourceColor(resource) { + const typeName = getResourceTypeName(resource); + return RESOURCE_COLORS[typeName] || RESOURCE_COLORS["Resource"]; +} + +function isTipRackLike(resource) { + return resource.category === "tip_rack" || + resource instanceof TipRack || + (resource.children.length > 0 && resource.children[0] instanceof TipSpot); +} + +function isPlateLike(resource) { + return resource.category === "plate" || + resource instanceof Plate || + (resource.children.length > 0 && resource.children[0] instanceof Well); +} + +function isTubeRackLike(resource) { + return resource.category === "tube_rack" || + resource instanceof TubeRack || + (resource.children.length > 0 && resource.children[0] instanceof Tube); +} + +function isCarrierLike(resource) { + return resource instanceof Carrier || + ["carrier", "tip_carrier", "plate_carrier", "trough_carrier", "tube_carrier"] + .includes(resource.category); +} + +function getResourceSummary(resource) { + if (isTipRackLike(resource)) { + const totalSpots = resource.children.length; + let tipsPresent = 0; + for (let i = 0; i < resource.children.length; i++) { + if (resource.children[i].has_tip) { + tipsPresent++; + } + } + return `${tipsPresent}/${totalSpots} tips`; + } + + if (isPlateLike(resource)) { + const numChildren = resource.children.length; + return `${numChildren} wells`; + } + + if (isTubeRackLike(resource)) { + const numChildren = resource.children.length; + return `${numChildren} tubes`; + } + + if (resource instanceof Container) { + const vol = resource.getVolume(); + if (vol > 0) { + return `${vol.toFixed(1)} \u00B5L`; + } + return ""; + } + + if (isCarrierLike(resource)) { + // Count meaningful children (skip empty resource holders) + let childCount = 0; + let childType = ""; + for (let i = 0; i < resource.children.length; i++) { + const holder = resource.children[i]; + if (holder.children && holder.children.length > 0) { + childCount++; + if (!childType && holder.children[0]) { + childType = (holder.children[0].resourceType || "item").toLowerCase() + "s"; + } + } + } + if (childCount > 0) { + return `${childCount} ${childType}`; + } + return `${resource.children.length} sites`; + } + + return ""; +} + +function isResourceHolder(resource) { + return resource instanceof ResourceHolder || resource instanceof PlateHolder || + resource.category === "resource_holder" || resource.category === "plate_holder"; +} + +function isDeckLike(resource) { + return resource instanceof Deck || resource instanceof LiquidHandler || + ["deck", "liquid_handler"].includes(resource.category); +} + +// Get the "display children" of a resource, skipping invisible intermediaries +// like ResourceHolders, and flattening Deck/LiquidHandler wrappers. +function getDisplayChildren(resource) { + if (isDeckLike(resource)) { + // Flatten: collect all children, recursing through nested decks/LiquidHandlers + let result = []; + for (let i = 0; i < resource.children.length; i++) { + const child = resource.children[i]; + if (isDeckLike(child)) { + result = result.concat(getDisplayChildren(child)); + } else { + result.push(child); + } + } + return result; + } + + if (isCarrierLike(resource)) { + // Return one entry per slot. Each entry is {index, resource} or {index, empty: true}. + let result = []; + for (let i = 0; i < resource.children.length; i++) { + const holder = resource.children[i]; + if (isResourceHolder(holder) && holder.children.length > 0) { + for (let j = 0; j < holder.children.length; j++) { + result.push({ index: i, resource: holder.children[j] }); + } + } else if (isResourceHolder(holder)) { + result.push({ index: i, empty: true, holderName: holder.name }); + } else { + result.push({ index: i, resource: holder }); + } + } + // Sort by high y to low y; when y is equal, sort by ascending x. + function getSlotLocation(entry) { + if (entry.empty) return resource.children[entry.index].location; + return entry.resource.parent ? entry.resource.parent.location : entry.resource.location; + } + result.sort((a, b) => { + const al = getSlotLocation(a); + const bl = getSlotLocation(b); + if (bl.y !== al.y) return bl.y - al.y; + return al.x - bl.x; + }); + // Assign display indices: for vertical layouts (varying y), use n,...,1,0 top to bottom. + // For horizontal layouts (same y, varying x), use 0,...,n-1 left to right. + const allSameY = result.length > 1 && result.every(function (e) { + return getSlotLocation(e).y === getSlotLocation(result[0]).y; + }); + for (let i = 0; i < result.length; i++) { + result[i].index = allSameY ? i : result.length - 1 - i; + } + return result; + } + + // Leaf containers: don't show individual wells/tips/tubes + if (isTipRackLike(resource) || isPlateLike(resource) || isTubeRackLike(resource)) { + return []; + } + + return resource.children || []; +} + +function buildEmptySlotDOM(index, depth, holderName) { + const node = document.createElement("div"); + node.className = "tree-node tree-node-empty"; + + const row = document.createElement("div"); + row.className = "tree-node-row empty-slot"; + row.style.paddingLeft = (8 + depth * 16) + "px"; + + const arrow = document.createElement("span"); + arrow.className = "tree-node-arrow"; + + const indexSpan = document.createElement("span"); + indexSpan.className = "tree-node-index"; + indexSpan.textContent = index; + + const nameSpan = document.createElement("span"); + nameSpan.className = "tree-node-name empty"; + nameSpan.textContent = ""; + + row.appendChild(arrow); + row.appendChild(indexSpan); + row.appendChild(nameSpan); + node.appendChild(row); + + // Hover to highlight the resource holder on canvas + if (holderName) { + row.addEventListener("mouseenter", function () { + showHoverHighlight(holderName); + }); + row.addEventListener("mouseleave", function () { + clearHoverHighlight(); + }); + } + + return node; +} + +function buildTreeNodeDOM(resource, depth, slotIndex) { + const node = document.createElement("div"); + node.className = "tree-node"; + node.dataset.resourceName = resource.name; + + const row = document.createElement("div"); + row.className = "tree-node-row"; + row.style.paddingLeft = (8 + depth * 16) + "px"; + + const displayChildren = getDisplayChildren(resource); + // Carrier children are {index, resource} objects; others are plain resources + const isCarrier = isCarrierLike(resource); + const hasVisibleChildren = displayChildren.length > 0; + + // Arrow + const arrow = document.createElement("span"); + arrow.className = "tree-node-arrow"; + if (hasVisibleChildren) { + arrow.classList.add("has-children"); + arrow.textContent = "\u25BC"; + } + + // Slot index (shown for resources inside a carrier) + if (slotIndex !== undefined) { + const indexSpan = document.createElement("span"); + indexSpan.className = "tree-node-index"; + indexSpan.textContent = slotIndex; + row.appendChild(arrow); + row.appendChild(indexSpan); + } else { + // Color dot (only for top-level items without a slot index) + const dot = document.createElement("span"); + dot.className = "tree-node-dot"; + dot.style.backgroundColor = getResourceColor(resource); + row.appendChild(arrow); + row.appendChild(dot); + } + + // Name + const nameSpan = document.createElement("span"); + nameSpan.className = "tree-node-name"; + nameSpan.textContent = resource.name; + nameSpan.title = `${resource.name} (${resource.resourceType})`; + + // Type label + const typeSpan = document.createElement("span"); + typeSpan.className = "tree-node-type"; + typeSpan.textContent = resource.resourceType; + + // Info summary + const info = document.createElement("span"); + info.className = "tree-node-info"; + info.textContent = getResourceSummary(resource); + + row.appendChild(nameSpan); + row.appendChild(typeSpan); + row.appendChild(info); + node.appendChild(row); + + // Hover row to show yellow highlight on canvas + row.addEventListener("mouseenter", function () { + showHoverHighlight(resource.name); + }); + row.addEventListener("mouseleave", function () { + clearHoverHighlight(); + }); + + // Click row to select + show UML panel on canvas (single click only) + var clickTimer = null; + row.addEventListener("click", function (e) { + e.stopPropagation(); + if (clickTimer) { clearTimeout(clickTimer); clickTimer = null; return; } + clickTimer = setTimeout(function () { + clickTimer = null; + showUmlPanel(resource.name); + }, 250); + }); + + // Double-click row to focus/zoom on resource + row.addEventListener("dblclick", function (e) { + e.stopPropagation(); + if (clickTimer) { clearTimeout(clickTimer); clickTimer = null; } + focusOnResource(resource.name); + }); + + // Click arrow to toggle children + if (hasVisibleChildren) { + arrow.addEventListener("click", function (e) { + e.stopPropagation(); + const childrenContainer = node.querySelector(":scope > .tree-node-children"); + if (childrenContainer) { + const isCollapsed = childrenContainer.classList.toggle("collapsed"); + arrow.textContent = isCollapsed ? "\u25B6" : "\u25BC"; + buildWrtDropdown(); + } + }); + } + + // Build children + if (hasVisibleChildren) { + const childrenDiv = document.createElement("div"); + childrenDiv.className = "tree-node-children"; + for (let i = 0; i < displayChildren.length; i++) { + const entry = displayChildren[i]; + if (isCarrier && entry.empty) { + childrenDiv.appendChild(buildEmptySlotDOM(entry.index, depth + 1, entry.holderName)); + } else if (isCarrier && entry.resource) { + childrenDiv.appendChild(buildTreeNodeDOM(entry.resource, depth + 1, entry.index)); + } else { + childrenDiv.appendChild(buildTreeNodeDOM(entry, depth + 1)); + } + } + node.appendChild(childrenDiv); + } + + return node; +} + +function buildResourceTree(rootResource) { + const treeContainer = document.getElementById("resource-tree"); + if (!treeContainer) return; + treeContainer.innerHTML = ""; + if (!rootResource) return; + sidebarRootResource = rootResource; + + // Build a root node for the master resource (e.g. LiquidHandler) + const rootNode = document.createElement("div"); + rootNode.className = "tree-node"; + rootNode.dataset.resourceName = rootResource.name; + + const rootRow = document.createElement("div"); + rootRow.className = "tree-node-row"; + rootRow.style.paddingLeft = "8px"; + + const rootArrow = document.createElement("span"); + rootArrow.className = "tree-node-arrow has-children"; + rootArrow.textContent = "\u25BC"; + + const rootDot = document.createElement("span"); + rootDot.className = "tree-node-dot"; + rootDot.style.backgroundColor = getResourceColor(rootResource); + + const rootName = document.createElement("span"); + rootName.className = "tree-node-name"; + rootName.textContent = rootResource.name; + rootName.title = `${rootResource.name} (${rootResource.resourceType})`; + + const rootType = document.createElement("span"); + rootType.className = "tree-node-type"; + rootType.textContent = rootResource.resourceType; + + rootRow.appendChild(rootArrow); + rootRow.appendChild(rootDot); + rootRow.appendChild(rootName); + rootRow.appendChild(rootType); + rootNode.appendChild(rootRow); + + rootRow.addEventListener("mouseenter", function () { + showHoverHighlight(rootResource.name); + }); + rootRow.addEventListener("mouseleave", function () { + clearHoverHighlight(); + }); + var rootClickTimer = null; + rootRow.addEventListener("click", function (e) { + e.stopPropagation(); + if (rootClickTimer) { clearTimeout(rootClickTimer); rootClickTimer = null; return; } + rootClickTimer = setTimeout(function () { + rootClickTimer = null; + showUmlPanel(rootResource.name); + }, 250); + }); + rootRow.addEventListener("dblclick", function (e) { + e.stopPropagation(); + if (rootClickTimer) { clearTimeout(rootClickTimer); rootClickTimer = null; } + focusOnResource(rootResource.name); + }); + + // Build deck-level children inside the root node + const topChildren = getDisplayChildren(rootResource); + topChildren.sort((a, b) => { + const ax = a.location ? a.location.x : (a.getAbsoluteLocation ? a.getAbsoluteLocation().x : 0); + const bx = b.location ? b.location.x : (b.getAbsoluteLocation ? b.getAbsoluteLocation().x : 0); + return ax - bx; + }); + + const childrenDiv = document.createElement("div"); + childrenDiv.className = "tree-node-children"; + for (let i = 0; i < topChildren.length; i++) { + childrenDiv.appendChild(buildTreeNodeDOM(topChildren[i], 1)); + } + rootNode.appendChild(childrenDiv); + + rootArrow.addEventListener("click", function (e) { + e.stopPropagation(); + const isCollapsed = childrenDiv.classList.toggle("collapsed"); + rootArrow.textContent = isCollapsed ? "\u25B6" : "\u25BC"; + }); + + treeContainer.appendChild(rootNode); + buildWrtDropdown(); +} + +function addResourceToTree(resource) { + if (!resource || !resource.parent) return; + const treeContainer = document.getElementById("resource-tree"); + if (!treeContainer) return; + + // Find the parent node in the tree + const parentNode = treeContainer.querySelector( + `.tree-node[data-resource-name="${CSS.escape(resource.parent.name)}"]` + ); + if (!parentNode) { + // Parent not in tree; rebuild the whole tree + const rootName = Object.keys(resources).find( + (n) => resources[n] && !resources[n].parent + ); + if (rootName) buildResourceTree(resources[rootName]); + return; + } + + // Rebuild the parent node's subtree to reflect the new child + const parentResource = resources[resource.parent.name]; + if (!parentResource) return; + const depth = getResourceDepth(parentResource); + const newParentNode = buildTreeNodeDOM(parentResource, depth); + + parentNode.replaceWith(newParentNode); +} + +function removeResourceFromTree(resourceName) { + const treeContainer = document.getElementById("resource-tree"); + if (!treeContainer) return; + + const node = treeContainer.querySelector( + `.tree-node[data-resource-name="${CSS.escape(resourceName)}"]` + ); + if (node) { + // Find the parent tree-node and rebuild it + const parentTreeNode = node.parentElement && node.parentElement.closest(".tree-node"); + if (parentTreeNode) { + const parentName = parentTreeNode.dataset.resourceName; + const parentResource = resources[parentName]; + if (parentResource) { + const depth = getResourceDepth(parentResource); + const newNode = buildTreeNodeDOM(parentResource, depth); + parentTreeNode.replaceWith(newNode); + return; + } + } + node.remove(); + } +} + +function getResourceDepth(resource) { + let depth = 0; + let current = resource; + while (current.parent) { + depth++; + current = current.parent; + } + return depth; +} + +function updateSidepanelState(resourceName) { + const treeContainer = document.getElementById("resource-tree"); + if (!treeContainer) return; + + const resource = resources[resourceName]; + if (!resource) return; + + // For tip spots and wells, update the parent summary instead + if (resource instanceof TipSpot || resource instanceof Well || resource instanceof Tube) { + if (resource.parent) { + updateSidepanelNodeInfo(resource.parent.name); + } + return; + } + + updateSidepanelNodeInfo(resourceName); +} + +function updateSidepanelNodeInfo(resourceName) { + const treeContainer = document.getElementById("resource-tree"); + if (!treeContainer) return; + + const resource = resources[resourceName]; + if (!resource) return; + + const node = treeContainer.querySelector( + `.tree-node[data-resource-name="${CSS.escape(resourceName)}"]` + ); + if (!node) return; + + const infoSpan = node.querySelector(":scope > .tree-node-row > .tree-node-info"); + if (infoSpan) { + infoSpan.textContent = getResourceSummary(resource); + } +} + +function showHoverHighlight(resourceName) { + clearHoverHighlight(); + const resource = resources[resourceName]; + if (!resource || !resource.group) return; + const absPos = resource.getAbsoluteLocation(); + // Outer glow rect (turquoise shadow, no fill) + sidepanelHoverGlow = new Konva.Rect({ + x: absPos.x, + y: absPos.y, + width: resource.size_x, + height: resource.size_y, + stroke: "rgba(0, 220, 220, 0.7)", + strokeWidth: 2, + shadowColor: "rgba(0, 220, 220, 0.8)", + shadowBlur: 12, + shadowOffsetX: 0, + shadowOffsetY: 0, + listening: false, + }); + // Inner fill rect (yellow, no shadow) + sidepanelHoverRect = new Konva.Rect({ + x: absPos.x, + y: absPos.y, + width: resource.size_x, + height: resource.size_y, + fill: "rgba(255, 230, 0, 0.25)", + listening: false, + }); + resourceLayer.add(sidepanelHoverGlow); + resourceLayer.add(sidepanelHoverRect); + resourceLayer.draw(); +} + +function clearHoverHighlight() { + if (sidepanelHoverGlow) { + sidepanelHoverGlow.destroy(); + sidepanelHoverGlow = null; + } + if (sidepanelHoverRect) { + sidepanelHoverRect.destroy(); + sidepanelHoverRect = null; + } + resourceLayer.draw(); +} + +function highlightSidebarRow(resourceName) { + clearSidebarHighlight(); + const tree = document.getElementById("resource-tree"); + if (!tree) return; + const nodes = tree.querySelectorAll(".tree-node"); + const nodeByName = {}; + for (const node of nodes) { + if (node.dataset.resourceName) { + nodeByName[node.dataset.resourceName] = node; + } + } + // Walk up the parent chain until we find a resource with a sidebar entry. + let name = resourceName; + while (name) { + if (nodeByName[name]) { + const row = nodeByName[name].querySelector(":scope > .tree-node-row"); + if (row) { + row.classList.add("canvas-hover"); + row.scrollIntoView({ block: "nearest" }); + } + return; + } + const res = resources[name]; + if (res && res.parent) { + name = res.parent.name; + } else { + break; + } + } +} + +function clearSidebarHighlight() { + const tree = document.getElementById("resource-tree"); + if (!tree) return; + const highlighted = tree.querySelectorAll(".tree-node-row.canvas-hover"); + for (const row of highlighted) { + row.classList.remove("canvas-hover"); + } +} + +function highlightResourceOnCanvas(resourceName) { + const resource = resources[resourceName]; + if (!resource) return; + + // Update selected state in tree + const treeContainer = document.getElementById("resource-tree"); + if (treeContainer) { + const prev = treeContainer.querySelector(".tree-node-row.selected"); + if (prev) prev.classList.remove("selected"); + + const node = treeContainer.querySelector( + `.tree-node[data-resource-name="${CSS.escape(resourceName)}"]` + ); + if (node) { + const row = node.querySelector(":scope > .tree-node-row"); + if (row) row.classList.add("selected"); + } + } + + // Remove previous highlight + if (sidepanelHighlightRect) { + sidepanelHighlightRect.destroy(); + sidepanelHighlightRect = null; + } + + if (!resource.group) return; + + // Get absolute position on the canvas + const absPos = resource.getAbsoluteLocation(); + + // Draw a highlight rectangle on the resource layer + sidepanelHighlightRect = new Konva.Rect({ + x: absPos.x - 2, + y: absPos.y - 2, + width: resource.size_x + 4, + height: resource.size_y + 4, + stroke: "#0d6efd", + strokeWidth: 2, + dash: [6, 3], + listening: false, + }); + resourceLayer.add(sidepanelHighlightRect); + resourceLayer.draw(); + + // Auto-remove highlight after 2 seconds + setTimeout(function () { + if (sidepanelHighlightRect) { + sidepanelHighlightRect.destroy(); + sidepanelHighlightRect = null; + resourceLayer.draw(); + } + }, 2000); +} + +function focusOnResource(resourceName) { + var resource = resources[resourceName]; + if (!resource || !stage) return; + + var absPos = resource.getAbsoluteLocation(); + var padding = 60; + var stageW = stage.width(); + var stageH = stage.height(); + var viewW = stageW - padding * 2; + var viewH = stageH - padding * 2; + var fitScale = Math.min(viewW / resource.size_x, viewH / resource.size_y); + + // Adaptive max zoom based on resource size (smaller resources get more zoom) + var resourceArea = resource.size_x * resource.size_y; + var maxScale; + if (resourceArea > 50000) maxScale = 2; // large (e.g. deck, carrier) + else if (resourceArea > 5000) maxScale = 5; // medium (e.g. plate, tiprack) + else if (resourceArea > 500) maxScale = 10; // small (e.g. well, tipspot) + else maxScale = 15; // tiny + fitScale = Math.min(fitScale, maxScale); + + stage.scaleX(fitScale); + stage.scaleY(-fitScale); + + // Center the resource in the viewport + var centerX = (stageW - resource.size_x * fitScale) / 2 - absPos.x * fitScale; + var centerY = (stageH + resource.size_y * fitScale) / 2 + absPos.y * fitScale - stageH * fitScale; + stage.x(centerX); + stage.y(centerY); + + if (typeof updateScaleBar === "function") updateScaleBar(); + stage.batchDraw(); + + // Also highlight the resource + highlightResourceOnCanvas(resourceName); +} + +// Expand/collapse tree nodes to a given depth +function getTreeDepthLimit() { + var input = document.getElementById("tree-depth-input"); + if (!input) return 1; + var val = parseInt(input.value, 10); + return isNaN(val) || val < 0 ? 1 : val; +} + +function setTreeNodeExpansion(node, depth, maxDepth, expand) { + var children = node.querySelectorAll(":scope > .tree-node-children > .tree-node"); + var childrenContainer = node.querySelector(":scope > .tree-node-children"); + var arrow = node.querySelector(":scope > .tree-node-row > .tree-node-arrow.has-children"); + if (!childrenContainer) return; + + if (expand && depth < maxDepth) { + childrenContainer.classList.remove("collapsed"); + if (arrow) arrow.textContent = "\u25BC"; + } else if (!expand && depth >= maxDepth) { + childrenContainer.classList.add("collapsed"); + if (arrow) arrow.textContent = "\u25B6"; + } + + children.forEach(function (child) { + setTreeNodeExpansion(child, depth + 1, maxDepth, expand); + }); +} + +function getMaxTreeDepth(container, depth) { + var maxD = depth; + var nodes = container.querySelectorAll(":scope > .tree-node"); + for (var i = 0; i < nodes.length; i++) { + var childrenDiv = nodes[i].querySelector(":scope > .tree-node-children"); + if (childrenDiv) { + var childMax = getMaxTreeDepth(childrenDiv, depth + 1); + if (childMax > maxD) maxD = childMax; + } + } + return maxD; +} + +function expandAllTreeNodes() { + var tree = document.getElementById("resource-tree"); + if (!tree) return; + tree.querySelectorAll(".tree-node-children.collapsed").forEach(function (el) { + el.classList.remove("collapsed"); + }); + tree.querySelectorAll(".tree-node-arrow.has-children").forEach(function (el) { + el.textContent = "\u25BC"; + }); + var depthInput = document.getElementById("tree-depth-input"); + if (depthInput) { + depthInput.value = getMaxTreeDepth(tree, 0); + } + buildWrtDropdown(); +} + +function collapseAllTreeNodes() { + var tree = document.getElementById("resource-tree"); + if (!tree) return; + var maxDepth = getTreeDepthLimit(); + var roots = tree.querySelectorAll(":scope > .tree-node"); + roots.forEach(function (root) { + setTreeNodeExpansion(root, 0, maxDepth, false); + }); + buildWrtDropdown(); +} + +// Sidepanel collapse toggle, resize, expand/collapse all +window.addEventListener("load", function () { + var expandBtn = document.getElementById("expand-all-btn"); + var collapseBtn = document.getElementById("collapse-all-btn"); + if (expandBtn) expandBtn.addEventListener("click", expandAllTreeNodes); + if (collapseBtn) collapseBtn.addEventListener("click", collapseAllTreeNodes); + + var depthInput = document.getElementById("tree-depth-input"); + if (depthInput) { + depthInput.addEventListener("input", function () { + collapseAllTreeNodes(); + }); + } + + // Left toolbar tool switching + var cursorBtn = document.getElementById("toolbar-cursor-btn"); + var coordsBtn = document.getElementById("toolbar-coords-btn"); + var coordsPanel = document.getElementById("coords-panel"); + var gifBtn = document.getElementById("toolbar-gif-btn"); + var gifPanel = document.getElementById("gif-panel"); + function setActiveTool(tool) { + activeTool = tool === "gif" ? "cursor" : tool; + if (cursorBtn) cursorBtn.classList.toggle("active", tool === "cursor"); + if (coordsBtn) coordsBtn.classList.toggle("active", tool === "coords"); + if (gifBtn) gifBtn.classList.toggle("active", tool === "gif"); + if (coordsPanel) coordsPanel.style.display = tool === "coords" ? "" : "none"; + if (gifPanel) gifPanel.style.display = tool === "gif" ? "" : "none"; + if (tool !== "coords") updateCoordsPanel(null); + } + if (cursorBtn) cursorBtn.addEventListener("click", function () { setActiveTool("cursor"); }); + if (coordsBtn) coordsBtn.addEventListener("click", function () { setActiveTool("coords"); }); + if (gifBtn) gifBtn.addEventListener("click", function () { setActiveTool("gif"); }); + + // Left toolbar collapse toggle + var leftToggle = document.getElementById("toolbar-left-toggle"); + var leftToolbar = document.getElementById("toolbar-left"); + if (leftToggle && leftToolbar) { + leftToggle.addEventListener("click", function () { + leftToolbar.classList.toggle("collapsed"); + }); + } + + const toggle = document.getElementById("toolbar-tree-btn"); + const searchBtn = document.getElementById("toolbar-search-btn"); + const panel = document.getElementById("sidepanel"); + const treeHeader = document.querySelector(".sidepanel-header"); + const treeView = document.getElementById("resource-tree"); + const searchView = document.getElementById("search-view"); + const searchInput = document.getElementById("search-input"); + + var sidepanelWidthBeforeCollapse = null; + + function setSidepanelView(view) { + // Ensure sidepanel is visible + if (panel.classList.contains("collapsed")) { + panel.classList.remove("collapsed"); + if (sidepanelWidthBeforeCollapse) { + panel.style.width = sidepanelWidthBeforeCollapse; + } else { + panel.style.width = ""; + } + } + + if (view === "tree") { + treeHeader.style.display = ""; + treeView.style.display = ""; + searchView.style.display = "none"; + toggle.classList.add("active"); + searchBtn.classList.remove("active"); + } else if (view === "search") { + treeHeader.style.display = "none"; + treeView.style.display = "none"; + searchView.style.display = ""; + toggle.classList.remove("active"); + searchBtn.classList.add("active"); + searchInput.focus(); + } + } + + if (toggle && panel) { + toggle.addEventListener("click", function () { + if (!panel.classList.contains("collapsed") && toggle.classList.contains("active")) { + // Already showing tree — collapse + sidepanelWidthBeforeCollapse = panel.style.width || panel.offsetWidth + "px"; + panel.style.width = ""; + panel.classList.add("collapsed"); + toggle.classList.remove("active"); + } else { + setSidepanelView("tree"); + } + }); + } + + if (searchBtn && panel) { + searchBtn.addEventListener("click", function () { + if (!panel.classList.contains("collapsed") && searchBtn.classList.contains("active")) { + // Already showing search — collapse + sidepanelWidthBeforeCollapse = panel.style.width || panel.offsetWidth + "px"; + panel.style.width = ""; + panel.classList.add("collapsed"); + searchBtn.classList.remove("active"); + } else { + setSidepanelView("search"); + } + }); + } + + // Fuzzy search on resources + function fuzzyMatch(query, text) { + query = query.toLowerCase(); + text = text.toLowerCase(); + var qi = 0; + for (var ti = 0; ti < text.length && qi < query.length; ti++) { + if (text[ti] === query[qi]) qi++; + } + return qi === query.length; + } + + function fuzzyScore(query, text) { + query = query.toLowerCase(); + text = text.toLowerCase(); + // Prioritize: exact match > starts with > contains > fuzzy + if (text === query) return 4; + if (text.startsWith(query)) return 3; + if (text.indexOf(query) !== -1) return 2; + return 1; + } + + // Collect all resources with a sort key matching the Workcell Tree display order. + // Each resource gets a hierarchical key [topX, depth, slotIndex, ...] for sorting. + function getResourcesInTreeOrder() { + if (!rootResource) return []; + var result = []; + + function addResource(res, sortKey) { + result.push({ resource: res, sortKey: sortKey }); + } + + // Get top-level children (deck items) sorted by x + var topChildren = getDisplayChildren(rootResource); + topChildren.sort(function (a, b) { + var ax = a.location ? a.location.x : 0; + var bx = b.location ? b.location.x : 0; + return ax - bx; + }); + + addResource(rootResource, [-1]); // root first + + for (var ti = 0; ti < topChildren.length; ti++) { + var topChild = topChildren[ti]; + var topKey = [ti]; + addResource(topChild, topKey); + + // Carrier children — include sites (resource holders) for search + var carrierChildren = getDisplayChildren(topChild); + for (var ci = 0; ci < carrierChildren.length; ci++) { + var entry = carrierChildren[ci]; + // Insert the site holder before its child at this slot + if (isCarrierLike(topChild) && entry.index !== undefined) { + var holder = topChild.children[entry.index]; + if (holder && isResourceHolder(holder)) { + addResource(holder, topKey.concat([ci, 0])); + } + } + var child = entry.resource || entry; + if (!child || entry.empty) continue; + addResource(child, topKey.concat([ci, 1])); + + // Leaf children (wells/tips) not shown in tree + if (isTipRackLike(child) || isPlateLike(child) || isTubeRackLike(child)) { + for (var li = 0; li < (child.children || []).length; li++) { + addResource(child.children[li], topKey.concat([ci, 1, li])); + } + } + + // Deeper children + var subChildren = getDisplayChildren(child); + for (var si = 0; si < subChildren.length; si++) { + var subEntry = subChildren[si]; + var subChild = subEntry.resource || subEntry; + if (!subChild || subEntry.empty) continue; + addResource(subChild, topKey.concat([ci, 1, si])); + } + } + + // If top-level item is itself a leaf rack (not on a carrier) + if (isTipRackLike(topChild) || isPlateLike(topChild) || isTubeRackLike(topChild)) { + for (var li = 0; li < (topChild.children || []).length; li++) { + addResource(topChild.children[li], topKey.concat([li])); + } + } + } + + // Sort by hierarchical key + result.sort(function (a, b) { + var ka = a.sortKey, kb = b.sortKey; + for (var i = 0; i < Math.min(ka.length, kb.length); i++) { + if (ka[i] !== kb[i]) return ka[i] - kb[i]; + } + return ka.length - kb.length; + }); + + // Deduplicate (sites may appear for each child in the same slot) + var seen = {}; + var deduped = []; + for (var i = 0; i < result.length; i++) { + var name = result[i].resource.name; + if (!seen[name]) { + seen[name] = true; + deduped.push(result[i].resource); + } + } + return deduped; + } + + function performSearch(query) { + var resultsDiv = document.getElementById("search-results"); + if (!resultsDiv) return; + resultsDiv.innerHTML = ""; + + if (!query || query.trim() === "") return; + + var includeWells = document.getElementById("search-include-wells"); + var showWells = includeWells && includeWells.checked; + var includeTips = document.getElementById("search-include-tips"); + var showTips = includeTips && includeTips.checked; + var includeSites = document.getElementById("search-include-sites"); + var showSites = includeSites && includeSites.checked; + var terms = query.trim().toLowerCase().split(/\s+/); + var allResources = getResourcesInTreeOrder(); + var matches = []; + + for (var ri = 0; ri < allResources.length; ri++) { + var resource = allResources[ri]; + var name = resource.name; + if (!resource) continue; + if (!showWells && resource instanceof Container) continue; + if (!showTips && resource instanceof TipSpot) continue; + if (!showSites && isResourceHolder(resource)) continue; + var allMatch = true; + var totalScore = 0; + for (var i = 0; i < terms.length; i++) { + if (fuzzyMatch(terms[i], name)) { + totalScore += fuzzyScore(terms[i], name); + } else if (fuzzyMatch(terms[i], resource.resourceType || "")) { + totalScore += fuzzyScore(terms[i], resource.resourceType || ""); + } else { + allMatch = false; + break; + } + } + if (allMatch) matches.push({ resource: resource, score: totalScore, order: ri }); + } + + matches.sort(function (a, b) { + if (b.score !== a.score) return b.score - a.score; + return a.order - b.order; + }); + + for (var i = 0; i < matches.length; i++) { + var res = matches[i].resource; + var row = document.createElement("div"); + row.className = "tree-node-row"; + row.style.paddingLeft = "12px"; + + var dot = document.createElement("span"); + dot.className = "tree-node-dot"; + dot.style.backgroundColor = getResourceColor(res); + + var nameSpan = document.createElement("span"); + nameSpan.className = "tree-node-name"; + nameSpan.textContent = res.name; + nameSpan.title = res.name + " (" + (res.resourceType || "") + ")"; + + var typeSpan = document.createElement("span"); + typeSpan.className = "tree-node-type"; + typeSpan.textContent = res.resourceType || ""; + + row.appendChild(dot); + row.appendChild(nameSpan); + row.appendChild(typeSpan); + + row.addEventListener("mouseenter", (function (rName) { + return function () { showHoverHighlight(rName); }; + })(res.name)); + row.addEventListener("mouseleave", function () { + clearHoverHighlight(); + }); + (function (rName) { + var searchClickTimer = null; + row.addEventListener("click", function () { + if (searchClickTimer) { clearTimeout(searchClickTimer); searchClickTimer = null; return; } + searchClickTimer = setTimeout(function () { + searchClickTimer = null; + showUmlPanel(rName); + }, 250); + }); + row.addEventListener("dblclick", function () { + if (searchClickTimer) { clearTimeout(searchClickTimer); searchClickTimer = null; } + focusOnResource(rName); + }); + })(res.name); + + resultsDiv.appendChild(row); + } + } + + if (searchInput) { + searchInput.addEventListener("input", function () { + performSearch(this.value); + }); + } + + var includeWellsCheckbox = document.getElementById("search-include-wells"); + if (includeWellsCheckbox && searchInput) { + includeWellsCheckbox.addEventListener("change", function () { + performSearch(searchInput.value); + }); + } + + var includeTipsCheckbox = document.getElementById("search-include-tips"); + if (includeTipsCheckbox && searchInput) { + includeTipsCheckbox.addEventListener("change", function () { + performSearch(searchInput.value); + }); + } + + var includeSitesCheckbox = document.getElementById("search-include-sites"); + if (includeSitesCheckbox && searchInput) { + includeSitesCheckbox.addEventListener("change", function () { + performSearch(searchInput.value); + }); + } + + // Navbar right toggle (mirrors toolbar-tree-btn behavior) + var rightToggle = document.getElementById("toolbar-right-toggle"); + var rightToolbar = document.getElementById("toolbar"); + var sidebarWasCollapsedBeforeInspectorClose = false; + + if (rightToggle && panel && rightToolbar) { + rightToggle.addEventListener("click", function () { + var toolbarHidden = rightToolbar.style.display === "none"; + + if (toolbarHidden) { + // Toolbar hidden — reopen toolbar, restore sidebar to previous state + rightToolbar.style.display = ""; + if (!sidebarWasCollapsedBeforeInspectorClose) { + panel.classList.remove("collapsed"); + if (sidepanelWidthBeforeCollapse) { + panel.style.width = sidepanelWidthBeforeCollapse; + } else { + panel.style.width = ""; + } + toggle.classList.add("active"); + } + } else { + // Toolbar visible — remember sidebar state, then collapse all + sidebarWasCollapsedBeforeInspectorClose = panel.classList.contains("collapsed"); + if (!sidebarWasCollapsedBeforeInspectorClose) { + sidepanelWidthBeforeCollapse = panel.style.width || panel.offsetWidth + "px"; + panel.style.width = ""; + panel.classList.add("collapsed"); + } + rightToolbar.style.display = "none"; + toggle.classList.remove("active"); + searchBtn.classList.remove("active"); + } + }); + } + + // Drag-to-resize handle + const handle = document.getElementById("sidepanel-resize-handle"); + if (handle && panel) { + let dragging = false; + + handle.addEventListener("mousedown", function (e) { + e.preventDefault(); + dragging = true; + handle.classList.add("dragging"); + document.body.style.cursor = "col-resize"; + document.body.style.userSelect = "none"; + }); + + document.addEventListener("mousemove", function (e) { + if (!dragging) return; + const newWidth = window.innerWidth - e.clientX; + const clamped = Math.max(150, Math.min(newWidth, window.innerWidth * 0.6)); + panel.style.width = clamped + "px"; + }); + + document.addEventListener("mouseup", function () { + if (!dragging) return; + dragging = false; + handle.classList.remove("dragging"); + document.body.style.cursor = ""; + document.body.style.userSelect = ""; + }); + } +}); + +// =========================================================================== +// UML-Style Resource Info Panel +// =========================================================================== + +var umlPanelResourceName = null; + +function getUmlAttributes(resource) { + var attrs = []; + attrs.push({ key: "name", value: JSON.stringify(resource.name) }); + attrs.push({ key: "type", value: resource.resourceType || resource.constructor.name }); + attrs.push({ key: "size_x", value: resource.size_x }); + attrs.push({ key: "size_y", value: resource.size_y }); + attrs.push({ key: "size_z", value: resource.size_z }); + if (resource.location) { + attrs.push({ key: "location", value: "(" + resource.location.x + ", " + resource.location.y + ", " + (resource.location.z || 0) + ")" }); + } + var abs = resource.getAbsoluteLocation(); + attrs.push({ key: "abs_location", value: "(" + abs.x.toFixed(1) + ", " + abs.y.toFixed(1) + ", " + (abs.z || 0).toFixed(1) + ")" }); + attrs.push({ key: "parent", value: resource.parent ? JSON.stringify(resource.parent.name) : "none" }); + attrs.push({ key: "children", value: resource.children ? resource.children.length : 0 }); + if (resource.category) { + attrs.push({ key: "category", value: resource.category }); + } + + // Container (Well/Trough/Tube) + if (resource instanceof Container) { + attrs.push({ key: "max_volume", value: resource.maxVolume }); + attrs.push({ key: "volume", value: resource.volume }); + if (resource.material_z_thickness != null) { + attrs.push({ key: "material_z_thickness", value: resource.material_z_thickness }); + } + } + // Well-specific + if (resource instanceof Well) { + if (resource.cross_section_type) { + attrs.push({ key: "cross_section_type", value: resource.cross_section_type }); + } + } + // TipSpot + if (resource instanceof TipSpot) { + attrs.push({ key: "has_tip", value: resource.has_tip }); + if (resource.tip) { + attrs.push({ key: "tip", value: JSON.stringify(resource.tip) }); + } + } + // Plate/TipRack/TubeRack + if (resource instanceof Plate || resource instanceof TipRack || resource instanceof TubeRack) { + if (resource.num_items_x != null) attrs.push({ key: "num_items_x", value: resource.num_items_x }); + if (resource.num_items_y != null) attrs.push({ key: "num_items_y", value: resource.num_items_y }); + } + // ResourceHolder + if (resource instanceof ResourceHolder) { + if (resource.spot != null) attrs.push({ key: "spot", value: resource.spot }); + } + // HamiltonSTARDeck + if (resource instanceof HamiltonSTARDeck) { + attrs.push({ key: "num_rails", value: resource.num_rails }); + } + + return attrs; +} + +function getUmlMethods(resource) { + var methods = ["getAbsoluteLocation()", "serialize()", "draw()", "destroy()"]; + if (resource instanceof Container) { + methods.push("getVolume()"); + methods.push("setVolume()"); + methods.push("setState()"); + } + if (resource instanceof TipSpot) { + methods.push("setState()"); + } + if (resource instanceof Plate || resource instanceof TipRack || resource instanceof TubeRack) { + methods.push("update()"); + } + return methods; +} + +function buildUmlPanelDOM(resource) { + var panel = document.createElement("div"); + panel.className = "uml-panel"; + panel.id = "uml-panel"; + + // Close button + var closeBtn = document.createElement("button"); + closeBtn.className = "uml-close-btn"; + closeBtn.textContent = "\u00D7"; + closeBtn.addEventListener("click", function (e) { + e.stopPropagation(); + hideUmlPanel(); + }); + panel.appendChild(closeBtn); + + // Header + var header = document.createElement("div"); + header.className = "uml-header"; + var nameDiv = document.createElement("div"); + nameDiv.className = "uml-header-name"; + nameDiv.textContent = resource.name; + var typeDiv = document.createElement("div"); + typeDiv.className = "uml-header-type"; + typeDiv.textContent = "\u00AB" + (resource.resourceType || resource.constructor.name) + "\u00BB"; + header.appendChild(nameDiv); + header.appendChild(typeDiv); + panel.appendChild(header); + + // Separator + var sep1 = document.createElement("div"); + sep1.className = "uml-separator"; + panel.appendChild(sep1); + + // Attributes section + var attrsSection = document.createElement("div"); + attrsSection.className = "uml-section"; + var attrsTitle = document.createElement("div"); + attrsTitle.className = "uml-section-title"; + attrsTitle.textContent = "Attributes"; + attrsSection.appendChild(attrsTitle); + + var attrs = getUmlAttributes(resource); + for (var i = 0; i < attrs.length; i++) { + var row = document.createElement("div"); + row.className = "uml-row"; + var keySpan = document.createElement("span"); + keySpan.className = "uml-key"; + keySpan.textContent = attrs[i].key + ":"; + var valSpan = document.createElement("span"); + valSpan.className = "uml-value"; + valSpan.textContent = " " + attrs[i].value; + row.appendChild(keySpan); + row.appendChild(valSpan); + attrsSection.appendChild(row); + } + panel.appendChild(attrsSection); + + // Separator + var sep2 = document.createElement("div"); + sep2.className = "uml-separator"; + panel.appendChild(sep2); + + // Methods section + var methodsSection = document.createElement("div"); + methodsSection.className = "uml-section"; + var methodsTitle = document.createElement("div"); + methodsTitle.className = "uml-section-title"; + methodsTitle.textContent = "Methods"; + methodsSection.appendChild(methodsTitle); + + var methods = getUmlMethods(resource); + for (var i = 0; i < methods.length; i++) { + var methodDiv = document.createElement("div"); + methodDiv.className = "uml-method"; + methodDiv.textContent = methods[i]; + methodsSection.appendChild(methodDiv); + } + panel.appendChild(methodsSection); + + return panel; +} + +function showUmlPanel(resourceName) { + var resource = resources[resourceName]; + if (!resource) return; + + // Toggle off if clicking the same resource + if (umlPanelResourceName === resourceName) { + hideUmlPanel(); + return; + } + + umlPanelResourceName = resourceName; + + // Remove existing panel + var existing = document.getElementById("uml-panel"); + if (existing) existing.remove(); + + // Build and insert the panel into
+ var mainEl = document.querySelector("main"); + if (!mainEl) return; + var panelDOM = buildUmlPanelDOM(resource); + mainEl.appendChild(panelDOM); + + // Also highlight the resource on canvas + highlightResourceOnCanvas(resourceName); +} + +function hideUmlPanel() { + umlPanelResourceName = null; + var existing = document.getElementById("uml-panel"); + if (existing) existing.remove(); +} + +// Close UML panel when clicking on empty canvas area +window.addEventListener("load", function () { + var kanvas = document.getElementById("kanvas"); + if (kanvas) { + kanvas.addEventListener("click", function (e) { + // Only close if the click is directly on the kanvas container (not on a child) + if (e.target === kanvas || e.target.tagName === "CANVAS") { + hideUmlPanel(); + } + }); + } +}); diff --git a/pylabrobot/visualizer/main.css b/pylabrobot/visualizer/main.css index 3ec438f0b47..d904140d149 100644 --- a/pylabrobot/visualizer/main.css +++ b/pylabrobot/visualizer/main.css @@ -11,26 +11,581 @@ button { nav.navbar { height: 60px; + border-bottom: 3px solid #888; + padding: 0; +} + +nav.navbar .container-fluid { + justify-content: flex-start; + align-items: center; + height: 100%; + padding: 0; + gap: 0; +} + +.navbar-divider { + width: 1px; + height: 100%; + background: #ced4da; + flex-shrink: 0; +} + +nav.navbar .navbar-brand { + margin-right: 0; + margin-left: 12px; +} + + +nav.navbar .status-box { + margin-left: auto; + margin-right: 16px; } .content { height: calc(100vh - 60px); margin: 0; + display: flex; } main { height: 100%; + flex: 1; + min-width: 0; + position: relative; +} + +#home-button { + position: absolute; + top: 20px; + right: 75px; + cursor: pointer; + opacity: 0.55; + border-radius: 6px; + padding: 2px; + background: rgba(255, 255, 255, 0.75); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + transition: opacity 0.15s, transform 0.15s, background 0.15s, box-shadow 0.15s; + z-index: 5; + line-height: 0; +} + +#home-button:hover { + opacity: 1; + transform: scale(1.12); + background: rgba(0, 255, 255, 0.4); + box-shadow: 0 0 10px rgba(0, 255, 255, 0.8); +} + +#home-button.clicked { + background: rgba(255, 230, 0, 0.65); + box-shadow: 0 0 12px rgba(255, 230, 0, 0.9); + opacity: 1; +} + +#axis-legend { + position: absolute; + top: 8px; + right: 8px; + pointer-events: none; + opacity: 0.85; + background: rgba(255, 255, 255, 0.75); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border-radius: 6px; + padding: 2px; +} + +#scale-bar { + position: absolute; + bottom: 12px; + right: 12px; + pointer-events: none; + display: flex; + flex-direction: column; + align-items: center; + background: rgba(255, 255, 255, 0.75); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border-radius: 6px; + padding: 4px 8px; +} + +#scale-bar-line { + height: 8px; + border-left: 3px solid #333; + border-right: 3px solid #333; + border-bottom: 3px solid #333; +} + +#scale-bar-label { + font-size: 13px; + font-weight: 700; + color: #333; + margin-top: 2px; + white-space: nowrap; +} + +/* Toolbar (permanent left sidebar) */ +#toolbar-left { + width: 48px; + min-width: 48px; + height: 100%; + background: #dde1e5; + border-right: 1px solid #ced4da; + display: flex; + flex-direction: column; + align-items: center; + flex-shrink: 0; + padding-top: 6px; + transition: width 0.15s, min-width 0.15s, padding 0.15s, border 0.15s; + overflow: hidden; +} + +.toolbar-left-toggle { + width: 46px; + min-width: 46px; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + border: none; + background: transparent; + cursor: pointer; + opacity: 0.7; + transition: opacity 0.15s, background 0.15s; +} + +.toolbar-left-toggle:hover, +.toolbar-right-toggle:hover { + opacity: 1; + background: #dee2e6; +} + +.toolbar-right-toggle { + width: 47px; + min-width: 47px; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + border: none; + background: transparent; + cursor: pointer; + opacity: 0.7; + transition: opacity 0.15s, background 0.15s; +} + +#toolbar-left.collapsed { + width: 0; + min-width: 0; + padding-top: 0; + border-right: none; + overflow: hidden; +} + +/* Toolbar (permanent right sidebar) */ +#toolbar { + width: 48px; + min-width: 48px; + height: 100%; + background: #dde1e5; + border-left: 1px solid #ced4da; + display: flex; + flex-direction: column; + align-items: center; + flex-shrink: 0; + padding-top: 6px; +} + +.toolbar-section { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + padding: 4px 0; + border-bottom: 1px solid #dee2e6; +} + +.toolbar-btn { + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + border: 1.5px solid #999; + background: transparent; + border-radius: 6px; + cursor: pointer; + opacity: 0.55; + transition: opacity 0.15s, background 0.15s, border-color 0.15s; +} + +.toolbar-btn:hover { + opacity: 1; + background: #ced4da; + border-color: #adb5bd; +} + +.toolbar-btn.active { + opacity: 1; + background: #cfe2ff; + border-color: #999; +} + +.tool-panel { + position: absolute; + top: 58px; + left: 8px; + background: rgba(255, 255, 255, 0.9); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border: 1px solid #ced4da; + border-radius: 8px; + padding: 8px 12px; + font-size: 15px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace; + display: flex; + flex-direction: column; + gap: 0; + z-index: 5; +} + +.tool-panel-row { + display: flex; + flex-direction: column; + align-items: center; + gap: 2px; +} + +.tool-panel-title { + font-size: 15px; + font-weight: 700; + color: #555; + margin-bottom: 6px; + width: 100%; +} + +.tool-panel-axes { + display: flex; + flex-direction: row; + gap: 12px; +} + +.tool-panel-row-full { + flex-direction: row; + gap: 6px; + width: 105%; + margin-top: 8px; + padding-top: 8px; + border-top: 1px solid #ced4da; + align-self: center; + justify-content: center; +} + +.tool-panel-row-full .tool-panel-select { + flex: 1; +} + +.tool-panel-select { + font-size: 14px; + font-family: inherit; + border: 1px solid #ced4da; + border-radius: 3px; + background: #f8f9fa; + color: #333; + padding: 1px 2px; + cursor: pointer; + text-align: center; +} + +.tool-panel-label { + font-weight: 700; + color: #555; + margin-right: 2px; +} + +.tool-panel-value { + color: #333; + font-weight: 600; + font-variant-numeric: tabular-nums; +} + +/* Sidepanel */ +#sidepanel { + position: relative; + width: 310px; + min-width: 150px; + max-width: 60vw; + height: 100%; + border-left: 1px solid #dee2e6; + background: #f8f9fa; + display: flex; + flex-direction: column; + overflow: hidden; +} + +#sidepanel.collapsed { + width: 0 !important; + min-width: 0; + border-left: none; +} + +#sidepanel.collapsed .sidepanel-header, +#sidepanel.collapsed #resource-tree, +#sidepanel.collapsed .sidepanel-resize-handle { + display: none; +} + +.sidepanel-resize-handle { + position: absolute; + left: 0; + top: 0; + width: 5px; + height: 100%; + cursor: col-resize; + z-index: 10; +} + +.sidepanel-resize-handle:hover, +.sidepanel-resize-handle.dragging { + background: #0d6efd; + opacity: 0.4; +} + +.sidepanel-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 8px 12px; + border-bottom: 1px solid #dee2e6; + background: #e9ecef; +} + +.sidepanel-title { + font-weight: 700; + font-size: 18px; + color: #333; +} + +.sidepanel-header-actions { + display: flex; + gap: 4px; +} + +.tree-action-btn { + width: 26px; + height: 26px; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid #ced4da; + background: #f8f9fa; + border-radius: 4px; + cursor: pointer; + opacity: 0.7; + transition: opacity 0.15s, background 0.15s; +} + +.tree-action-btn:hover { + opacity: 1; + background: #dee2e6; +} + +.tree-depth-input { + width: 36px; + height: 26px; + text-align: center; + font-size: 13px; + font-weight: 600; + border: 1px solid #ced4da; + border-radius: 4px; + background: #f8f9fa; + color: #333; + padding: 0 2px; + -moz-appearance: textfield; +} + +.tree-depth-input::-webkit-outer-spin-button, +.tree-depth-input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +#resource-tree { + flex: 1; + overflow-y: auto; + padding: 4px 0; + font-size: 17px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; +} + +/* Tree nodes */ +.tree-node { + user-select: none; +} + +.tree-node-row { + display: flex; + align-items: center; + padding: 3px 8px; + cursor: pointer; + white-space: nowrap; +} + +.tree-node-row:hover, +.tree-node-row.canvas-hover { + background: #dee2e6; +} + +.tree-node-row.selected { + background: #cfe2ff; +} + +.tree-node-arrow { + width: 16px; + font-size: 10px; + color: #666; + text-align: center; + flex-shrink: 0; +} + +.tree-node-arrow.has-children { + cursor: pointer; +} + +.tree-node-dot { + width: 8px; + height: 8px; + border-radius: 50%; + margin-right: 6px; + flex-shrink: 0; +} + +.tree-node-index { + min-width: 18px; + text-align: center; + font-size: 12px; + font-weight: 700; + color: #666; + margin-right: 5px; + flex-shrink: 0; +} + +.tree-node-row.empty-slot .tree-node-name.empty { + color: #aaa; + font-style: italic; + font-weight: 400; +} + +.tree-node-name { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + color: #333; + font-weight: 600; +} + +.tree-node-type { + margin-left: 6px; + font-size: 12px; + color: #999; + flex-shrink: 0; + font-weight: 400; +} + +.tree-node-info { + margin-left: 6px; + font-size: 14px; + color: #555; + flex-shrink: 0; + font-weight: 600; +} + +.tree-node-children { + display: block; +} + +.tree-node-children.collapsed { + display: none; +} + +/* Search view */ +#search-view { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.search-header { + padding: 8px 12px; + border-bottom: 1px solid #dee2e6; +} + +.search-input { + width: 100%; + padding: 8px 10px; + font-size: 17px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + border: 1px solid #ced4da; + border-radius: 5px; + background: #fff; + color: #333; + outline: none; +} + +.search-filters { + display: flex; + align-items: center; + gap: 10px; + margin-top: 8px; +} + +.search-filters-label { + font-size: 15px; + font-weight: 600; + color: #555; +} + +.search-filter-label { + display: flex; + align-items: center; + gap: 5px; + font-size: 15px; + color: #555; + cursor: pointer; + user-select: none; +} + +.search-input:focus { + border-color: #86b7fe; + box-shadow: 0 0 0 2px rgba(13, 110, 253, 0.15); +} + +#search-results { + flex: 1; + overflow-y: auto; + padding: 4px 0; + font-size: 17px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; } #kanvas { width: 100%; height: 100%; - overflow: scroll; + overflow: hidden; +} + +.status-box { + display: flex; + align-items: center; + gap: 8px; } #status-indicator { - height: 10px; - width: 10px; + height: 14px; + width: 14px; border-radius: 50%; display: inline-block; background-color: gray; @@ -38,6 +593,8 @@ main { #status-label { color: gray; + font-size: 18px; + font-weight: 600; } #status-indicator.connected { @@ -62,14 +619,111 @@ h3.form-row-header { margin-bottom: 8px; } -/* gif recording */ +/* UML-style resource info panel */ +.uml-panel { + position: absolute; + top: 58px; + right: 8px; + width: 290px; + max-height: calc(100% - 70px); + overflow-y: auto; + background: rgba(255, 255, 255, 0.92); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border: 1px solid #ced4da; + border-radius: 8px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace; + font-size: 13px; + z-index: 6; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.12); +} + +.uml-header { + padding: 10px 12px 8px; +} + +.uml-header-name { + font-weight: 700; + font-size: 15px; + color: #222; +} + +.uml-header-type { + font-style: italic; + font-size: 12px; + color: #777; + margin-top: 1px; +} + +.uml-separator { + height: 1px; + background: #ced4da; + margin: 0; +} + +.uml-section { + padding: 6px 12px; +} + +.uml-section-title { + font-size: 11px; + font-weight: 700; + color: #999; + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 4px; +} + +.uml-row { + display: flex; + align-items: baseline; + padding: 1px 0; + line-height: 1.4; +} + +.uml-key { + color: #777; + margin-right: 4px; + flex-shrink: 0; +} + +.uml-value { + color: #333; + font-weight: 600; + word-break: break-all; +} + +.uml-method { + color: #555; + padding: 1px 0; + line-height: 1.4; +} -.recording-stuff { +.uml-close-btn { + position: absolute; + top: 6px; + right: 8px; + width: 20px; + height: 20px; display: flex; align-items: center; justify-content: center; + border: none; + background: transparent; + cursor: pointer; + font-size: 16px; + color: #999; + border-radius: 3px; + line-height: 1; } +.uml-close-btn:hover { + background: #dee2e6; + color: #333; +} + +/* gif recording */ + .gif-box { display: flex; align-items: center; diff --git a/pylabrobot/visualizer/vis.js b/pylabrobot/visualizer/vis.js index 9c3c6a5a47b..fcaf0d97a8c 100644 --- a/pylabrobot/visualizer/vis.js +++ b/pylabrobot/visualizer/vis.js @@ -30,11 +30,12 @@ function setRootResource(data) { resource.location = { x: 0, y: 0, z: 0 }; resource.draw(resourceLayer); - // center the root resource on the stage. - let centerXOffset = (stage.width() - resource.size_x) / 2; - let centerYOffset = (stage.height() - resource.size_y) / 2; - stage.x(centerXOffset); - stage.y(-centerYOffset); + // Store globally so fitToViewport() can use it. + rootResource = resource; + + fitToViewport(); + + buildResourceTree(resource); } function removeResource(resourceName) { @@ -46,7 +47,15 @@ function setState(allStates) { for (let resourceName in allStates) { let state = allStates[resourceName]; let resource = resources[resourceName]; - resource.setState(state); + if (!resource) { + console.warn(`[setState] resource not found: ${resourceName}`); + continue; + } + try { + resource.setState(state); + } catch (e) { + console.error(`[setState] error for ${resourceName}:`, e); + } } } @@ -60,16 +69,22 @@ async function processCentralEvent(event, data) { resource = loadResource(data.resource); resource.draw(resourceLayer); setState(data.state); + addResourceToTree(resource); break; case "resource_unassigned": + removeResourceFromTree(data.resource_name); removeResource(data.resource_name); break; case "set_state": let allStates = data; setState(allStates); - + // Rebuild the sidepanel tree so summaries reflect the updated state + const rootName = Object.keys(resources).find( + (n) => resources[n] && !resources[n].parent + ); + if (rootName) buildResourceTree(resources[rootName]); break; default: diff --git a/pylabrobot/visualizer/visualizer.py b/pylabrobot/visualizer/visualizer.py index 5c70a9f8667..2694b682306 100644 --- a/pylabrobot/visualizer/visualizer.py +++ b/pylabrobot/visualizer/visualizer.py @@ -1,5 +1,6 @@ import asyncio import http.server +import inspect import json import logging import os @@ -20,6 +21,8 @@ from pylabrobot.__version__ import STANDARD_FORM_JSON_VERSION from pylabrobot.resources import Resource +from pylabrobot.resources.tip_tracker import set_tip_tracking +from pylabrobot.resources.volume_tracker import set_volume_tracking logger = logging.getLogger("pylabrobot") @@ -45,6 +48,8 @@ def __init__( ws_port: int = 2121, fs_port: int = 1337, open_browser: bool = True, + name: Optional[str] = None, + favicon: Optional[str] = None, ): """Create a new Visualizer. Use :meth:`.setup` to start the visualization. @@ -55,10 +60,28 @@ def __init__( fs_port: The port of the file server. If this port is in use, the port will be incremented until a free port is found. open_browser: If `True`, the visualizer will open a browser window when it is started. + name: A custom name to display in the browser header. If ``None``, the filename of the + calling script or notebook is detected automatically. + favicon: Path to a ``.png`` file to use as the browser tab icon. If ``None``, the + PyLabRobot logo is used. """ self.setup_finished = False + if name is not None: + self._source_filename = name + else: + self._source_filename = self._detect_source_filename() + + if favicon is not None: + if not favicon.endswith(".png"): + raise ValueError("favicon must be a .png file") + if not os.path.isfile(favicon): + raise FileNotFoundError(f"favicon file not found: {favicon}") + self._favicon_path = os.path.abspath(favicon) + else: + self._favicon_path = os.path.join(os.path.dirname(__file__), "img", "logo.png") + # Hook into the resource (un)assigned callbacks so we can send the appropriate events to the # browser. self._root_resource = resource @@ -182,7 +205,13 @@ def _assemble_command( "data": data, "event": event, } - return json.dumps(command_data), id_ + # Python's json.dumps outputs bare Infinity/-Infinity for float('inf'), which is + # not valid JSON and causes JSON.parse() in the browser to throw SyntaxError. + # Replace bare tokens with quoted strings so the browser's JSON reviver can handle them. + serialized = json.dumps(command_data) + serialized = serialized.replace(": Infinity", ': "Infinity"') + serialized = serialized.replace(": -Infinity", ': "-Infinity"') + return serialized, id_ def has_connection(self) -> bool: """Return `True` if a websocket connection has been established.""" @@ -255,6 +284,91 @@ def fst(self) -> threading.Thread: raise RuntimeError("The file server thread has not been started yet.") return self._fst + @staticmethod + def _detect_source_filename() -> str: + """Detect the filename of the calling script or notebook.""" + + # 1. VS Code sets __vsc_ipynb_file__ in the IPython user namespace. + try: + ipython = get_ipython() # type: ignore[name-defined] # noqa: F821 + vsc_file = getattr(ipython, "user_ns", {}).get("__vsc_ipynb_file__") + if vsc_file: + return os.path.basename(vsc_file) + except NameError: + pass + + # 2. Try ipynbname package (works for classic Jupyter Notebook and JupyterLab). + try: + import ipynbname # type: ignore[import-untyped] + + nb_path = ipynbname.path() + if nb_path: + return os.path.basename(str(nb_path)) + except Exception: + pass + + # 3. Query the Jupyter REST API using the kernel connection file. + try: + import ipykernel # type: ignore[import-untyped] + import json as _json + import re + import urllib.request + + # Get the kernel id from the connection file path. + connection_file = ipykernel.get_connection_file() + kernel_id = os.path.basename(connection_file).replace("kernel-", "").replace(".json", "") + + # Try common Jupyter server ports and tokens. + # First, try to get server info from jupyter_core / notebook. + servers = [] + try: + from jupyter_server.serverapp import list_running_servers # type: ignore[import-untyped] + + servers = list(list_running_servers()) + except Exception: + pass + if not servers: + try: + from notebook.notebookapp import list_running_servers # type: ignore[import-untyped] + + servers = list(list_running_servers()) + except Exception: + pass + + for srv in servers: + base_url = srv.get("url", "").rstrip("/") + token = srv.get("token", "") + try: + api_url = f"{base_url}/api/sessions" + if token: + api_url += f"?token={token}" + req = urllib.request.Request(api_url) + with urllib.request.urlopen(req, timeout=2) as resp: + sessions = _json.loads(resp.read().decode()) + for sess in sessions: + kid = sess.get("kernel", {}).get("id", "") + if kid == kernel_id: + nb_path = sess.get("notebook", {}).get("path", "") or sess.get("path", "") + if nb_path: + return os.path.basename(nb_path) + except Exception: + continue + except Exception: + pass + + # 4. Fall back to stack inspection for .py scripts. + for frame_info in inspect.stack(): + fname = frame_info.filename + if fname == __file__: + continue + basename = os.path.basename(fname) + if "ipykernel" in fname or fname.startswith("<"): + return "" + if basename.endswith(".py"): + return basename + + return "" + async def setup(self): """Start the visualizer. @@ -264,6 +378,10 @@ async def setup(self): if self.setup_finished: raise RuntimeError("The visualizer has already been started.") + # Enable tip and volume tracking so the visualizer receives real-time state updates. + set_tip_tracking(True) + set_volume_tracking(True) + await self._run_ws_server() self._run_file_server() self.setup_finished = True @@ -318,7 +436,8 @@ def _run_file_server(self): ) def start_server(lock): - ws_port, fs_port = self.ws_port, self.fs_port + ws_port, fs_port, source_filename = self.ws_port, self.fs_port, self._source_filename + favicon_path = self._favicon_path # try to start the server. If the port is in use, try with another port until it succeeds. class QuietSimpleHTTPRequestHandler(http.server.SimpleHTTPRequestHandler): @@ -330,6 +449,12 @@ def __init__(self, *args, **kwargs): def log_message(self, format, *args): pass + def end_headers(self): + self.send_header("Cache-Control", "no-cache, no-store, must-revalidate") + self.send_header("Pragma", "no-cache") + self.send_header("Expires", "0") + super().end_headers() + def do_GET(self) -> None: # rewrite some info in the index.html file on the fly, # like a simple template engine @@ -339,11 +464,19 @@ def do_GET(self) -> None: content = content.replace("{{ ws_port }}", str(ws_port)) content = content.replace("{{ fs_port }}", str(fs_port)) + content = content.replace("{{ source_filename }}", source_filename) self.send_response(200) self.send_header("Content-type", "text/html") self.end_headers() self.wfile.write(content.encode("utf-8")) + elif self.path == "/favicon.png": + with open(favicon_path, "rb") as f: + data = f.read() + self.send_response(200) + self.send_header("Content-type", "image/png") + self.end_headers() + self.wfile.write(data) else: return super().do_GET() From 4b9e5078d2623d67b7346fba6191ccf16a7aaad6 Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Sun, 1 Feb 2026 17:04:24 +0000 Subject: [PATCH 02/20] testing with Rick --- pylabrobot/visualizer/lib.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pylabrobot/visualizer/lib.js b/pylabrobot/visualizer/lib.js index 061801d3a87..babebc4517d 100644 --- a/pylabrobot/visualizer/lib.js +++ b/pylabrobot/visualizer/lib.js @@ -454,8 +454,7 @@ class Resource { const cy = base.y + yOff; const cz = (base.z || 0) + zOff; const czStr = zNA ? "na" : cz.toFixed(1); - const isAbsolute = sidebarRootResource && wrtName === sidebarRootResource.name; - const wrtLabel = isAbsolute ? "abs" : "wrt " + wrtName; + const wrtLabel = "wrt " + wrtName; labelText = `${this.name}\n${wrtLabel}: (${cx.toFixed(1)}, ${cy.toFixed(1)}, ${czStr}) mm`; } else { labelText = this.tooltipLabel(); From 1a6b9367900d8c364d4214d4b0ffde181780048a Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Mon, 2 Feb 2026 00:25:39 +0000 Subject: [PATCH 03/20] create "Module Section" / liquid handler displaying pipette states multi-channel wip, integrated arm wip --- pylabrobot/visualizer/img/integrated_arm.png | Bin 0 -> 28655 bytes .../visualizer/img/multi_channel_pipette.png | Bin 0 -> 28110 bytes .../visualizer/img/single_channel_pipette.png | Bin 0 -> 7490 bytes pylabrobot/visualizer/index.html | 1 + pylabrobot/visualizer/lib.js | 320 ++++++++++++++++++ pylabrobot/visualizer/main.css | 79 +++++ pylabrobot/visualizer/visualizer.py | 7 +- 7 files changed, 403 insertions(+), 4 deletions(-) create mode 100644 pylabrobot/visualizer/img/integrated_arm.png create mode 100644 pylabrobot/visualizer/img/multi_channel_pipette.png create mode 100644 pylabrobot/visualizer/img/single_channel_pipette.png diff --git a/pylabrobot/visualizer/img/integrated_arm.png b/pylabrobot/visualizer/img/integrated_arm.png new file mode 100644 index 0000000000000000000000000000000000000000..0e518888d8322ee326045bab297fea570e3dcabd GIT binary patch literal 28655 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 literal 0 HcmV?d00001 diff --git a/pylabrobot/visualizer/img/multi_channel_pipette.png b/pylabrobot/visualizer/img/multi_channel_pipette.png new file mode 100644 index 0000000000000000000000000000000000000000..80baac8ee1203a452d5e201badd10c27019669d0 GIT binary patch literal 28110 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 Al>h($ literal 0 HcmV?d00001 diff --git a/pylabrobot/visualizer/img/single_channel_pipette.png b/pylabrobot/visualizer/img/single_channel_pipette.png new file mode 100644 index 0000000000000000000000000000000000000000..e0cc7cd26b7dc3c68e621b34ce9fcf3e6b040989 GIT binary patch literal 7490 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 literal 0 HcmV?d00001 diff --git a/pylabrobot/visualizer/index.html b/pylabrobot/visualizer/index.html index 75fbdeffd74..0668f32c67b 100644 --- a/pylabrobot/visualizer/index.html +++ b/pylabrobot/visualizer/index.html @@ -34,6 +34,7 @@ {{ source_filename }} +
diff --git a/pylabrobot/visualizer/lib.js b/pylabrobot/visualizer/lib.js index babebc4517d..320cae01c33 100644 --- a/pylabrobot/visualizer/lib.js +++ b/pylabrobot/visualizer/lib.js @@ -1195,10 +1195,86 @@ class TubeRack extends Resource { class PlateHolder extends ResourceHolder {} +function fillHeadIcons(panel, headState) { + panel.innerHTML = ""; + panel.style.display = "flex"; + panel.style.flexWrap = "wrap"; + panel.style.gap = "6px"; + panel.style.alignItems = "flex-start"; + var channels = Object.keys(headState).sort(function (a, b) { return +a - +b; }); + for (var ci = 0; ci < channels.length; ci++) { + var ch = channels[ci]; + var tipData = headState[ch] && headState[ch].tip; + var hasTip = tipData !== null && tipData !== undefined; + // Scale tip length: total_tip_length in mm, map to px (0.4 px/mm, min 10, max 40) + var tipLenPx = 0; + if (hasTip && tipData.total_tip_length) { + tipLenPx = Math.max(10, Math.min(40, tipData.total_tip_length * 0.4)); + } + var col = document.createElement("div"); + col.style.display = "flex"; + col.style.flexDirection = "column"; + col.style.alignItems = "center"; + var label = document.createElement("span"); + label.textContent = ch; + label.style.fontSize = "15px"; + label.style.fontWeight = "700"; + label.style.color = "#888"; + label.style.marginBottom = "2px"; + col.appendChild(label); + // Base pipette is 27px; tip adds a straight section + taper below + var svgH = hasTip ? 27 + tipLenPx : 27; + var icon = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + icon.setAttribute("width", "14"); + icon.setAttribute("height", String(svgH)); + icon.setAttribute("viewBox", "0 0 14 " + svgH); + // Black cylinder (top): 14px wide, 20px tall with rounded ends + var shapes = + '' + + '' + + '' + + // Silver cylinder (bottom): 10px wide, 5px tall, centered + '' + + '' + + ''; + if (hasTip) { + // Tip: straight section (40% of length) then taper to point (60%) + var straightH = Math.round(tipLenPx * 0.4); + var taperH = tipLenPx - straightH; + var straightEnd = 25 + straightH; + var tipEnd = straightEnd + taperH; + shapes += + '' + + ''; + } + icon.innerHTML = shapes; + col.appendChild(icon); + panel.appendChild(col); + } +} + class LiquidHandler extends Resource { + constructor(resource) { + super(resource); + this.numHeads = 0; + this.headState = {}; + } + drawMainShape() { return undefined; // just draw the children (deck and so on) } + + setState(state) { + if (state.head_state) { + this.headState = state.head_state; + this.numHeads = Object.keys(state.head_state).length; + // Update dropdown panel if it exists + var panel = document.getElementById("single-channel-dropdown-" + this.name); + if (panel) { + fillHeadIcons(panel, this.headState); + } + } + } } // =========================================================================== @@ -1998,6 +2074,7 @@ function buildResourceTree(rootResource) { treeContainer.appendChild(rootNode); buildWrtDropdown(); + buildNavbarLHModules(); } function addResourceToTree(resource) { @@ -2944,3 +3021,246 @@ window.addEventListener("load", function () { }); } }); + +// =========================================================================== +// Navbar Liquid Handler Module Buttons +// =========================================================================== + +function makeSVG(viewBox, innerHTML) { + var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + svg.setAttribute("width", "40"); + svg.setAttribute("height", "40"); + svg.setAttribute("viewBox", viewBox); + svg.setAttribute("preserveAspectRatio", "xMidYMid meet"); + svg.innerHTML = innerHTML; + return svg; +} + +var multiChannelSVG = (function () { + var body = + '' + + // === Main body — large isometric block === + '' + + '' + + '' + + '' + + // === Dark adapter plate === + '' + + '' + + ''; + + // === Isometric tip grid === + // The adapter plate bottom face is a diamond: + // back=(20,21), left=(2,21), front=(20,28), right=(38,21) + // Isometric axes on this face: + // "column" axis: left→right = from (2,21) toward (38,21) through back → direction (+2.25, -0.44) + // "row" axis: back→front = from back toward (20,28) → direction (-2.25, +0.44) per step + // But we want to shift each row forward (toward viewer). + // + // Grid: 8 columns x 6 rows. Origin at center of adapter bottom face. + // Column direction (along right edge): dx_c=+2.25, dy_c=-0.44 + // Row direction (toward front): dx_r=-2.25, dy_r=+0.44 + // Center of bottom face: (20, 24.5) + + var cx = 20, cy = 24.5; + var cols = 8, rows = 6; + var dx_c = 2.1, dy_c = -0.75; // step per column (going right-back) + var dx_r = -2.1, dy_r = 0.75; // step per row (going left-front) + var tipLen = 11; // tip length (straight down) + + // Collect all tips with their positions, sorted back-to-front for correct overlap + var tips = []; + for (var r = 0; r < rows; r++) { + for (var c = 0; c < cols; c++) { + var cc = c - (cols - 1) / 2; // center columns + var rr = r - (rows - 1) / 2; // center rows + var x = cx + cc * dx_c + rr * dx_r; + var y = cy + cc * dy_c + rr * dy_r; + // depth: back rows (low r) are far, front rows (high r) are near + tips.push({ x: x, y: y, row: r }); + } + } + // Sort back-to-front (low y first = back = draw first) + tips.sort(function (a, b) { return a.y - b.y; }); + + var tipsSvg = ''; + for (var i = 0; i < tips.length; i++) { + var t = tips[i]; + // Depth shading: back rows lighter, front rows darker + var frac = t.row / (rows - 1); // 0=back, 1=front + var gray = Math.round(180 - frac * 150); // 180 (light) → 30 (dark) + var sw = 0.5 + frac * 0.5; // 0.5 → 1.0 stroke width + var color = 'rgb(' + gray + ',' + gray + ',' + gray + ')'; + var x1 = t.x.toFixed(1); + var y1 = t.y.toFixed(1); + var y2 = (t.y + tipLen).toFixed(1); + tipsSvg += ''; + } + + return body + tipsSvg + ''; +})(); + +var singleChannelSVG = + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + ''; + +var integratedArmSVG = + '' + + // --- Dark vertical column (isometric cylinder) --- + // Column left face + '' + + // Column right face + '' + + // Column top ellipse + '' + + // Highlight strip + '' + + // --- White carriage plate (isometric box) --- + // Plate top face + '' + + // Plate front-left face + '' + + // Plate front-right face + '' + + // --- Back arm (upper rail, extends front-right) --- + // Back arm top + '' + + // Back arm front face + '' + + // Back arm right end + '' + + // --- Front arm (lower rail, extends front-right) --- + // Front arm top + '' + + // Front arm front face + '' + + // Front arm right end + '' + + // --- Back gripper (at end of back arm) --- + // Back crossbar top + '' + + // Back crossbar front + '' + + // Back left finger + '' + + // Back right finger + '' + + // --- Front gripper (at end of front arm) --- + // Front crossbar top + '' + + // Front crossbar front + '' + + // Front left finger + '' + + // Front right finger + '' + + ''; + +function buildNavbarLHModules() { + var container = document.getElementById("navbar-lh-modules"); + if (!container) return; + container.innerHTML = ""; + + // Find all LiquidHandler resources + var lhNames = []; + for (var name in resources) { + if (resources[name] instanceof LiquidHandler) { + lhNames.push(name); + } + } + + for (var i = 0; i < lhNames.length; i++) { + var lhName = lhNames[i]; + var group = document.createElement("div"); + group.className = "navbar-pipette-group"; + + // Label + var label = document.createElement("span"); + label.className = "navbar-pipette-label"; + label.textContent = lhName; + group.appendChild(label); + + // Multi-channel button + var multiBtn = document.createElement("button"); + multiBtn.className = "navbar-pipette-btn"; + multiBtn.title = "Multi-Channel Pipettes"; + var multiImg = document.createElement("img"); + multiImg.src = "img/multi_channel_pipette.png"; + multiImg.alt = "Multi-Channel Pipettes"; + multiImg.style.width = "44px"; + multiImg.style.height = "44px"; + multiImg.style.objectFit = "contain"; + multiBtn.appendChild(multiImg); + group.appendChild(multiBtn); + + // Single-channel button + var singleBtn = document.createElement("button"); + singleBtn.className = "navbar-pipette-btn"; + singleBtn.title = "Single-Channel Pipettes"; + var singleImg = document.createElement("img"); + singleImg.src = "img/single_channel_pipette.png"; + singleImg.alt = "Single-Channel Pipettes"; + singleImg.style.width = "44px"; + singleImg.style.height = "44px"; + singleImg.style.objectFit = "contain"; + singleBtn.appendChild(singleImg); + group.appendChild(singleBtn); + + // Single-channel dropdown panel + (function (btn, handlerName) { + var panelId = "single-channel-dropdown-" + handlerName; + btn.addEventListener("click", function () { + var existing = document.getElementById(panelId); + if (existing) { + // Toggle + var isOpen = existing.classList.toggle("open"); + btn.classList.toggle("active", isOpen); + return; + } + // Create the dropdown panel inside
+ var mainEl = document.querySelector("main"); + if (!mainEl) return; + var panel = document.createElement("div"); + panel.className = "module-dropdown open"; + panel.id = panelId; + // Position below the button + var btnRect = btn.getBoundingClientRect(); + var mainRect = mainEl.getBoundingClientRect(); + panel.style.top = (btnRect.bottom - mainRect.top + 20) + "px"; + panel.style.left = (btnRect.left - mainRect.left + btnRect.width / 2) + "px"; + // Show head icons from LiquidHandler state + var lhResource = resources[handlerName]; + var headState = (lhResource && lhResource.headState) ? lhResource.headState : {}; + fillHeadIcons(panel, headState); + mainEl.appendChild(panel); + btn.classList.add("active"); + }); + })(singleBtn, lhName); + + // Integrated arm button + var armBtn = document.createElement("button"); + armBtn.className = "navbar-pipette-btn"; + armBtn.title = "Integrated Arm"; + var armImg = document.createElement("img"); + armImg.src = "img/integrated_arm.png"; + armImg.alt = "Integrated Arm"; + armImg.style.width = "44px"; + armImg.style.height = "44px"; + armImg.style.objectFit = "contain"; + armBtn.appendChild(armImg); + group.appendChild(armBtn); + + container.appendChild(group); + } +} diff --git a/pylabrobot/visualizer/main.css b/pylabrobot/visualizer/main.css index d904140d149..e0b5428917e 100644 --- a/pylabrobot/visualizer/main.css +++ b/pylabrobot/visualizer/main.css @@ -41,6 +41,85 @@ nav.navbar .status-box { margin-right: 16px; } +#navbar-lh-modules { + position: absolute; + left: 50%; + transform: translateX(-50%); + display: flex; + align-items: center; + gap: 8px; +} + +.navbar-pipette-group { + display: flex; + align-items: center; + border: 1.5px solid #ced4da; + border-radius: 6px; + background: #f8f9fa; + overflow: hidden; +} + +.navbar-pipette-label { + padding: 0 8px 0 10px; + font-size: 12px; + font-weight: 700; + color: #555; + white-space: nowrap; + border-right: 1px solid #ced4da; + height: 48px; + display: flex; + align-items: center; +} + +.navbar-pipette-btn { + border: none; + background: transparent; + width: 48px; + height: 48px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + opacity: 0.8; + transition: opacity 0.15s, background 0.15s; + padding: 0; +} + +.navbar-pipette-btn:hover { + opacity: 1; + background: #dee2e6; +} + +.navbar-pipette-btn + .navbar-pipette-btn { + border-left: 1px solid #ced4da; +} + +.navbar-pipette-btn.active { + background: #cfe2ff; + opacity: 1; +} + +.module-dropdown { + position: absolute; + top: 0; + left: 50%; + transform: translateX(-50%); + background: rgba(255, 255, 255, 0.92); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border: 2px solid #999; + border-radius: 8px; + padding: 16px 20px; + z-index: 5; + width: fit-content; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.12); + display: none; +} + +.module-dropdown.open { + display: flex; +} + .content { height: calc(100vh - 60px); margin: 0; diff --git a/pylabrobot/visualizer/visualizer.py b/pylabrobot/visualizer/visualizer.py index 2694b682306..06aa72bdfbf 100644 --- a/pylabrobot/visualizer/visualizer.py +++ b/pylabrobot/visualizer/visualizer.py @@ -564,10 +564,9 @@ async def _send_resources_and_state(self): def save_resource_state(resource: Resource): """Recursively save the state of the resource and all child resources.""" - if hasattr(resource, "tracker"): - resource_state = resource.tracker.serialize() - if resource_state is not None: - state[resource.name] = resource_state + resource_state = resource.serialize_state() + if resource_state: + state[resource.name] = resource_state for child in resource.children: save_resource_state(child) From ac1402adbc19342dfa2c1a6cf257fae7743be0c7 Mon Sep 17 00:00:00 2001 From: Rick Wierenga Date: Mon, 2 Feb 2026 11:11:28 -0800 Subject: [PATCH 04/20] remove unused variables --- pylabrobot/visualizer/lib.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/pylabrobot/visualizer/lib.js b/pylabrobot/visualizer/lib.js index 320cae01c33..e7c69a76a33 100644 --- a/pylabrobot/visualizer/lib.js +++ b/pylabrobot/visualizer/lib.js @@ -120,7 +120,6 @@ function getWrtAnchorOffset(wrtResource) { else if (yRef && yRef.value === "back") yOff = wrtResource.size_y; var zOff = 0; - var zNA = false; if (zRef) { if (zRef.value === "center") zOff = wrtResource.size_z / 2; else if (zRef.value === "top") zOff = wrtResource.size_z; @@ -155,7 +154,6 @@ var scaleX, scaleY; var resources = {}; // name -> Resource object -var homeView = null; // saved initial view {x, y, scaleX, scaleY} var rootResource = null; // the root resource for fit-to-viewport function fitToViewport() { From 98e37caf1fb7d3da4f25b7920c136c840a8ec384 Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Mon, 2 Feb 2026 20:49:13 +0000 Subject: [PATCH 05/20] updates to arm tracking --- .../liquid_handling/backends/chatterbox.py | 1 + .../backends/hamilton/STAR_backend.py | 1 + .../backends/hamilton/STAR_chatterbox.py | 17 +- .../backends/hamilton/STAR_tests.py | 5 + .../backends/hamilton/vantage_tests.py | 1 + pylabrobot/liquid_handling/liquid_handler.py | 66 ++- .../liquid_handling/liquid_handler_tests.py | 1 + .../visualizer/img/multi_channel_pipette.png | Bin 28110 -> 38330 bytes pylabrobot/visualizer/index.html | 5 +- pylabrobot/visualizer/lib.js | 493 +++++++++++++++++- pylabrobot/visualizer/main.css | 46 +- pylabrobot/visualizer/vis.js | 19 + 12 files changed, 615 insertions(+), 40 deletions(-) diff --git a/pylabrobot/liquid_handling/backends/chatterbox.py b/pylabrobot/liquid_handling/backends/chatterbox.py index 183eebaab60..ce95096c078 100644 --- a/pylabrobot/liquid_handling/backends/chatterbox.py +++ b/pylabrobot/liquid_handling/backends/chatterbox.py @@ -43,6 +43,7 @@ def __init__(self, num_channels: int = 8): """Initialize a chatter box backend.""" super().__init__() self._num_channels = num_channels + self.num_arms = 1 async def setup(self): await super().setup() diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py index ed16f51ff40..af819994660 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py @@ -1447,6 +1447,7 @@ async def setup( self.autoload_installed = autoload_configuration_byte == "1" self.core96_head_installed = left_x_drive_configuration_byte_1[2] == "1" self.iswap_installed = left_x_drive_configuration_byte_1[1] == "1" + self.num_arms = 1 if self.iswap_installed else 0 self._head96_information: Optional[Head96Information] = None initialized = await self.request_instrument_initialization_status() diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR_chatterbox.py b/pylabrobot/liquid_handling/backends/hamilton/STAR_chatterbox.py index ade7567e232..54bf816d19f 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR_chatterbox.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR_chatterbox.py @@ -10,17 +10,24 @@ class STARChatterboxBackend(STARBackend): """Chatterbox backend for 'STAR'""" - def __init__(self, num_channels: int = 8, core96_head_installed: bool = True): + def __init__( + self, + num_channels: int = 8, + core96_head_installed: bool = True, + iswap_installed: bool = True, + ): """Initialize a chatter box backend. Args: num_channels: Number of pipetting channels (default: 8) core96_head_installed: Whether the CoRe 96 head is installed (default: True) + iswap_installed: Whether the iSWAP robotic arm is installed (default: True) """ super().__init__() self._num_channels = num_channels self._iswap_parked = True self._core96_head_installed = core96_head_installed + self._iswap_installed = iswap_installed async def setup( self, @@ -53,6 +60,7 @@ async def setup( # Use bitwise operations to check specific bits self.iswap_installed = bool(xl_value & 0b10) # Check bit 1 self.core96_head_installed = bool(xl_value & 0b100) # Check bit 2 + self.num_arms = 1 if self.iswap_installed else 0 # Parse autoload from kb configuration byte configuration_data1 = bin(conf["kb"]).split("b")[-1].zfill(8) @@ -132,12 +140,13 @@ async def request_extended_configuration(self): """ # Calculate xl byte based on installed modules # Bit 0: (reserved) - # Bit 1: iSWAP (always True in this mock) + # Bit 1: iSWAP (based on __init__ parameter) # Bit 2: 96-head (based on __init__ parameter) - xl_value = 0b10 # iSWAP installed (bit 1) + xl_value = 0 + if self._iswap_installed: + xl_value |= 0b10 # Add iSWAP (bit 1) if self._core96_head_installed: xl_value |= 0b100 # Add 96-head (bit 2) - # Result: xl = 6 (0b110) if 96-head installed, 2 (0b10) if not self._extended_conf = { "ka": 65537, diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR_tests.py b/pylabrobot/liquid_handling/backends/hamilton/STAR_tests.py index 89ade38b7f1..92be623f6bb 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR_tests.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR_tests.py @@ -178,6 +178,7 @@ def __init__(self): async def setup(self) -> None: # type: ignore self._num_channels = 8 self.iswap_installed = True + self.num_arms = 1 self.core96_head_installed = True self._core_parked = True @@ -262,6 +263,7 @@ def __init__(self, name: str): self.STAR._num_channels = 8 self.STAR.core96_head_installed = True self.STAR.iswap_installed = True + self.STAR.num_arms = 1 self.STAR.setup = unittest.mock.AsyncMock() self.STAR._core_parked = True self.STAR._iswap_parked = True @@ -1093,6 +1095,7 @@ async def asyncSetUp(self): self.STAR._num_channels = 8 self.STAR.core96_head_installed = True self.STAR.iswap_installed = True + self.STAR.num_arms = 1 self.STAR.setup = unittest.mock.AsyncMock() self.STAR._core_parked = True self.STAR._iswap_parked = True @@ -1222,6 +1225,7 @@ async def asyncSetUp(self): self.star._num_channels = 8 self.star.core96_head_installed = True self.star.iswap_installed = True + self.star.num_arms = 1 self.star.setup = unittest.mock.AsyncMock() self.star._core_parked = True self.star._iswap_parked = True @@ -1418,6 +1422,7 @@ async def asyncSetUp(self): self.backend._num_channels = 8 self.backend.core96_head_installed = True self.backend.iswap_installed = True + self.backend.num_arms = 1 self.backend.setup = unittest.mock.AsyncMock() self.backend._core_parked = True self.backend._iswap_parked = True diff --git a/pylabrobot/liquid_handling/backends/hamilton/vantage_tests.py b/pylabrobot/liquid_handling/backends/hamilton/vantage_tests.py index 2c612016dfa..3ecbd3957cd 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/vantage_tests.py +++ b/pylabrobot/liquid_handling/backends/hamilton/vantage_tests.py @@ -217,6 +217,7 @@ async def setup(self) -> None: # type: ignore self.setup_finished = True self._num_channels = 8 self.iswap_installed = True + self.num_arms = 1 self.core96_head_installed = True async def send_command( diff --git a/pylabrobot/liquid_handling/liquid_handler.py b/pylabrobot/liquid_handling/liquid_handler.py index 027ec486ab5..8cb0ca6a50a 100644 --- a/pylabrobot/liquid_handling/liquid_handler.py +++ b/pylabrobot/liquid_handling/liquid_handler.py @@ -145,7 +145,17 @@ def __init__( self.location = Coordinate.zero() super().assign_child_resource(deck, location=deck.location or Coordinate.zero()) - self._resource_pickup: Optional[ResourcePickup] = None + num_arms = getattr(self.backend, "num_arms", 0) + self._resource_pickups: Dict[int, Optional[ResourcePickup]] = {a: None for a in range(num_arms)} + + @property + def _resource_pickup(self) -> Optional[ResourcePickup]: + """Backward-compatible access to the first arm's pickup state.""" + return self._resource_pickups.get(0) + + @_resource_pickup.setter + def _resource_pickup(self, value: Optional[ResourcePickup]) -> None: + self._resource_pickups[0] = value async def setup(self, **backend_kwargs): """Prepare the robot for use.""" @@ -158,16 +168,51 @@ async def setup(self, **backend_kwargs): await super().setup(**backend_kwargs) self.head = {c: TipTracker(thing=f"Channel {c}") for c in range(self.backend.num_channels)} - self.head96 = {c: TipTracker(thing=f"Channel {c}") for c in range(96)} - self._resource_pickup = None + has_head96 = getattr(self.backend, "core96_head_installed", True) + self.head96 = {c: TipTracker(thing=f"Channel {c}") for c in range(96)} if has_head96 else {} + + self.backend.set_heads(head=self.head, head96=self.head96 or None) + + for tracker in self.head.values(): + tracker.register_callback(self._state_updated) + for tracker in self.head96.values(): + tracker.register_callback(self._state_updated) + + num_arms = getattr(self.backend, "num_arms", 0) + self._resource_pickups = {a: None for a in range(num_arms)} def serialize_state(self) -> Dict[str, Any]: """Serialize the state of this liquid handler. Use :meth:`~Resource.serialize_all_states` to serialize the state of the liquid handler and all children (the deck).""" head_state = {channel: tracker.serialize() for channel, tracker in self.head.items()} - return {"head_state": head_state} + head96_state = {channel: tracker.serialize() for channel, tracker in self.head96.items()} \ + if self.head96 else None + if self._resource_pickups: + arm_state: Optional[Dict[int, Any]] = {} + for arm_id, pickup in self._resource_pickups.items(): + if pickup is not None: + res = pickup.resource + arm_entry: Dict[str, Any] = { + "resource_name": res.name, + "resource_type": type(res).__name__, + "direction": pickup.direction.name, + "pickup_distance_from_top": pickup.pickup_distance_from_top, + "size_x": res.get_size_x(), + "size_y": res.get_size_y(), + "size_z": res.get_size_z(), + } + if hasattr(res, "num_items_x"): + arm_entry["num_items_x"] = res.num_items_x + if hasattr(res, "num_items_y"): + arm_entry["num_items_y"] = res.num_items_y + arm_state[arm_id] = arm_entry + else: + arm_state[arm_id] = None + else: + arm_state = None + return {"head_state": head_state, "head96_state": head96_state, "arm_state": arm_state} def load_state(self, state: Dict[str, Any]): """Load the liquid handler state from a file. Use :meth:`~Resource.load_all_state` to load the @@ -177,6 +222,13 @@ def load_state(self, state: Dict[str, Any]): for channel, tracker_state in head_state.items(): self.head[channel].load_state(tracker_state) + head96_state = state.get("head96_state", {}) + for channel, tracker_state in head96_state.items(): + self.head96[channel].load_state(tracker_state) + + # arm_state is informational only (read via serialize_state); no load needed since + # _resource_pickup is set/cleared by pick_up_resource/drop_resource at runtime. + def update_head_state(self, state: Dict[int, Optional[Tip]]): """Update the state of the liquid handler head. @@ -1964,6 +2016,9 @@ async def pick_up_resource( direction=direction, ) + if not self._resource_pickups: + raise RuntimeError("No robotic arm is installed on this liquid handler.") + if self._resource_pickup is not None: raise RuntimeError(f"Resource {self._resource_pickup.resource.name} already picked up") @@ -1989,6 +2044,8 @@ async def pick_up_resource( self._resource_pickup = None raise e + self._state_updated() + async def move_picked_up_resource( self, to: Coordinate, @@ -2151,6 +2208,7 @@ async def drop_resource( result = await self.backend.drop_resource(drop=drop, **backend_kwargs) self._resource_pickup = None + self._state_updated() # we rotate the resource on top of its original rotation. So in order to set the new rotation, # we have to subtract its current rotation. diff --git a/pylabrobot/liquid_handling/liquid_handler_tests.py b/pylabrobot/liquid_handling/liquid_handler_tests.py index 41431412035..19bcc8f608c 100644 --- a/pylabrobot/liquid_handling/liquid_handler_tests.py +++ b/pylabrobot/liquid_handling/liquid_handler_tests.py @@ -66,6 +66,7 @@ def _create_mock_backend(num_channels: int = 8): """Create a mock LiquidHandlerBackend with the specified number of channels.""" mock = unittest.mock.create_autospec(LiquidHandlerBackend, instance=True) type(mock).num_channels = PropertyMock(return_value=num_channels) + mock.num_arms = 1 mock.can_pick_up_tip.return_value = True return mock diff --git a/pylabrobot/visualizer/img/multi_channel_pipette.png b/pylabrobot/visualizer/img/multi_channel_pipette.png index 80baac8ee1203a452d5e201badd10c27019669d0..c66c881c07b44cf55ed12f451368c7fa83256e86 100644 GIT binary patch literal 38330 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

T$0{wbgLKv3K(=f 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 Lt0(4(OK{qMiUZ#} literal 28110 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 Al>h($ diff --git a/pylabrobot/visualizer/index.html b/pylabrobot/visualizer/index.html index 0668f32c67b..ec021b79832 100644 --- a/pylabrobot/visualizer/index.html +++ b/pylabrobot/visualizer/index.html @@ -30,10 +30,11 @@

Logo - PyLabRobot Visualizer - {{ source_filename }} + {{ source_filename }} +
diff --git a/pylabrobot/visualizer/lib.js b/pylabrobot/visualizer/lib.js index e7c69a76a33..840af909e5a 100644 --- a/pylabrobot/visualizer/lib.js +++ b/pylabrobot/visualizer/lib.js @@ -153,6 +153,11 @@ function getLocationWrt(resource, wrtName) { var scaleX, scaleY; var resources = {}; // name -> Resource object +// Serialized resource data saved before resources are destroyed (e.g. picked up by arm). +// Used by the arm panel to re-instantiate the resource and draw it on a live Konva stage +// using the exact same draw() code as the main canvas — guaranteeing visual consistency. +// Each entry is a plain JS object from resource.serialize() (~1-5 KB for a 96-well plate). +var resourceSnapshots = {}; // name -> serialized resource data var rootResource = null; // the root resource for fit-to-viewport @@ -941,6 +946,13 @@ class Well extends Container { this.cross_section_type = cross_section_type; } + serialize() { + return { + ...super.serialize(), + cross_section_type: this.cross_section_type, + }; + } + drawMainShape() { const mainShape = new Konva.Group({}); if (this.cross_section_type === "circle") { @@ -1195,19 +1207,19 @@ class PlateHolder extends ResourceHolder {} function fillHeadIcons(panel, headState) { panel.innerHTML = ""; - panel.style.display = "flex"; - panel.style.flexWrap = "wrap"; - panel.style.gap = "6px"; - panel.style.alignItems = "flex-start"; + // Fixed height: pipette (27) + max tip (80mm * 0.8 = 64px) + var maxTipPx = 64; // 80mm max tip + var fixedSvgH = 27 + maxTipPx; var channels = Object.keys(headState).sort(function (a, b) { return +a - +b; }); for (var ci = 0; ci < channels.length; ci++) { var ch = channels[ci]; var tipData = headState[ch] && headState[ch].tip; var hasTip = tipData !== null && tipData !== undefined; - // Scale tip length: total_tip_length in mm, map to px (0.4 px/mm, min 10, max 40) + // Scale tip length: total_tip_length in mm, map to px (0.8 px/mm, clamp 10mm–80mm) var tipLenPx = 0; if (hasTip && tipData.total_tip_length) { - tipLenPx = Math.max(10, Math.min(40, tipData.total_tip_length * 0.4)); + var clampedMm = Math.max(10, Math.min(80, tipData.total_tip_length)); + tipLenPx = clampedMm * 0.8; } var col = document.createElement("div"); col.style.display = "flex"; @@ -1220,12 +1232,10 @@ function fillHeadIcons(panel, headState) { label.style.color = "#888"; label.style.marginBottom = "2px"; col.appendChild(label); - // Base pipette is 27px; tip adds a straight section + taper below - var svgH = hasTip ? 27 + tipLenPx : 27; var icon = document.createElementNS("http://www.w3.org/2000/svg", "svg"); icon.setAttribute("width", "14"); - icon.setAttribute("height", String(svgH)); - icon.setAttribute("viewBox", "0 0 14 " + svgH); + icon.setAttribute("height", String(fixedSvgH)); + icon.setAttribute("viewBox", "0 0 14 " + fixedSvgH); // Black cylinder (top): 14px wide, 20px tall with rounded ends var shapes = '' + @@ -1242,8 +1252,8 @@ function fillHeadIcons(panel, headState) { var straightEnd = 25 + straightH; var tipEnd = straightEnd + taperH; shapes += - '' + - ''; + '' + + ''; } icon.innerHTML = shapes; col.appendChild(icon); @@ -1251,11 +1261,306 @@ function fillHeadIcons(panel, headState) { } } +function fillHead96Grid(panel, head96State) { + panel.innerHTML = ""; + // Split channels into groups of 96 (one grid per multi-channel pipette) + var allChannels = Object.keys(head96State).sort(function (a, b) { return +a - +b; }); + var numPipettes = Math.max(1, Math.ceil(allChannels.length / 96)); + // Compute dot size to fit within panel height + var panelH = parseFloat(panel.style.height) || 0; + var availH = panelH > 0 ? panelH - 32 - 12 - 3 : 100; + // 8 rows with gaps: 8d + 7*0.25d = 9.75d = availH + var dotSize = Math.max(6, Math.min(14, Math.floor(availH / 9.75))); + var gapSize = Math.max(1, Math.round(dotSize * 0.25)); + for (var p = 0; p < numPipettes; p++) { + var startCh = p * 96; + var box = document.createElement("div"); + box.style.border = "1.5px solid #aaa"; + box.style.borderRadius = "6px"; + box.style.padding = "6px"; + box.style.background = "rgba(245, 245, 245, 0.6)"; + box.style.display = "inline-block"; + var grid = document.createElement("div"); + grid.style.display = "grid"; + grid.style.gridTemplateColumns = "repeat(12, " + dotSize + "px)"; + grid.style.gridTemplateRows = "repeat(8, " + dotSize + "px)"; + grid.style.gap = gapSize + "px"; + // 8 rows x 12 cols, column-major within each 96-group + for (var row = 0; row < 8; row++) { + for (var col = 0; col < 12; col++) { + var ch = startCh + col * 8 + row; + var chState = head96State[String(ch)] || head96State[ch]; + var hasTip = chState && chState.tip !== null && chState.tip !== undefined; + var dot = document.createElement("div"); + dot.style.width = dotSize + "px"; + dot.style.height = dotSize + "px"; + dot.style.borderRadius = "50%"; + dot.style.border = "1.5px solid " + (hasTip ? "#555" : "#bbb"); + dot.style.background = hasTip ? "#666" : "#e8e8e8"; + dot.title = "Channel " + ch + (hasTip ? " (tip)" : ""); + grid.appendChild(dot); + } + } + // Pipette index label (only when multiple 96-head pipettes) + if (numPipettes > 1) { + var wrapper = document.createElement("div"); + wrapper.style.display = "flex"; + wrapper.style.flexDirection = "column"; + wrapper.style.alignItems = "center"; + var idLabel = document.createElement("span"); + idLabel.textContent = String(p); + idLabel.style.fontSize = "15px"; + idLabel.style.fontWeight = "700"; + idLabel.style.color = "#888"; + idLabel.style.marginBottom = "2px"; + wrapper.appendChild(idLabel); + box.appendChild(grid); + wrapper.appendChild(box); + panel.appendChild(wrapper); + } else { + box.appendChild(grid); + panel.appendChild(box); + } + } +} + +function buildSingleArm(armData) { + // Build one gripper visualization column + var hasPlate = armData !== null && armData !== undefined; + var col = document.createElement("div"); + col.style.display = "flex"; + col.style.flexDirection = "column"; + col.style.alignItems = "center"; + col.style.justifyContent = "center"; + + // Compute scaled plate dimensions for gripper sizing. + // The serialized resource data (saved before destruction in resourceSnapshots) is used + // to re-instantiate the resource and draw it on a live Konva stage inside the arm panel, + // using the exact same draw() code as the main canvas. + var plateW = 52, plateH = 22; + var snapshot = null; // serialized resource data, or null + if (hasPlate) { + snapshot = resourceSnapshots[armData.resource_name] || null; + var sizeX = snapshot ? snapshot.size_x : (armData.size_x || 127); + var sizeY = snapshot ? snapshot.size_y : (armData.size_y || 86); + var scale = Math.min(80 / sizeX, 80 / sizeY); + plateW = Math.round(sizeX * scale); + plateH = Math.round(sizeY * scale); + } + + // Carriage uses a fixed "closed" gap regardless of plate presence. + // Fingers spread outward to accommodate a held plate. + // SVG is always wide enough for a standard plate (127×86 mm) so the popup + // does not resize when a plate is picked up or dropped. + var stdPlateW = Math.round(127 * Math.min(80 / 127, 80 / 86)); // ≈80px + var minFingerGap = Math.round((stdPlateW + 16) * 1.1); // ≈106px + var closedGap = Math.round((52 + 16) * 1.1); // default closed spacing + var fingerGap = hasPlate ? Math.round((plateW + 16) * 1.1) : closedGap; + var svgW = Math.max(closedGap, fingerGap, minFingerGap) + 28; // 14px margin each side (room for outer guide bars) + var svgH = 110; + var cx = svgW / 2; // centre x + + // Finger (rail) positions spread based on plate size + var lRailX = cx - fingerGap / 2 - 7; // left rail, offset outward from center + var rRailX = cx + fingerGap / 2 - 1; // right rail, offset outward from center + + var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + svg.setAttribute("width", String(svgW)); + svg.setAttribute("height", String(svgH)); + svg.setAttribute("viewBox", "0 0 " + svgW + " " + svgH); + var shapes = ""; + + // Horizontal guide bars — drawn first (behind rails), extending inward from each finger + var barH = 5.1, barW = 12, barY = 10; // aligned with top of rails + shapes += ''; + shapes += ''; + + // Draw rails after guide bars (painter's order) + // Left rail (7px wide, 90% of original 8px) + shapes += ''; + // Right rail + shapes += ''; + + // Top carriage block (grey, wide) — fixed size, drawn after rails so it covers them + var carriageW = closedGap + 8; + var carriageX = cx - carriageW / 2; + shapes += ''; + // Darker top strip on carriage + shapes += ''; + // Centre mounting post + shapes += ''; + + // Cushion geometry: pad y=74, height=22 → center at y=85 + var cushY = 74, cushH = 22, pinH = 2.4; + var cushCenterY = cushY + cushH / 2; // 85 + var pinOffset = 5; // distance from center to pin center + var pinTopY = cushCenterY - pinOffset - pinH / 2; // 78.8 + var pinBotY = cushCenterY + pinOffset - pinH / 2; // 88.8 + + // Left finger cushion — pins drawn first (behind), then vertical pad on top + var lCushX = lRailX + 7 + 2; // rail width + gap + shapes += ''; + shapes += ''; + shapes += ''; + + // Right finger cushion — pins drawn first (behind), then vertical pad on top + var rCushX = rRailX + 1 - 2 - 4; // rail left edge - gap - cushion width + shapes += ''; + shapes += ''; + shapes += ''; + + svg.innerHTML = shapes; + // Wrap the SVG and plate in a positioned container. + var svgContainer = document.createElement("div"); + svgContainer.style.position = "relative"; + svgContainer.style.width = svgW + "px"; + svgContainer.style.height = svgH + "px"; + svgContainer.appendChild(svg); + + if (hasPlate && snapshot) { + // Render the plate using the exact same Konva draw() code as the main canvas. + // The serialized resource data (saved before destruction) is re-instantiated via + // loadResource() and drawn on a temporary DOM-attached Konva stage. The result is + // exported as a PNG data URL and displayed as an overlay on the gripper SVG. + // Konva requires its container to be in the DOM to render, so we use a hidden div + // attached to document.body, then clean up after export. + // Cost: one temporary Konva stage + ~97 nodes for a 96-well plate, created and + // destroyed each time the arm panel updates. + var plateX = cx - plateW / 2; + var plateY = 85 - plateH / 2; + try { + var realW = Math.ceil(snapshot.size_x); + var realH = Math.ceil(snapshot.size_y); + // Create a hidden div in the DOM for Konva to render into + var tmpDiv = document.createElement("div"); + tmpDiv.style.position = "fixed"; + tmpDiv.style.left = "-9999px"; + tmpDiv.style.top = "-9999px"; + document.body.appendChild(tmpDiv); + var plateStage = new Konva.Stage({ container: tmpDiv, width: realW, height: realH }); + var plateLayer = new Konva.Layer(); + plateStage.add(plateLayer); + // Re-instantiate the resource from saved serialized data and draw it. + // Temporarily save/restore global resources to avoid conflicts. + var savedRes = {}; + var snapshotData = JSON.parse(JSON.stringify(snapshot)); + snapshotData.parent_name = undefined; + snapshotData.location = { x: 0, y: 0, z: 0, type: "Coordinate" }; + function _saveKey(n) { if (n in resources) savedRes[n] = resources[n]; } + _saveKey(snapshotData.name); + for (var si = 0; si < (snapshotData.children || []).length; si++) { + _saveKey(snapshotData.children[si].name); + } + var plateCopy = loadResource(snapshotData); + plateCopy.draw(plateLayer); + plateLayer.draw(); + // Export to data URL + var plateDataUrl = plateStage.toDataURL({ pixelRatio: 2 }); + // Clean up: restore resources, destroy offscreen stage + delete resources[plateCopy.name]; + for (var ci2 = 0; ci2 < plateCopy.children.length; ci2++) { + delete resources[plateCopy.children[ci2].name]; + } + for (var rk in savedRes) { resources[rk] = savedRes[rk]; } + plateStage.destroy(); + document.body.removeChild(tmpDiv); + // Display as an overlay + var plateImg = document.createElement("img"); + plateImg.src = plateDataUrl; + plateImg.style.position = "absolute"; + plateImg.style.left = plateX + "px"; + plateImg.style.top = plateY + "px"; + plateImg.style.width = plateW + "px"; + plateImg.style.height = plateH + "px"; + plateImg.style.pointerEvents = "none"; + svgContainer.appendChild(plateImg); + } catch (e) { + console.warn("[arm plate render] failed:", e); + } + } else if (hasPlate) { + // Fallback: simple colored rectangle when no serialized data is available + var plateX2 = cx - plateW / 2; + var plateY2 = 85 - plateH / 2; + var fallbackColor = RESOURCE_COLORS[armData.resource_type] || RESOURCE_COLORS["Resource"]; + var fallbackSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + fallbackSvg.setAttribute("width", String(svgW)); + fallbackSvg.setAttribute("height", String(svgH)); + fallbackSvg.setAttribute("viewBox", "0 0 " + svgW + " " + svgH); + fallbackSvg.style.position = "absolute"; + fallbackSvg.style.left = "0"; + fallbackSvg.style.top = "0"; + fallbackSvg.style.pointerEvents = "none"; + fallbackSvg.innerHTML = ''; + svgContainer.appendChild(fallbackSvg); + } + + col.appendChild(svgContainer); + var label = document.createElement("div"); + label.style.fontSize = "11px"; + label.style.fontWeight = "600"; + label.style.color = "#666"; + label.style.marginTop = "4px"; + label.style.textAlign = "center"; + if (hasPlate) { + label.textContent = armData.resource_name + " (" + armData.resource_type + ")"; + } else { + label.textContent = "No plate held"; + } + col.appendChild(label); + return col; +} + +function fillArmPanel(panel, armState) { + panel.innerHTML = ""; + if (!armState || Object.keys(armState).length === 0) { + // Match the dimensions of a normal arm panel (closed gripper SVG + label) + var stdW = Math.round((Math.round(127 * Math.min(80 / 127, 80 / 86)) + 16) * 1.1) + 28; + var msg = document.createElement("div"); + msg.style.display = "flex"; + msg.style.alignItems = "center"; + msg.style.justifyContent = "center"; + msg.style.width = stdW + "px"; + msg.style.height = "130px"; + msg.style.padding = "16px"; + msg.style.boxSizing = "border-box"; + msg.style.color = "#888"; + msg.style.fontSize = "13px"; + msg.style.fontWeight = "500"; + msg.style.textAlign = "center"; + msg.textContent = "No robotic arm is installed on this liquid handler."; + panel.appendChild(msg); + return; + } + var arms = Object.keys(armState).sort(function (a, b) { return +a - +b; }); + for (var i = 0; i < arms.length; i++) { + var armId = arms[i]; + var wrapper = document.createElement("div"); + wrapper.style.display = "flex"; + wrapper.style.flexDirection = "column"; + wrapper.style.alignItems = "center"; + // Arm index label (only when multiple arms) + if (arms.length > 1) { + var idLabel = document.createElement("span"); + idLabel.textContent = armId; + idLabel.style.fontSize = "15px"; + idLabel.style.fontWeight = "700"; + idLabel.style.color = "#888"; + idLabel.style.marginBottom = "2px"; + wrapper.appendChild(idLabel); + } + wrapper.appendChild(buildSingleArm(armState[armId])); + panel.appendChild(wrapper); + } +} + class LiquidHandler extends Resource { constructor(resource) { super(resource); this.numHeads = 0; this.headState = {}; + this.head96State = {}; + this.armState = {}; } drawMainShape() { @@ -1266,12 +1571,41 @@ class LiquidHandler extends Resource { if (state.head_state) { this.headState = state.head_state; this.numHeads = Object.keys(state.head_state).length; - // Update dropdown panel if it exists var panel = document.getElementById("single-channel-dropdown-" + this.name); if (panel) { fillHeadIcons(panel, this.headState); } } + if (state.head96_state) { + this.head96State = state.head96_state; + var panel96 = document.getElementById("multi-channel-dropdown-" + this.name); + if (panel96) { + fillHead96Grid(panel96, this.head96State); + } + } + if ("arm_state" in state) { + this.armState = state.arm_state; + // Snapshot each held resource NOW, while it is still in the resources dict. + // pick_up_resource() sends set_state BEFORE resource_unassigned fires, so + // the resource and all its children are still intact at this point. + for (var armKey in (this.armState || {})) { + var ad = this.armState[armKey]; + if (ad && ad.resource_name && !resourceSnapshots[ad.resource_name]) { + var res = resources[ad.resource_name]; + if (res) { + try { + resourceSnapshots[ad.resource_name] = res.serialize(); + } catch (e) { + console.warn("[arm snapshot] failed for " + ad.resource_name, e); + } + } + } + } + var armPanel = document.getElementById("arm-dropdown-" + this.name); + if (armPanel) { + fillArmPanel(armPanel, this.armState); + } + } } } @@ -3183,12 +3517,29 @@ function buildNavbarLHModules() { var group = document.createElement("div"); group.className = "navbar-pipette-group"; - // Label - var label = document.createElement("span"); + // Label (styled as button without changing appearance) + var label = document.createElement("button"); label.className = "navbar-pipette-label"; - label.textContent = lhName; + label.innerHTML = lhName + "
Modules"; group.appendChild(label); + // Collapsible container for module buttons + var moduleBtns = document.createElement("div"); + moduleBtns.className = "navbar-module-btns"; + group.appendChild(moduleBtns); + + // Toggle module buttons on label click + label.addEventListener("click", function () { + var collapsed = moduleBtns.classList.toggle("collapsed"); + label.classList.toggle("collapsed", collapsed); + // Close any open dropdowns when collapsing + if (collapsed) { + var dropdowns = document.querySelectorAll(".module-dropdown.open"); + dropdowns.forEach(function (d) { d.classList.remove("open"); }); + group.querySelectorAll(".navbar-pipette-btn.active").forEach(function (b) { b.classList.remove("active"); }); + } + }); + // Multi-channel button var multiBtn = document.createElement("button"); multiBtn.className = "navbar-pipette-btn"; @@ -3200,7 +3551,7 @@ function buildNavbarLHModules() { multiImg.style.height = "44px"; multiImg.style.objectFit = "contain"; multiBtn.appendChild(multiImg); - group.appendChild(multiBtn); + moduleBtns.appendChild(multiBtn); // Single-channel button var singleBtn = document.createElement("button"); @@ -3213,7 +3564,51 @@ function buildNavbarLHModules() { singleImg.style.height = "44px"; singleImg.style.objectFit = "contain"; singleBtn.appendChild(singleImg); - group.appendChild(singleBtn); + moduleBtns.appendChild(singleBtn); + + // Helper: position both panels based on the single-channel button position + function positionPanels(handlerName, singleBtnRef) { + var mainEl = document.querySelector("main"); + if (!mainEl) return; + var mainRect = mainEl.getBoundingClientRect(); + var btnRect = singleBtnRef.getBoundingClientRect(); + var topPx = (btnRect.bottom - mainRect.top + 20); + var singleCenterPx = (btnRect.left - mainRect.left + btnRect.width / 2); + + var singlePanel = document.getElementById("single-channel-dropdown-" + handlerName); + if (singlePanel) { + singlePanel.style.top = topPx + "px"; + singlePanel.style.left = singleCenterPx + "px"; + } + + // Measure single panel (temporarily show if hidden) + var singleW = 0, singleH = 0, singleLeft = singleCenterPx; + if (singlePanel) { + var wasHidden = !singlePanel.classList.contains("open"); + if (wasHidden) { singlePanel.style.visibility = "hidden"; singlePanel.classList.add("open"); } + singleW = singlePanel.offsetWidth; + singleH = singlePanel.offsetHeight; + singleLeft = singleCenterPx - singleW / 2; + if (wasHidden) { singlePanel.classList.remove("open"); singlePanel.style.visibility = ""; } + } + + var multiPanel = document.getElementById("multi-channel-dropdown-" + handlerName); + if (multiPanel && multiPanel.classList.contains("open")) { + multiPanel.style.transform = "none"; + multiPanel.style.top = topPx + "px"; + multiPanel.style.height = singleH > 0 ? singleH + "px" : "auto"; + multiPanel.style.left = (singleLeft - multiPanel.offsetWidth - 8) + "px"; + } + + var armPanel = document.getElementById("arm-dropdown-" + handlerName); + if (armPanel && armPanel.classList.contains("open")) { + armPanel.style.transform = "none"; + armPanel.style.top = topPx + "px"; + armPanel.style.height = singleH > 0 ? singleH + "px" : "auto"; + var singleRight = singleLeft + singleW; + armPanel.style.left = (singleRight + 8) + "px"; + } + } // Single-channel dropdown panel (function (btn, handlerName) { @@ -3221,31 +3616,50 @@ function buildNavbarLHModules() { btn.addEventListener("click", function () { var existing = document.getElementById(panelId); if (existing) { - // Toggle var isOpen = existing.classList.toggle("open"); btn.classList.toggle("active", isOpen); + positionPanels(handlerName, btn); return; } - // Create the dropdown panel inside
var mainEl = document.querySelector("main"); if (!mainEl) return; var panel = document.createElement("div"); panel.className = "module-dropdown open"; panel.id = panelId; - // Position below the button - var btnRect = btn.getBoundingClientRect(); - var mainRect = mainEl.getBoundingClientRect(); - panel.style.top = (btnRect.bottom - mainRect.top + 20) + "px"; - panel.style.left = (btnRect.left - mainRect.left + btnRect.width / 2) + "px"; - // Show head icons from LiquidHandler state var lhResource = resources[handlerName]; var headState = (lhResource && lhResource.headState) ? lhResource.headState : {}; fillHeadIcons(panel, headState); mainEl.appendChild(panel); btn.classList.add("active"); + positionPanels(handlerName, btn); }); })(singleBtn, lhName); + // Multi-channel dropdown panel + (function (btn, handlerName, singleBtnRef) { + var panelId = "multi-channel-dropdown-" + handlerName; + btn.addEventListener("click", function () { + var existing = document.getElementById(panelId); + if (existing) { + var isOpen = existing.classList.toggle("open"); + btn.classList.toggle("active", isOpen); + if (isOpen) positionPanels(handlerName, singleBtnRef); + return; + } + var mainEl = document.querySelector("main"); + if (!mainEl) return; + var panel = document.createElement("div"); + panel.className = "module-dropdown multi-channel open"; + panel.id = panelId; + var lhResource = resources[handlerName]; + var head96State = (lhResource && lhResource.head96State) ? lhResource.head96State : {}; + fillHead96Grid(panel, head96State); + mainEl.appendChild(panel); + positionPanels(handlerName, singleBtnRef); + btn.classList.add("active"); + }); + })(multiBtn, lhName, singleBtn); + // Integrated arm button var armBtn = document.createElement("button"); armBtn.className = "navbar-pipette-btn"; @@ -3257,7 +3671,32 @@ function buildNavbarLHModules() { armImg.style.height = "44px"; armImg.style.objectFit = "contain"; armBtn.appendChild(armImg); - group.appendChild(armBtn); + moduleBtns.appendChild(armBtn); + + // Arm dropdown panel + (function (btn, handlerName, singleBtnRef) { + var panelId = "arm-dropdown-" + handlerName; + btn.addEventListener("click", function () { + var existing = document.getElementById(panelId); + if (existing) { + var isOpen = existing.classList.toggle("open"); + btn.classList.toggle("active", isOpen); + if (isOpen) positionPanels(handlerName, singleBtnRef); + return; + } + var mainEl = document.querySelector("main"); + if (!mainEl) return; + var panel = document.createElement("div"); + panel.className = "module-dropdown arm open"; + panel.id = panelId; + var lhResource = resources[handlerName]; + var armState = (lhResource && lhResource.armState) ? lhResource.armState : {}; + fillArmPanel(panel, armState); + mainEl.appendChild(panel); + positionPanels(handlerName, singleBtnRef); + btn.classList.add("active"); + }); + })(armBtn, lhName, singleBtn); container.appendChild(group); } diff --git a/pylabrobot/visualizer/main.css b/pylabrobot/visualizer/main.css index e0b5428917e..480ffa33761 100644 --- a/pylabrobot/visualizer/main.css +++ b/pylabrobot/visualizer/main.css @@ -42,9 +42,6 @@ nav.navbar .status-box { } #navbar-lh-modules { - position: absolute; - left: 50%; - transform: translateX(-50%); display: flex; align-items: center; gap: 8px; @@ -66,9 +63,18 @@ nav.navbar .status-box { color: #555; white-space: nowrap; border-right: 1px solid #ced4da; + border-top: none; + border-bottom: none; + border-left: none; height: 48px; display: flex; align-items: center; + justify-content: center; + text-align: center; + line-height: 1.3; + background: transparent; + cursor: pointer; + font-family: inherit; } .navbar-pipette-btn { @@ -94,6 +100,24 @@ nav.navbar .status-box { border-left: 1px solid #ced4da; } +.navbar-module-btns { + display: flex; + align-items: center; + overflow: hidden; + transition: max-width 0.25s ease, opacity 0.2s ease; + max-width: 500px; + opacity: 1; +} + +.navbar-module-btns.collapsed { + max-width: 0; + opacity: 0; +} + +.navbar-pipette-label.collapsed { + border-right: none; +} + .navbar-pipette-btn.active { background: #cfe2ff; opacity: 1; @@ -118,6 +142,22 @@ nav.navbar .status-box { .module-dropdown.open { display: flex; + flex-wrap: wrap; + gap: 6px; + align-items: flex-start; + overflow: hidden; + box-sizing: border-box; +} + +.module-dropdown.multi-channel.open { + gap: 10px; + align-items: center; + justify-content: center; +} + +.module-dropdown.arm.open { + align-items: center; + justify-content: center; } .content { diff --git a/pylabrobot/visualizer/vis.js b/pylabrobot/visualizer/vis.js index fcaf0d97a8c..e5b98a59d6a 100644 --- a/pylabrobot/visualizer/vis.js +++ b/pylabrobot/visualizer/vis.js @@ -38,6 +38,21 @@ function setRootResource(data) { buildResourceTree(resource); } +// Save the full serialized resource data before it is destroyed. +// Called from the resource_unassigned handler while the resource and all its +// children are still intact. The serialized data is later used by buildSingleArm +// to create a live Konva stage using the exact same draw() code as the main canvas. +// Cost: one serialize() call per unassigned resource — negligible. +function snapshotResource(resourceName) { + var res = resources[resourceName]; + if (!res) return; + try { + resourceSnapshots[resourceName] = res.serialize(); + } catch (e) { + console.warn("[snapshot] failed for " + resourceName, e); + } +} + function removeResource(resourceName) { let resource = resources[resourceName]; resource.destroy(); @@ -73,6 +88,10 @@ async function processCentralEvent(event, data) { break; case "resource_unassigned": + // Snapshot the resource before destruction so the arm panel can show a + // pixel-perfect replica. Done here (not in destroy()) because the Konva + // group and all children are guaranteed intact at this point. + snapshotResource(data.resource_name); removeResourceFromTree(data.resource_name); removeResource(data.resource_name); break; From 1d3bf89be4327aec19f77c36d573c93bfc636d69 Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Mon, 2 Feb 2026 20:49:31 +0000 Subject: [PATCH 06/20] `make format` --- .../liquid_handling/backends/hamilton/STAR_chatterbox.py | 2 +- pylabrobot/liquid_handling/liquid_handler.py | 7 +++++-- pylabrobot/visualizer/visualizer.py | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR_chatterbox.py b/pylabrobot/liquid_handling/backends/hamilton/STAR_chatterbox.py index 54bf816d19f..b3f8443c213 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR_chatterbox.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR_chatterbox.py @@ -144,7 +144,7 @@ async def request_extended_configuration(self): # Bit 2: 96-head (based on __init__ parameter) xl_value = 0 if self._iswap_installed: - xl_value |= 0b10 # Add iSWAP (bit 1) + xl_value |= 0b10 # Add iSWAP (bit 1) if self._core96_head_installed: xl_value |= 0b100 # Add 96-head (bit 2) diff --git a/pylabrobot/liquid_handling/liquid_handler.py b/pylabrobot/liquid_handling/liquid_handler.py index 8cb0ca6a50a..6deef6be4b0 100644 --- a/pylabrobot/liquid_handling/liquid_handler.py +++ b/pylabrobot/liquid_handling/liquid_handler.py @@ -187,8 +187,11 @@ def serialize_state(self) -> Dict[str, Any]: serialize the state of the liquid handler and all children (the deck).""" head_state = {channel: tracker.serialize() for channel, tracker in self.head.items()} - head96_state = {channel: tracker.serialize() for channel, tracker in self.head96.items()} \ - if self.head96 else None + head96_state = ( + {channel: tracker.serialize() for channel, tracker in self.head96.items()} + if self.head96 + else None + ) if self._resource_pickups: arm_state: Optional[Dict[int, Any]] = {} for arm_id, pickup in self._resource_pickups.items(): diff --git a/pylabrobot/visualizer/visualizer.py b/pylabrobot/visualizer/visualizer.py index 06aa72bdfbf..9a4e1952fb9 100644 --- a/pylabrobot/visualizer/visualizer.py +++ b/pylabrobot/visualizer/visualizer.py @@ -309,11 +309,12 @@ def _detect_source_filename() -> str: # 3. Query the Jupyter REST API using the kernel connection file. try: - import ipykernel # type: ignore[import-untyped] import json as _json import re import urllib.request + import ipykernel # type: ignore[import-untyped] + # Get the kernel id from the connection file path. connection_file = ipykernel.get_connection_file() kernel_id = os.path.basename(connection_file).replace("kernel-", "").replace(".json", "") From 4ee8d1536ed0a445202b3153dca0407d53104b1e Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Mon, 2 Feb 2026 21:14:30 +0000 Subject: [PATCH 07/20] selective appearance of lh modules in navbar --- pylabrobot/visualizer/lib.js | 71 +++++++++++++++++++---- pylabrobot/visualizer/vis.js | 4 ++ pylabrobot/visualizer/visualizer.py | 7 +++ pylabrobot/visualizer/visualizer_tests.py | 1 + 4 files changed, 71 insertions(+), 12 deletions(-) diff --git a/pylabrobot/visualizer/lib.js b/pylabrobot/visualizer/lib.js index 840af909e5a..f178462fea2 100644 --- a/pylabrobot/visualizer/lib.js +++ b/pylabrobot/visualizer/lib.js @@ -1263,6 +1263,22 @@ function fillHeadIcons(panel, headState) { function fillHead96Grid(panel, head96State) { panel.innerHTML = ""; + if (!head96State || Object.keys(head96State).length === 0) { + // Set panel dimensions to match a normal 96-head grid, then center message + panel.style.minWidth = "180px"; + panel.style.minHeight = "130px"; + panel.style.alignItems = "center"; + panel.style.justifyContent = "center"; + var msg = document.createElement("span"); + msg.style.color = "#888"; + msg.style.fontSize = "13px"; + msg.style.fontWeight = "500"; + msg.style.textAlign = "center"; + msg.style.padding = "16px"; + msg.textContent = "No multi-channel pipette is installed on this liquid handler."; + panel.appendChild(msg); + return; + } // Split channels into groups of 96 (one grid per multi-channel pipette) var allChannels = Object.keys(head96State).sort(function (a, b) { return +a - +b; }); var numPipettes = Math.max(1, Math.ceil(allChannels.length / 96)); @@ -1514,20 +1530,18 @@ function buildSingleArm(armData) { function fillArmPanel(panel, armState) { panel.innerHTML = ""; if (!armState || Object.keys(armState).length === 0) { - // Match the dimensions of a normal arm panel (closed gripper SVG + label) + // Set panel dimensions to match a normal arm panel, then center message var stdW = Math.round((Math.round(127 * Math.min(80 / 127, 80 / 86)) + 16) * 1.1) + 28; - var msg = document.createElement("div"); - msg.style.display = "flex"; - msg.style.alignItems = "center"; - msg.style.justifyContent = "center"; - msg.style.width = stdW + "px"; - msg.style.height = "130px"; - msg.style.padding = "16px"; - msg.style.boxSizing = "border-box"; + panel.style.minWidth = stdW + "px"; + panel.style.minHeight = "130px"; + panel.style.alignItems = "center"; + panel.style.justifyContent = "center"; + var msg = document.createElement("span"); msg.style.color = "#888"; msg.style.fontSize = "13px"; msg.style.fontWeight = "500"; msg.style.textAlign = "center"; + msg.style.padding = "16px"; msg.textContent = "No robotic arm is installed on this liquid handler."; panel.appendChild(msg); return; @@ -1576,8 +1590,11 @@ class LiquidHandler extends Resource { fillHeadIcons(panel, this.headState); } } - if (state.head96_state) { + if ("head96_state" in state) { this.head96State = state.head96_state; + // Show/hide multi-channel button based on whether the module exists + var multiBtn = document.getElementById("multi-channel-btn-" + this.name); + if (multiBtn) multiBtn.style.display = (this.head96State !== null && this.head96State !== undefined) ? "" : "none"; var panel96 = document.getElementById("multi-channel-dropdown-" + this.name); if (panel96) { fillHead96Grid(panel96, this.head96State); @@ -1585,6 +1602,9 @@ class LiquidHandler extends Resource { } if ("arm_state" in state) { this.armState = state.arm_state; + // Show/hide arm button based on whether the module exists + var armBtnEl = document.getElementById("arm-btn-" + this.name); + if (armBtnEl) armBtnEl.style.display = (this.armState !== null && this.armState !== undefined) ? "" : "none"; // Snapshot each held resource NOW, while it is still in the resources dict. // pick_up_resource() sends set_state BEFORE resource_unassigned fires, so // the resource and all its children are still intact at this point. @@ -3540,9 +3560,12 @@ function buildNavbarLHModules() { } }); - // Multi-channel button + // Multi-channel button (hidden unless setState has already confirmed module exists) + var lhRes = resources[lhName]; var multiBtn = document.createElement("button"); multiBtn.className = "navbar-pipette-btn"; + multiBtn.id = "multi-channel-btn-" + lhName; + multiBtn.style.display = (lhRes && lhRes.head96State !== null && lhRes.head96State !== undefined) ? "" : "none"; multiBtn.title = "Multi-Channel Pipettes"; var multiImg = document.createElement("img"); multiImg.src = "img/multi_channel_pipette.png"; @@ -3556,6 +3579,7 @@ function buildNavbarLHModules() { // Single-channel button var singleBtn = document.createElement("button"); singleBtn.className = "navbar-pipette-btn"; + singleBtn.id = "single-channel-btn-" + lhName; singleBtn.title = "Single-Channel Pipettes"; var singleImg = document.createElement("img"); singleImg.src = "img/single_channel_pipette.png"; @@ -3660,9 +3684,11 @@ function buildNavbarLHModules() { }); })(multiBtn, lhName, singleBtn); - // Integrated arm button + // Integrated arm button (hidden unless setState has already confirmed module exists) var armBtn = document.createElement("button"); armBtn.className = "navbar-pipette-btn"; + armBtn.id = "arm-btn-" + lhName; + armBtn.style.display = (lhRes && lhRes.armState !== null && lhRes.armState !== undefined) ? "" : "none"; armBtn.title = "Integrated Arm"; var armImg = document.createElement("img"); armImg.src = "img/integrated_arm.png"; @@ -3701,3 +3727,24 @@ function buildNavbarLHModules() { container.appendChild(group); } } + +/** + * Programmatically open all visible module panels (single-channel, multi-channel, arm) + * for every LiquidHandler. Buttons that are hidden (module absent) are skipped. + */ +function openAllModulePanels() { + for (var name in resources) { + if (!(resources[name] instanceof LiquidHandler)) continue; + var btns = [ + document.getElementById("single-channel-btn-" + name), + document.getElementById("multi-channel-btn-" + name), + document.getElementById("arm-btn-" + name), + ]; + for (var i = 0; i < btns.length; i++) { + var btn = btns[i]; + if (btn && btn.style.display !== "none") { + btn.click(); + } + } + } +} diff --git a/pylabrobot/visualizer/vis.js b/pylabrobot/visualizer/vis.js index e5b98a59d6a..5fbd72abea2 100644 --- a/pylabrobot/visualizer/vis.js +++ b/pylabrobot/visualizer/vis.js @@ -106,6 +106,10 @@ async function processCentralEvent(event, data) { if (rootName) buildResourceTree(resources[rootName]); break; + case "show_modules": + openAllModulePanels(); + break; + default: throw new Error(`Unknown event: ${event}`); } diff --git a/pylabrobot/visualizer/visualizer.py b/pylabrobot/visualizer/visualizer.py index 9a4e1952fb9..85c05ffe7ec 100644 --- a/pylabrobot/visualizer/visualizer.py +++ b/pylabrobot/visualizer/visualizer.py @@ -50,6 +50,7 @@ def __init__( open_browser: bool = True, name: Optional[str] = None, favicon: Optional[str] = None, + show_modules_at_start: bool = True, ): """Create a new Visualizer. Use :meth:`.setup` to start the visualization. @@ -64,9 +65,12 @@ def __init__( calling script or notebook is detected automatically. favicon: Path to a ``.png`` file to use as the browser tab icon. If ``None``, the PyLabRobot logo is used. + show_modules_at_start: If ``True``, module popups (pipettes, arm) are opened automatically + when the visualizer starts. """ self.setup_finished = False + self._show_modules_at_start = show_modules_at_start if name is not None: self._source_filename = name @@ -574,6 +578,9 @@ def save_resource_state(resource: Resource): save_resource_state(self._root_resource) await self.send_command("set_state", state, wait_for_response=False) + if self._show_modules_at_start: + await self.send_command("show_modules", {}, wait_for_response=False) + def _handle_resource_assigned_callback(self, resource: Resource) -> None: """Called when a resource is assigned to a resource already in the tree starting from the root resource. This method will send an event about the new resource""" diff --git a/pylabrobot/visualizer/visualizer_tests.py b/pylabrobot/visualizer/visualizer_tests.py index e97c88f1a7a..92b7d3b8aa1 100644 --- a/pylabrobot/visualizer/visualizer_tests.py +++ b/pylabrobot/visualizer/visualizer_tests.py @@ -86,6 +86,7 @@ async def test_event_sent(self): await self.client.send('{"event": "ready"}') _ = await self.client.recv() # set_root_resource _ = await self.client.recv() # set_state + _ = await self.client.recv() # show_modules await self.vis.send_command("test", wait_for_response=False) recv = await self.client.recv() From ef1c67feec07b04d4e60efc5a4d5b20c342bb8f8 Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Mon, 2 Feb 2026 21:26:37 +0000 Subject: [PATCH 08/20] remove flickering in navbar --- pylabrobot/visualizer/lib.js | 4 ++-- pylabrobot/visualizer/vis.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pylabrobot/visualizer/lib.js b/pylabrobot/visualizer/lib.js index f178462fea2..09c6084cb1c 100644 --- a/pylabrobot/visualizer/lib.js +++ b/pylabrobot/visualizer/lib.js @@ -2343,7 +2343,7 @@ function buildTreeNodeDOM(resource, depth, slotIndex) { return node; } -function buildResourceTree(rootResource) { +function buildResourceTree(rootResource, { rebuildNavbar = true } = {}) { const treeContainer = document.getElementById("resource-tree"); if (!treeContainer) return; treeContainer.innerHTML = ""; @@ -2426,7 +2426,7 @@ function buildResourceTree(rootResource) { treeContainer.appendChild(rootNode); buildWrtDropdown(); - buildNavbarLHModules(); + if (rebuildNavbar) buildNavbarLHModules(); } function addResourceToTree(resource) { diff --git a/pylabrobot/visualizer/vis.js b/pylabrobot/visualizer/vis.js index 5fbd72abea2..82d28aeb10d 100644 --- a/pylabrobot/visualizer/vis.js +++ b/pylabrobot/visualizer/vis.js @@ -103,7 +103,7 @@ async function processCentralEvent(event, data) { const rootName = Object.keys(resources).find( (n) => resources[n] && !resources[n].parent ); - if (rootName) buildResourceTree(resources[rootName]); + if (rootName) buildResourceTree(resources[rootName], { rebuildNavbar: false }); break; case "show_modules": From b18b80ad690c61f732f58c24b0e25f6975b460a0 Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Mon, 2 Feb 2026 21:57:01 +0000 Subject: [PATCH 09/20] update 96-head visual --- pylabrobot/visualizer/lib.js | 57 ++++++++++++++++++++++++----- pylabrobot/visualizer/visualizer.py | 26 +++++++++---- 2 files changed, 67 insertions(+), 16 deletions(-) diff --git a/pylabrobot/visualizer/lib.js b/pylabrobot/visualizer/lib.js index 09c6084cb1c..29fdd659913 100644 --- a/pylabrobot/visualizer/lib.js +++ b/pylabrobot/visualizer/lib.js @@ -1291,11 +1291,13 @@ function fillHead96Grid(panel, head96State) { for (var p = 0; p < numPipettes; p++) { var startCh = p * 96; var box = document.createElement("div"); - box.style.border = "1.5px solid #aaa"; + box.style.border = "1.5px solid #555"; box.style.borderRadius = "6px"; box.style.padding = "6px"; - box.style.background = "rgba(245, 245, 245, 0.6)"; - box.style.display = "inline-block"; + box.style.background = "#444"; + box.style.display = "inline-flex"; + box.style.flexDirection = "column"; + box.style.alignItems = "center"; var grid = document.createElement("div"); grid.style.display = "grid"; grid.style.gridTemplateColumns = "repeat(12, " + dotSize + "px)"; @@ -1311,12 +1313,51 @@ function fillHead96Grid(panel, head96State) { dot.style.width = dotSize + "px"; dot.style.height = dotSize + "px"; dot.style.borderRadius = "50%"; - dot.style.border = "1.5px solid " + (hasTip ? "#555" : "#bbb"); - dot.style.background = hasTip ? "#666" : "#e8e8e8"; + dot.style.border = "1.5px solid " + (hasTip ? "#40CDA1" : "#555"); + dot.style.background = hasTip ? "#40CDA1" : "white"; dot.title = "Channel " + ch + (hasTip ? " (tip)" : ""); grid.appendChild(dot); } } + // Bars outside the grid box, 60% of the corresponding dimension, centered + var gridW = 12 * dotSize + 11 * gapSize; + var gridH = 8 * dotSize + 7 * gapSize; + var hBarW = Math.round(gridW * 0.6); + var vBarH = Math.round(gridH * 0.6); + function makeHBar() { + var bar = document.createElement("div"); + bar.style.width = hBarW + "px"; + bar.style.height = "4px"; + bar.style.background = "#444"; + return bar; + } + function makeVBar() { + var bar = document.createElement("div"); + bar.style.width = "4px"; + bar.style.height = vBarH + "px"; + bar.style.background = "#444"; + return bar; + } + + // Row: left bar + box + right bar + box.appendChild(grid); + var midRow = document.createElement("div"); + midRow.style.display = "flex"; + midRow.style.flexDirection = "row"; + midRow.style.alignItems = "center"; + midRow.appendChild(makeVBar()); + midRow.appendChild(box); + midRow.appendChild(makeVBar()); + + // Column: top bar + midRow + bottom bar + var boxWrap = document.createElement("div"); + boxWrap.style.display = "flex"; + boxWrap.style.flexDirection = "column"; + boxWrap.style.alignItems = "center"; + boxWrap.appendChild(makeHBar()); + boxWrap.appendChild(midRow); + boxWrap.appendChild(makeHBar()); + // Pipette index label (only when multiple 96-head pipettes) if (numPipettes > 1) { var wrapper = document.createElement("div"); @@ -1330,12 +1371,10 @@ function fillHead96Grid(panel, head96State) { idLabel.style.color = "#888"; idLabel.style.marginBottom = "2px"; wrapper.appendChild(idLabel); - box.appendChild(grid); - wrapper.appendChild(box); + wrapper.appendChild(boxWrap); panel.appendChild(wrapper); } else { - box.appendChild(grid); - panel.appendChild(box); + panel.appendChild(boxWrap); } } } diff --git a/pylabrobot/visualizer/visualizer.py b/pylabrobot/visualizer/visualizer.py index 85c05ffe7ec..760cc27212f 100644 --- a/pylabrobot/visualizer/visualizer.py +++ b/pylabrobot/visualizer/visualizer.py @@ -120,6 +120,9 @@ def register_state_update(resource): self._t: Optional[threading.Thread] = None self._stop_: Optional[asyncio.Future] = None + self._pending_state_updates: Dict[str, dict] = {} + self._flush_scheduled = False + self.received: List[dict] = [] @property @@ -615,10 +618,19 @@ def _handle_resource_unassigned_callback(self, resource: Resource) -> None: asyncio.run_coroutine_threadsafe(fut, self.loop) def _handle_state_update_callback(self, resource: Resource) -> None: - """Called when the state of a resource is updated. This method will send an event to the - browser about the updated state.""" - - # Send a `set_state` event to the browser. - data = {resource.name: resource.serialize_state()} - fut = self.send_command(event="set_state", data=data, wait_for_response=False) - asyncio.run_coroutine_threadsafe(fut, self.loop) + """Called when the state of a resource is updated. Updates are batched so that + rapid successive changes (e.g. 96-channel pickup) are sent as a single message.""" + + self._pending_state_updates[resource.name] = resource.serialize_state() + if not self._flush_scheduled: + self._flush_scheduled = True + self.loop.call_soon_threadsafe(self._flush_state_updates) + + def _flush_state_updates(self) -> None: + """Send all pending state updates as a single ``set_state`` event.""" + data = self._pending_state_updates + self._pending_state_updates = {} + self._flush_scheduled = False + if data: + fut = self.send_command(event="set_state", data=data, wait_for_response=False) + asyncio.ensure_future(fut) From 58f33f44649535bdcb846029d06c12cdd8f309c9 Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Mon, 2 Feb 2026 22:28:29 +0000 Subject: [PATCH 10/20] update single-channel popup --- pylabrobot/visualizer/lib.js | 233 +++++++++++++++++++++++++++++++++-- 1 file changed, 224 insertions(+), 9 deletions(-) diff --git a/pylabrobot/visualizer/lib.js b/pylabrobot/visualizer/lib.js index 29fdd659913..0479d4ba574 100644 --- a/pylabrobot/visualizer/lib.js +++ b/pylabrobot/visualizer/lib.js @@ -1205,6 +1205,161 @@ class TubeRack extends Resource { class PlateHolder extends ResourceHolder {} +// Track the currently open pipette info panel so it can be refreshed on state updates. +var _pipetteInfoState = null; // { ch, kind ("channel"|"tip"), anchorDropdown } + +function buildChannelAttrs(ch, headState) { + var chState = headState[ch] || {}; + var tipData = chState.tip; + var hasTip = tipData !== null && tipData !== undefined; + var attrs = [{ key: "channel", value: ch }]; + if (hasTip) { + attrs.push({ key: "has_tip", value: "true" }); + attrs.push({ key: "tip_type", value: tipData.type || "Unknown" }); + attrs.push({ key: "tip_size", value: (tipData.tip_size || "").replace(/_/g, " ") }); + attrs.push({ key: "max_volume", value: (tipData.maximal_volume || "?") + " \u00B5L" }); + attrs.push({ key: "has_filter", value: tipData.has_filter ? "Yes" : "No" }); + attrs.push({ key: "tip_length", value: (tipData.total_tip_length || "?") + " mm" }); + } else { + attrs.push({ key: "has_tip", value: "false" }); + } + return attrs; +} + +function buildTipAttrs(ch, headState) { + var chState = headState[ch] || {}; + var tipData = chState.tip; + var tipStateData = chState.tip_state; + if (!tipData) return null; + var attrs = [ + { key: "name", value: tipData.name || "Unknown" }, + { key: "type", value: tipData.type || "Unknown" }, + { key: "tip_size", value: (tipData.tip_size || "").replace(/_/g, " ") }, + { key: "total_tip_length", value: (tipData.total_tip_length || "?") + " mm" }, + { key: "has_filter", value: tipData.has_filter ? "Yes" : "No" }, + { key: "maximal_volume", value: (tipData.maximal_volume || "?") + " \u00B5L" }, + { key: "pickup_method", value: (tipData.pickup_method || "").replace(/_/g, " ") }, + ]; + if (tipStateData) { + attrs.push({ key: "volume", value: (tipStateData.volume || 0) + " / " + (tipStateData.max_volume || "?") + " \u00B5L" }); + attrs.push({ key: "origin", value: tipStateData.thing || "?" }); + } + return attrs; +} + +// Refresh the pipette info panel if one is open (called after state updates). +function refreshPipetteInfoPanel(headState) { + if (!_pipetteInfoState) return; + var existing = document.getElementById("pipette-info-panel"); + if (!existing) { _pipetteInfoState = null; return; } + var s = _pipetteInfoState; + var title, type, attrs; + if (s.kind === "channel") { + title = "Channel " + s.ch; + type = "PipetteChannel"; + attrs = buildChannelAttrs(s.ch, headState); + } else { + title = "Tip @ Channel " + s.ch; + type = "Tip"; + attrs = buildTipAttrs(s.ch, headState); + if (!attrs) { + // Tip was removed — close the panel + existing.remove(); + _pipetteInfoState = null; + return; + } + } + // No toggle — force show with updated data + existing.remove(); + _showPipetteInfoPanelInner(title, type, attrs, s.anchorDropdown); +} + +// Build a UML-style info panel for a pipette channel or tip, shown on click. +// `title` is the header name, `type` is the guillemet type label, +// `attrs` is an array of {key, value} pairs. +function showPipetteInfoPanel(title, type, attrs, anchorDropdown, ch, kind) { + var existing = document.getElementById("pipette-info-panel"); + if (existing) { + // Toggle off if clicking same thing + if (existing.dataset.key === title) { + existing.remove(); + _pipetteInfoState = null; + return; + } + existing.remove(); + } + _pipetteInfoState = { ch: ch, kind: kind, anchorDropdown: anchorDropdown }; + _showPipetteInfoPanelInner(title, type, attrs, anchorDropdown); +} + +function _showPipetteInfoPanelInner(title, type, attrs, anchorDropdown) { + + var panel = document.createElement("div"); + panel.className = "uml-panel"; + panel.id = "pipette-info-panel"; + panel.dataset.key = title; + + var closeBtn = document.createElement("button"); + closeBtn.className = "uml-close-btn"; + closeBtn.textContent = "\u00D7"; + closeBtn.addEventListener("click", function (e) { + e.stopPropagation(); + panel.remove(); + }); + panel.appendChild(closeBtn); + + var header = document.createElement("div"); + header.className = "uml-header"; + var nameDiv = document.createElement("div"); + nameDiv.className = "uml-header-name"; + nameDiv.textContent = title; + var typeDiv = document.createElement("div"); + typeDiv.className = "uml-header-type"; + typeDiv.textContent = "\u00AB" + type + "\u00BB"; + header.appendChild(nameDiv); + header.appendChild(typeDiv); + panel.appendChild(header); + + var sep = document.createElement("div"); + sep.className = "uml-separator"; + panel.appendChild(sep); + + var section = document.createElement("div"); + section.className = "uml-section"; + var sTitle = document.createElement("div"); + sTitle.className = "uml-section-title"; + sTitle.textContent = "Attributes"; + section.appendChild(sTitle); + for (var i = 0; i < attrs.length; i++) { + var row = document.createElement("div"); + row.className = "uml-row"; + var keySpan = document.createElement("span"); + keySpan.className = "uml-key"; + keySpan.textContent = attrs[i].key + ":"; + var valSpan = document.createElement("span"); + valSpan.className = "uml-value"; + valSpan.textContent = " " + attrs[i].value; + row.appendChild(keySpan); + row.appendChild(valSpan); + section.appendChild(row); + } + panel.appendChild(section); + + var mainEl = document.querySelector("main"); + if (!mainEl) return; + mainEl.appendChild(panel); + + // Position to the left of the single-channel dropdown + if (anchorDropdown) { + var mainRect = mainEl.getBoundingClientRect(); + var ddRect = anchorDropdown.getBoundingClientRect(); + var panelW = panel.offsetWidth; + panel.style.top = (ddRect.top - mainRect.top) + "px"; + panel.style.right = "auto"; + panel.style.left = (ddRect.right - mainRect.left + 8) + "px"; + } +} + function fillHeadIcons(panel, headState) { panel.innerHTML = ""; // Fixed height: pipette (27) + max tip (80mm * 0.8 = 64px) @@ -1225,37 +1380,96 @@ function fillHeadIcons(panel, headState) { col.style.display = "flex"; col.style.flexDirection = "column"; col.style.alignItems = "center"; + col.style.position = "relative"; + // Label + channel cylinder: clickable with hover glow var label = document.createElement("span"); label.textContent = ch; label.style.fontSize = "15px"; label.style.fontWeight = "700"; label.style.color = "#888"; label.style.marginBottom = "2px"; + label.style.cursor = "pointer"; + (function (ch) { + label.addEventListener("click", function (e) { + e.stopPropagation(); + showPipetteInfoPanel("Channel " + ch, "PipetteChannel", buildChannelAttrs(ch, headState), panel, ch, "channel"); + }); + })(ch); col.appendChild(label); var icon = document.createElementNS("http://www.w3.org/2000/svg", "svg"); icon.setAttribute("width", "14"); icon.setAttribute("height", String(fixedSvgH)); icon.setAttribute("viewBox", "0 0 14 " + fixedSvgH); - // Black cylinder (top): 14px wide, 20px tall with rounded ends - var shapes = + icon.style.overflow = "visible"; + icon.style.display = "block"; + // Glow filters for hover + var defs = document.createElementNS("http://www.w3.org/2000/svg", "defs"); + defs.innerHTML = + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + ''; + icon.appendChild(defs); + // Channel shapes (black cylinder + silver cylinder) — hover glow + click + var channelG = document.createElementNS("http://www.w3.org/2000/svg", "g"); + channelG.style.cursor = "pointer"; + (function (idx, ch) { + channelG.addEventListener("mouseenter", function () { this.setAttribute("filter", "url(#chGlow" + idx + ")"); }); + channelG.addEventListener("mouseleave", function () { this.removeAttribute("filter"); }); + channelG.addEventListener("click", function (e) { + e.stopPropagation(); + showPipetteInfoPanel("Channel " + ch, "PipetteChannel", buildChannelAttrs(ch, headState), panel, ch, "channel"); + }); + })(ci, ch); + channelG.innerHTML = '' + '' + '' + - // Silver cylinder (bottom): 10px wide, 5px tall, centered '' + '' + ''; + icon.appendChild(channelG); if (hasTip) { - // Tip: straight section (40% of length) then taper to point (60%) + var collarH = 6.5; + var collarY = 19.5; + var bodyStart = collarY + collarH; var straightH = Math.round(tipLenPx * 0.4); var taperH = tipLenPx - straightH; - var straightEnd = 25 + straightH; + var straightEnd = bodyStart + straightH; var tipEnd = straightEnd + taperH; - shapes += - '' + - ''; + var tipG = document.createElementNS("http://www.w3.org/2000/svg", "g"); + tipG.style.cursor = "pointer"; + (function (idx) { + tipG.addEventListener("mouseenter", function () { this.setAttribute("filter", "url(#tipGlow" + idx + ")"); }); + tipG.addEventListener("mouseleave", function () { this.removeAttribute("filter"); }); + })(ci); + var botW = (tipData.total_tip_length > 50) ? 2 : 1; + var botL = 7 - botW / 2; + var botR = 7 + botW / 2; + tipG.innerHTML = + '' + + '' + + ''; + (function (ch) { + tipG.addEventListener("click", function (e) { + e.stopPropagation(); + showPipetteInfoPanel("Tip @ Channel " + ch, "Tip", buildTipAttrs(ch, headState), panel, ch, "tip"); + }); + })(ch); + icon.appendChild(tipG); } - icon.innerHTML = shapes; col.appendChild(icon); panel.appendChild(col); } @@ -1627,6 +1841,7 @@ class LiquidHandler extends Resource { var panel = document.getElementById("single-channel-dropdown-" + this.name); if (panel) { fillHeadIcons(panel, this.headState); + refreshPipetteInfoPanel(this.headState); } } if ("head96_state" in state) { From 61f2ff35868ae61fb6e8fdb4aabf75055e530259 Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Mon, 2 Feb 2026 22:33:40 +0000 Subject: [PATCH 11/20] prioritize GIF maker --- pylabrobot/visualizer/main.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylabrobot/visualizer/main.css b/pylabrobot/visualizer/main.css index 480ffa33761..ecda5c50f0e 100644 --- a/pylabrobot/visualizer/main.css +++ b/pylabrobot/visualizer/main.css @@ -367,7 +367,7 @@ main { display: flex; flex-direction: column; gap: 0; - z-index: 5; + z-index: 20; } .tool-panel-row { From ff2d850930382897e04e95734d6567dacb7c4c06 Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Tue, 3 Feb 2026 00:03:19 +0000 Subject: [PATCH 12/20] create measurement recording inside `Get Location` tool --- pylabrobot/visualizer/index.html | 42 ++++- pylabrobot/visualizer/lib.js | 264 +++++++++++++++++++++++++++++++ pylabrobot/visualizer/main.css | 3 +- 3 files changed, 306 insertions(+), 3 deletions(-) diff --git a/pylabrobot/visualizer/index.html b/pylabrobot/visualizer/index.html index ec021b79832..a7c9bdfc5a4 100644 --- a/pylabrobot/visualizer/index.html +++ b/pylabrobot/visualizer/index.html @@ -33,7 +33,7 @@ PyLabRobot Visualizer - {{ source_filename }} + {{ source_filename }} @@ -104,7 +104,27 @@