- Delete 7 dead-squat widgets (registered UI only for other packs' nodes: UniRig/SAM3D/Mocap/FBXPreview/BVH/pointcloud) + their 6 orphaned viewer HTMLs + the entire unused js/viewer tree (21 files, imported by nothing; only ui/styles.css survives) + unused utils barrel. - Rename namespace geompack -> geometrypack (= DisplayName, lowercased); all 17 surviving extensions now geometrypack.*. - Add event.source provenance guards to every message listener (2 inline, 2 factory handlers via new iframe param). - Stop touching document.body: upload file input stays detached (click() works detached in modern browsers); folder autocomplete dropdown becomes a manual popover inside the widget's own DOM (top layer escapes canvas transforms without shared-DOM appends). Passes comfy-test's javascript isolation lint with 0 errors.
87 lines
3.9 KiB
JavaScript
87 lines
3.9 KiB
JavaScript
// Preview Mesh Slicer: interactive clip/slice viewer (viewer_slicer.html) in-node.
|
|
// On execute the Python node writes the mesh .vtp to temp; we mount the slicer
|
|
// iframe and postMessage it the file URL.
|
|
import { app } from "../../../scripts/app.js";
|
|
|
|
const TAG = "[PreviewMeshSlicer]";
|
|
console.log(`${TAG} script loaded`);
|
|
|
|
const EXTENSION_FOLDER = (() => {
|
|
const m = import.meta.url.match(/\/extensions\/([^/]+)\//);
|
|
return m ? m[1] : "ComfyUI-GeometryPack";
|
|
})();
|
|
|
|
function viewUrl(name) {
|
|
const norm = String(name).replace(/\\/g, "/");
|
|
const m = norm.match(/(?:^|\/)(output|input|temp)\/(.+)$/);
|
|
if (m) {
|
|
const [, type, rel] = m;
|
|
const parts = rel.split("/");
|
|
const fname = parts.pop();
|
|
return `/view?filename=${encodeURIComponent(fname)}&type=${type}&subfolder=${encodeURIComponent(parts.join("/"))}`;
|
|
}
|
|
return `/view?filename=${encodeURIComponent(norm.split("/").pop())}&type=temp&subfolder=`;
|
|
}
|
|
|
|
app.registerExtension({
|
|
name: "geometrypack.previewmeshslicer",
|
|
async beforeRegisterNodeDef(nodeType, nodeData) {
|
|
if (nodeData.name !== "GeomPackPreviewMeshSlicer") return;
|
|
console.log(`${TAG} registering for GeomPackPreviewMeshSlicer`);
|
|
|
|
const onCreated = nodeType.prototype.onNodeCreated;
|
|
nodeType.prototype.onNodeCreated = function () {
|
|
const r = onCreated?.apply(this, arguments);
|
|
const node = this;
|
|
const iframe = document.createElement("iframe");
|
|
iframe.style.cssText = "width:100%;height:100%;border:none;background:#2a2a2a;";
|
|
iframe.src = `/extensions/${EXTENSION_FOLDER}/viewer_slicer.html?v=` + Date.now();
|
|
const widget = node.addDOMWidget("slicer", "MESH_PREVIEW", iframe, {
|
|
getValue() { return ""; }, setValue() { },
|
|
});
|
|
widget.computeSize = (w) => [w || 480, Math.round((w || 480) * 1.15)];
|
|
node.size = [Math.max(node.size?.[0] || 0, 420), Math.max(node.size?.[1] || 0, 540)];
|
|
node._gpSlicerIframe = iframe;
|
|
|
|
// plane_json (a real schema input -> auto-created widget) is a machine-written
|
|
// mirror of whatever clip plane is picked interactively in the iframe, not
|
|
// something meant to be hand-edited -- hide it from the node body.
|
|
const planeWidget = node.widgets?.find((w) => w.name === "plane_json");
|
|
if (planeWidget) {
|
|
planeWidget.hidden = true;
|
|
planeWidget.computeSize = () => [0, -4];
|
|
}
|
|
|
|
// The clip plane only ever exists as local JS state inside the iframe
|
|
// (viewer_slicer.html) -- execute() has no other way to see it, so mirror it
|
|
// into the (hidden) plane_json widget on every change; that's what gets
|
|
// serialized into the prompt on the next queue.
|
|
window.addEventListener("message", (event) => {
|
|
if (event.source !== iframe.contentWindow) return;
|
|
if (event.data?.type !== "PLANE_UPDATE") return;
|
|
const w = node.widgets?.find((x) => x.name === "plane_json");
|
|
if (!w) return;
|
|
w.value = JSON.stringify(event.data);
|
|
node.setDirtyCanvas(true, true);
|
|
});
|
|
|
|
return r;
|
|
};
|
|
|
|
const onExecuted = nodeType.prototype.onExecuted;
|
|
nodeType.prototype.onExecuted = function (message) {
|
|
onExecuted?.apply(this, arguments);
|
|
const iframe = this._gpSlicerIframe;
|
|
const name = message?.mesh_file?.[0];
|
|
if (!iframe || !name) return;
|
|
const url = viewUrl(name);
|
|
setTimeout(() => {
|
|
if (iframe.contentWindow) {
|
|
console.log(`${TAG} loading ${url}`);
|
|
iframe.contentWindow.postMessage({ type: "LOAD_MESH", filepath: url, timestamp: Date.now() }, "*");
|
|
}
|
|
}, 250);
|
|
};
|
|
},
|
|
});
|