Files
PozzettiAndrea-ComfyUI-Geom…/javascript/js/compare_skeleton_widget.js
T
PozzettiAndrea 414f759791 Vendor frontend JS into javascript/; drop the prestartup copy dance
The viewer JS/HTML/bundles are now committed in javascript/ (declared via
pyproject [tool.comfy] web = "javascript") instead of being copied from the
shared comfy-3d-viewers pip package at prestartup. Consequences:

- requirements.txt is now just comfy-env (host env installs nothing else,
  per the never-touch-the-host-env principle)
- prestartup_script.py no longer copies viewers or web_src; it keeps only
  setup_env() and the input-asset copies
- WEB_DIRECTORY attribute removed; the pyproject declaration is the single
  source (served at /extensions/comfyui-geometrypack/, keyed by project name)
- web_src/ deleted; its 6 widgets now live in javascript/js/ as the tracked
  source, edited directly
- viewer_pbr.html hardcoded-fallback URL updated to the new mount name

Iframe URLs auto-detect the mount from import.meta.url, so the folder rename
and mount-name change are transparent at runtime.
2026-08-12 17:37:09 +02:00

220 lines
9.4 KiB
JavaScript

/**
* ComfyUI Compare Skeletons Widget
*
* Widget for UniRigCompareSkeletons node - displays two FBX skeletons side-by-side
* with synced camera rotation and cross-bone highlighting.
*/
import { app } from "../../../../scripts/app.js";
import { detectExtensionFolder, getViewerUrl } from "./mesh_preview_fbx.js";
console.log("[UniRig Compare] Loading compare skeleton widget...");
const extensionFolder = detectExtensionFolder();
if (!extensionFolder) {
console.error("[UniRig Compare] Could not detect extension folder");
}
console.log("[UniRig Compare] Detected extension folder:", extensionFolder);
app.registerExtension({
name: "unirig.compareskeletons",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === "CompareSkeletons" || nodeData.name === "UniRigCompareSkeletons") {
console.log("[UniRig Compare] Registering Compare Skeletons node");
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function() {
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
console.log("[UniRig Compare] Node created, adding compare viewer widget");
// Create iframe for comparison viewer
const iframe = document.createElement("iframe");
iframe.style.width = "100%";
iframe.style.flex = "1 1 0";
iframe.style.minHeight = "0";
iframe.style.border = "none";
iframe.style.backgroundColor = "#2a2a2a";
// Load viewer from file URL
const viewerUrl = getViewerUrl(extensionFolder, "viewer_fbx_compare.html");
iframe.src = viewerUrl;
console.log("[UniRig Compare] Setting iframe src to:", viewerUrl);
iframe.onload = () => {
console.log("[UniRig Compare] Iframe loaded successfully");
};
iframe.onerror = (e) => {
console.error("[UniRig Compare] Iframe failed to load:", e);
};
// Add widget
const widget = this.addDOMWidget("preview", "FBX_COMPARE_PREVIEW", iframe, {
getValue() { return ""; },
setValue(v) { }
});
console.log("[UniRig Compare] Widget created:", widget);
// Set widget size - wider for side-by-side view
widget.computeSize = function(width) {
const w = width || 800;
const h = w * 0.75; // Wider aspect ratio for dual view
return [w, h];
};
widget.element = iframe;
// Store iframe reference
this.compareViewerIframe = iframe;
this.compareViewerReady = false;
// Listen for ready message from iframe
const onMessage = (event) => {
if (event.data && event.data.type === 'VIEWER_READY') {
console.log("[UniRig Compare] Viewer iframe is ready!");
this.compareViewerReady = true;
}
};
window.addEventListener('message', onMessage.bind(this));
const notifyIframeResize = () => {
if (iframe.contentWindow) {
const rect = iframe.getBoundingClientRect();
iframe.contentWindow.postMessage({
type: 'RESIZE',
width: rect.width,
height: rect.height
}, '*');
}
};
this.onResize = function(size) {
const isVueNodes = iframe.closest('[data-node-id]') !== null ||
document.querySelector('.vue-graph-canvas') !== null;
if (!isVueNodes && size && size[1]) {
const nodeHeight = size[1];
const headerHeight = 70;
const availableHeight = Math.max(200, nodeHeight - headerHeight);
iframe.style.height = availableHeight + 'px';
}
requestAnimationFrame(() => {
notifyIframeResize();
});
};
let resizeTimeout = null;
let lastSize = { width: 0, height: 0 };
const resizeObserver = new ResizeObserver((entries) => {
const entry = entries[0];
const newWidth = entry.contentRect.width;
const newHeight = entry.contentRect.height;
if (Math.abs(newWidth - lastSize.width) < 1 && Math.abs(newHeight - lastSize.height) < 1) {
return;
}
lastSize = { width: newWidth, height: newHeight };
if (resizeTimeout) {
clearTimeout(resizeTimeout);
}
resizeTimeout = setTimeout(() => {
notifyIframeResize();
}, 50);
});
resizeObserver.observe(iframe);
const originalOnRemoved = this.onRemoved;
this.onRemoved = function() {
resizeObserver.disconnect();
if (resizeTimeout) {
clearTimeout(resizeTimeout);
}
window.removeEventListener('message', onMessage);
if (originalOnRemoved) {
originalOnRemoved.apply(this, arguments);
}
};
// Set initial node size (wider for dual view)
this.setSize([800, 600]);
// Handle execution
const onExecuted = this.onExecuted;
this.onExecuted = function(message) {
console.log("[UniRig Compare] onExecuted called with message:", message);
onExecuted?.apply(this, arguments);
// The message contains both FBX file paths
const leftFile = message?.fbx_file_left?.[0];
const rightFile = message?.fbx_file_right?.[0];
if (leftFile || rightFile) {
console.log("[UniRig Compare] Loading FBX files - Left:", leftFile, "Right:", rightFile);
// Build file paths
const buildFilepath = (filename) => {
if (!filename) return null;
if (!filename.includes('/') && !filename.includes('\\')) {
return `${window.location.origin}/view?filename=${encodeURIComponent(filename)}&type=output&subfolder=`;
} else {
const basename = filename.split(/[/\\]/).pop();
return `${window.location.origin}/view?filename=${encodeURIComponent(basename)}&type=output&subfolder=`;
}
};
const filepathLeft = buildFilepath(leftFile);
const filepathRight = buildFilepath(rightFile);
// Send message to iframe
const sendMessage = () => {
if (iframe.contentWindow) {
console.log("[UniRig Compare] Sending postMessage to iframe");
iframe.contentWindow.postMessage({
type: "LOAD_FBX_COMPARE",
filepathLeft: filepathLeft,
filepathRight: filepathRight,
timestamp: Date.now()
}, "*");
} else {
console.error("[UniRig Compare] Iframe contentWindow not available");
}
};
// Wait for iframe to be ready, or use timeout as fallback
if (this.compareViewerReady) {
sendMessage();
} else {
const checkReady = setInterval(() => {
if (this.compareViewerReady) {
clearInterval(checkReady);
sendMessage();
}
}, 50);
// Fallback timeout after 2 seconds
setTimeout(() => {
clearInterval(checkReady);
if (!this.compareViewerReady) {
console.warn("[UniRig Compare] Iframe not ready after 2s, sending anyway");
sendMessage();
}
}, 2000);
}
} else {
console.log("[UniRig Compare] No FBX files in message data. Keys:", Object.keys(message || {}));
}
};
return r;
};
}
}
});
console.log("[UniRig Compare] Compare skeleton widget registered");