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.
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* PostMessage utilities for iframe communication
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a message sender function for an iframe
|
||||
* @param {HTMLIFrameElement} iframe - The iframe element
|
||||
* @param {string} logPrefix - Logging prefix (e.g., "[GeomPack VTK]")
|
||||
* @returns {Function} Message sending function
|
||||
*/
|
||||
export function createMessageSender(iframe, logPrefix = "[Viewer]") {
|
||||
return (data) => {
|
||||
if (iframe.contentWindow) {
|
||||
iframe.contentWindow.postMessage(data, "*");
|
||||
} else {
|
||||
console.error(`${logPrefix} Iframe contentWindow not available`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mesh load message
|
||||
* @param {string} filepath - URL path to the mesh file
|
||||
* @returns {Object} Message data object
|
||||
*/
|
||||
export function createLoadMeshMessage(filepath) {
|
||||
return {
|
||||
type: "LOAD_MESH",
|
||||
filepath: filepath,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a dual mesh load message
|
||||
* @param {Object} options - Message options
|
||||
* @returns {Object} Message data object
|
||||
*/
|
||||
export function createLoadDualMeshMessage(options) {
|
||||
const {
|
||||
layout,
|
||||
mesh1Filepath,
|
||||
mesh2Filepath,
|
||||
meshFilepath, // For overlay mode
|
||||
opacity1 = 1.0,
|
||||
opacity2 = 1.0,
|
||||
showEdges = false,
|
||||
cameraState = "",
|
||||
selectedField = "",
|
||||
selectedChannel = "magnitude",
|
||||
selectedColormap = "erdc_rainbow_bright",
|
||||
} = options;
|
||||
|
||||
return {
|
||||
type: 'LOAD_DUAL_MESH',
|
||||
layout: layout,
|
||||
mesh1Filepath,
|
||||
mesh2Filepath,
|
||||
meshFilepath,
|
||||
opacity1,
|
||||
opacity2,
|
||||
showEdges,
|
||||
cameraState,
|
||||
selectedField,
|
||||
selectedChannel,
|
||||
selectedColormap,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build ComfyUI view API URL for a file.
|
||||
* Handles absolute paths (/home/.../output/sub/file.glb),
|
||||
* relative paths (output/sub/file.glb), and bare filenames (file.glb).
|
||||
* @param {string} filename - The filename or path
|
||||
* @param {string} defaultType - Default file type (default: "output")
|
||||
* @param {string} defaultSubfolder - Default subfolder path (default: "")
|
||||
* @returns {string} API URL
|
||||
*/
|
||||
export function buildViewUrl(filename, defaultType = "output", defaultSubfolder = "") {
|
||||
const normalized = filename.replace(/\\/g, '/');
|
||||
const pathMatch = normalized.match(/(?:^|\/)(output|input|temp)\/(.+)$/);
|
||||
if (pathMatch) {
|
||||
const [, type, relPath] = pathMatch;
|
||||
const parts = relPath.split('/');
|
||||
const fname = parts.pop();
|
||||
const subfolder = parts.join('/');
|
||||
return `/view?filename=${encodeURIComponent(fname)}&type=${type}&subfolder=${encodeURIComponent(subfolder)}`;
|
||||
}
|
||||
// Bare filename fallback
|
||||
const basename = normalized.split('/').pop();
|
||||
return `/view?filename=${encodeURIComponent(basename)}&type=${defaultType}&subfolder=${defaultSubfolder}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an iframe viewer manager for handling viewer switching
|
||||
* @param {HTMLIFrameElement} iframe - The iframe element
|
||||
* @param {string} logPrefix - Logging prefix
|
||||
* @returns {Object} Viewer manager object
|
||||
*/
|
||||
export function createViewerManager(iframe, logPrefix = "[Viewer]") {
|
||||
let currentViewerType = null;
|
||||
let iframeLoaded = false;
|
||||
let pendingMessage = null;
|
||||
|
||||
// Track iframe load state
|
||||
iframe.addEventListener('load', () => {
|
||||
iframeLoaded = true;
|
||||
// Send pending message if any
|
||||
if (pendingMessage && iframe.contentWindow) {
|
||||
iframe.contentWindow.postMessage(pendingMessage, "*");
|
||||
pendingMessage = null;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get current viewer type
|
||||
* @returns {string|null}
|
||||
*/
|
||||
getCurrentType() {
|
||||
return currentViewerType;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if iframe is loaded
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isLoaded() {
|
||||
return iframeLoaded;
|
||||
},
|
||||
|
||||
/**
|
||||
* Send a message to the iframe viewer
|
||||
* If iframe is not loaded yet, queue the message
|
||||
* @param {Object} data - Message data
|
||||
*/
|
||||
sendMessage(data) {
|
||||
if (iframeLoaded && iframe.contentWindow) {
|
||||
iframe.contentWindow.postMessage(data, "*");
|
||||
} else {
|
||||
pendingMessage = data;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Switch to a different viewer type if needed
|
||||
* @param {string} viewerType - New viewer type
|
||||
* @param {string} viewerUrl - URL of the new viewer
|
||||
* @param {Object} messageData - Message to send after viewer loads
|
||||
* @returns {boolean} True if viewer was switched, false if already on correct viewer
|
||||
*/
|
||||
switchViewer(viewerType, viewerUrl, messageData) {
|
||||
if (viewerType === currentViewerType) {
|
||||
// No switch needed, just send message
|
||||
this.sendMessage(messageData);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Viewer switch needed
|
||||
currentViewerType = viewerType;
|
||||
iframeLoaded = false;
|
||||
pendingMessage = messageData;
|
||||
|
||||
// Change iframe src to trigger reload
|
||||
iframe.src = viewerUrl + "?v=" + Date.now();
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset the manager state
|
||||
*/
|
||||
reset() {
|
||||
currentViewerType = null;
|
||||
iframeLoaded = false;
|
||||
pendingMessage = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an error message handler for iframe errors
|
||||
* @param {HTMLElement} infoPanel - The info panel to display errors
|
||||
* @param {string} logPrefix - Logging prefix
|
||||
* @returns {Function} Event handler function
|
||||
*/
|
||||
export function createErrorHandler(infoPanel, logPrefix = "[Viewer]") {
|
||||
return (event) => {
|
||||
if (event.data.type === 'MESH_ERROR' && event.data.error) {
|
||||
console.error(`${logPrefix} Error from viewer:`, event.data.error);
|
||||
if (infoPanel) {
|
||||
infoPanel.innerHTML = `<div style="color: #ff6b6b; padding: 8px;">Error: ${event.data.error}</div>`;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user