mirror of
https://git.datalinker.icu/kijai/ComfyUI-KJNodes.git
synced 2025-12-09 21:04:41 +08:00
commit d5e37ff797ecfe975bed982f25cbfbfd1e60c7ee
Author: kijai <40791699+kijai@users.noreply.github.com>
Date: Sat Apr 20 21:22:29 2024 +0300
Update spline_editor.js
commit bdade789ee3650cbd2b42bb844b16e175267575d
Author: kijai <40791699+kijai@users.noreply.github.com>
Date: Sat Apr 20 21:21:34 2024 +0300
spline editor fixes
commit 1aef2b8e43397eddc49dca304d59bd7a6674f55d
Author: kijai <40791699+kijai@users.noreply.github.com>
Date: Sat Apr 20 18:20:02 2024 +0300
spline editor updates
commit cb1e98abf1d9fcacc9b4912971df175c5a8ea9ac
Author: kijai <40791699+kijai@users.noreply.github.com>
Date: Sat Apr 20 11:27:50 2024 +0300
Update spline_editor.js
commit 891daeb4389318e9880f1ff68f53eba068f46739
Merge: d9712b0 6e1fa8d
Author: kijai <40791699+kijai@users.noreply.github.com>
Date: Fri Apr 19 18:40:44 2024 +0300
Merge branch 'main' into develop
commit d9712b0e04c70c6299fa61ce5c128ba56228ea11
Author: kijai <40791699+kijai@users.noreply.github.com>
Date: Thu Apr 18 01:45:32 2024 +0300
spline editor work
commit 92711dc7625da578cf59520a41462a845069ac56
Author: Kijai <40791699+kijai@users.noreply.github.com>
Date: Wed Apr 17 19:43:49 2024 +0300
Update spline_editor.js
commit 6f256423afa6d3e8a0ca2add461328d9f9b4ce82
Author: Kijai <40791699+kijai@users.noreply.github.com>
Date: Wed Apr 17 19:21:32 2024 +0300
Update spline_editor.js
commit 47c23d5a19c84d22d94668f5b5ba2ceab5b94988
Author: Kijai <40791699+kijai@users.noreply.github.com>
Date: Tue Apr 16 19:19:16 2024 +0300
reworking spline editor (not functional yet)
304 lines
9.4 KiB
JavaScript
304 lines
9.4 KiB
JavaScript
import { app } from '../../../scripts/app.js'
|
|
|
|
//from melmass
|
|
export function makeUUID() {
|
|
let dt = new Date().getTime()
|
|
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
const r = ((dt + Math.random() * 16) % 16) | 0
|
|
dt = Math.floor(dt / 16)
|
|
return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16)
|
|
})
|
|
return uuid
|
|
}
|
|
|
|
export const loadScript = (
|
|
FILE_URL,
|
|
async = true,
|
|
type = 'text/javascript',
|
|
) => {
|
|
return new Promise((resolve, reject) => {
|
|
try {
|
|
// Check if the script already exists
|
|
const existingScript = document.querySelector(`script[src="${FILE_URL}"]`)
|
|
if (existingScript) {
|
|
resolve({ status: true, message: 'Script already loaded' })
|
|
return
|
|
}
|
|
|
|
const scriptEle = document.createElement('script')
|
|
scriptEle.type = type
|
|
scriptEle.async = async
|
|
scriptEle.src = FILE_URL
|
|
|
|
scriptEle.addEventListener('load', (ev) => {
|
|
resolve({ status: true })
|
|
})
|
|
|
|
scriptEle.addEventListener('error', (ev) => {
|
|
reject({
|
|
status: false,
|
|
message: `Failed to load the script ${FILE_URL}`,
|
|
})
|
|
})
|
|
|
|
document.body.appendChild(scriptEle)
|
|
} catch (error) {
|
|
reject(error)
|
|
}
|
|
})
|
|
}
|
|
const create_documentation_stylesheet = () => {
|
|
const tag = 'kj-splineditor-stylesheet'
|
|
|
|
let styleTag = document.head.querySelector(tag)
|
|
|
|
if (!styleTag) {
|
|
styleTag = document.createElement('style')
|
|
styleTag.type = 'text/css'
|
|
styleTag.id = tag
|
|
styleTag.innerHTML = `
|
|
.spline-editor {
|
|
|
|
position: absolute;
|
|
|
|
font: 12px monospace;
|
|
line-height: 1.5em;
|
|
padding: 10px;
|
|
z-index: 0;
|
|
overflow: hidden;
|
|
}
|
|
`
|
|
document.head.appendChild(styleTag)
|
|
}
|
|
}
|
|
|
|
loadScript('/kjweb_async/svg-path-properties.min.js').catch((e) => {
|
|
console.log(e)
|
|
})
|
|
loadScript('/kjweb_async/protovis.min.js').catch((e) => {
|
|
console.log(e)
|
|
})
|
|
create_documentation_stylesheet()
|
|
|
|
function chainCallback(object, property, callback) {
|
|
if (object == undefined) {
|
|
//This should not happen.
|
|
console.error("Tried to add callback to non-existant object")
|
|
return;
|
|
}
|
|
if (property in object) {
|
|
const callback_orig = object[property]
|
|
object[property] = function () {
|
|
const r = callback_orig.apply(this, arguments);
|
|
callback.apply(this, arguments);
|
|
return r
|
|
};
|
|
} else {
|
|
object[property] = callback;
|
|
}
|
|
}
|
|
app.registerExtension({
|
|
name: 'KJNodes.SplineEditor',
|
|
|
|
async beforeRegisterNodeDef(nodeType, nodeData) {
|
|
if (nodeData?.name == 'SplineEditor') {
|
|
chainCallback(nodeType.prototype, "onNodeCreated", function () {
|
|
hideWidgetForGood(this, this.widgets.find(w => w.name === "coordinates"))
|
|
|
|
var element = document.createElement("div");
|
|
this.uuid = makeUUID()
|
|
element.id = `spline-editor-${this.uuid}`
|
|
|
|
this.splineEditor = this.addDOMWidget(nodeData.name, "SplineEditorWidget", element, {
|
|
serialize: false,
|
|
hideOnZoom: false,
|
|
});
|
|
this.addWidget("button", "New spline", null, () => {
|
|
|
|
if (!this.properties || !("points" in this.properties)) {
|
|
createSplineEditor(this)
|
|
this.addProperty("points", this.constructor.type, "string");
|
|
}
|
|
else {
|
|
createSplineEditor(this, true)
|
|
}
|
|
});
|
|
this.setSize([550, 800])
|
|
this.splineEditor.parentEl = document.createElement("div");
|
|
this.splineEditor.parentEl.className = "spline-editor";
|
|
this.splineEditor.parentEl.id = `spline-editor-${this.uuid}`
|
|
element.appendChild(this.splineEditor.parentEl);
|
|
|
|
//disable context menu on right click
|
|
document.addEventListener('contextmenu', function(e) {
|
|
if (e.button === 2) { // Right mouse button
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
}
|
|
})
|
|
chainCallback(this, "onGraphConfigured", function() {
|
|
createSplineEditor(this)
|
|
});
|
|
}); // onAfterGraphConfigured
|
|
}//node created
|
|
} //before register
|
|
})//register
|
|
|
|
|
|
function createSplineEditor(context, reset=false) {
|
|
console.log("creatingSplineEditor")
|
|
|
|
if (reset && context.splineEditor.element) {
|
|
context.splineEditor.element.innerHTML = ''; // Clear the container
|
|
}
|
|
const coordWidget = context.widgets.find(w => w.name === "coordinates");
|
|
const interpolationWidget = context.widgets.find(w => w.name === "interpolation");
|
|
const pointsWidget = context.widgets.find(w => w.name === "points_to_sample");
|
|
const pointsStoreWidget = context.widgets.find(w => w.name === "points_store");
|
|
const tensionWidget = context.widgets.find(w => w.name === "tension");
|
|
const segmentedWidget = context.widgets.find(w => w.name === "segmented");
|
|
|
|
// Initialize or reset points array
|
|
var w = 512
|
|
var h = 512
|
|
var i = 3
|
|
let points = [];
|
|
if (!reset && pointsStoreWidget.value != "") {
|
|
points = JSON.parse(pointsStoreWidget.value);
|
|
} else {
|
|
points = pv.range(1, 4).map((i, index) => {
|
|
if (index === 0) {
|
|
// First point at the bottom-left corner
|
|
return { x: 0, y: h };
|
|
} else if (index === 2) {
|
|
// Last point at the top-right corner
|
|
return { x: w, y: 0 };
|
|
} else {
|
|
// Other points remain as they were
|
|
return {
|
|
x: i * w / 5,
|
|
y: 50 + Math.random() * (h - 100)
|
|
};
|
|
}
|
|
});
|
|
pointsStoreWidget.value = JSON.stringify(points);
|
|
}
|
|
|
|
var vis = new pv.Panel()
|
|
.width(w)
|
|
.height(h)
|
|
.fillStyle("var(--comfy-menu-bg)")
|
|
.strokeStyle("gray")
|
|
.lineWidth(2)
|
|
.antialias(false)
|
|
.margin(10)
|
|
.event("mousedown", function() {
|
|
if (pv.event.shiftKey) { // Use pv.event to access the event object
|
|
i = points.push(this.mouse()) - 1;
|
|
return this;
|
|
}
|
|
})
|
|
.event("mouseup", function() {
|
|
if (this.pathElements !== null) {
|
|
let coords = samplePoints(pathElements[0], pointsWidget.value);
|
|
let coordsString = JSON.stringify(coords);
|
|
pointsStoreWidget.value = JSON.stringify(points);
|
|
if (coordWidget) {
|
|
coordWidget.value = coordsString;
|
|
}
|
|
}
|
|
});
|
|
|
|
vis.add(pv.Rule)
|
|
.data(pv.range(0, 8, .5))
|
|
.bottom(d => d * 64 + 0)
|
|
.strokeStyle("gray")
|
|
.lineWidth(1)
|
|
|
|
vis.add(pv.Line)
|
|
.data(() => points)
|
|
.left(d => d.x)
|
|
.top(d => d.y)
|
|
.interpolate(() => interpolationWidget.value)
|
|
.tension(() => tensionWidget.value)
|
|
.segmented(() => segmentedWidget.value)
|
|
.strokeStyle(pv.Colors.category10().by(pv.index))
|
|
.lineWidth(3)
|
|
|
|
vis.add(pv.Dot)
|
|
.data(() => points)
|
|
.left(d => d.x)
|
|
.top(d => d.y)
|
|
.radius(8)
|
|
.cursor("move")
|
|
.strokeStyle(function() { return i == this.index ? "#ff7f0e" : "#1f77b4"; })
|
|
.fillStyle(function() { return "rgba(100, 100, 100, 0.2)"; })
|
|
.event("mousedown", pv.Behavior.drag())
|
|
.event("dragstart", function() {
|
|
i = this.index;
|
|
if (pv.event.button === 2) {
|
|
points.splice(i--, 1);
|
|
vis.render();
|
|
}
|
|
return this;
|
|
})
|
|
.event("drag", vis)
|
|
.anchor("top").add(pv.Label)
|
|
.font(d => Math.sqrt(d[2]) * 32 + "px sans-serif")
|
|
//.text(d => `(${Math.round(d.x)}, ${Math.round(d.y)})`)
|
|
.text(d => {
|
|
// Normalize y to range 0.0 to 1.0, considering the inverted y-axis
|
|
var normalizedY = 1.0 - (d.y / h);
|
|
return `${normalizedY.toFixed(2)}`;
|
|
})
|
|
.textStyle("orange")
|
|
|
|
vis.render();
|
|
var svgElement = vis.canvas();
|
|
svgElement.style['zIndex'] = "2"
|
|
svgElement.style['position'] = "relative"
|
|
context.splineEditor.element.appendChild(svgElement);
|
|
var pathElements = svgElement.getElementsByTagName('path'); // Get all path elements
|
|
|
|
}
|
|
function samplePoints(svgPathElement, numSamples) {
|
|
var pathLength = svgPathElement.getTotalLength();
|
|
var points = [];
|
|
|
|
for (var i = 0; i < numSamples; i++) {
|
|
// Calculate the distance along the path for the current sample
|
|
var distance = (pathLength / (numSamples - 1)) * i;
|
|
|
|
// Get the point at the current distance
|
|
var point = svgPathElement.getPointAtLength(distance);
|
|
|
|
// Add the point to the array of points
|
|
points.push({ x: point.x, y: point.y });
|
|
}
|
|
//console.log(points);
|
|
return points;
|
|
}
|
|
|
|
//from melmass
|
|
export function hideWidgetForGood(node, widget, suffix = '') {
|
|
widget.origType = widget.type
|
|
widget.origComputeSize = widget.computeSize
|
|
widget.origSerializeValue = widget.serializeValue
|
|
widget.computeSize = () => [0, -4] // -4 is due to the gap litegraph adds between widgets automatically
|
|
widget.type = "converted-widget" + suffix
|
|
// widget.serializeValue = () => {
|
|
// // Prevent serializing the widget if we have no input linked
|
|
// const w = node.inputs?.find((i) => i.widget?.name === widget.name);
|
|
// if (w?.link == null) {
|
|
// return undefined;
|
|
// }
|
|
// return widget.origSerializeValue ? widget.origSerializeValue() : widget.value;
|
|
// };
|
|
|
|
// Hide any linked widgets, e.g. seed+seedControl
|
|
if (widget.linkedWidgets) {
|
|
for (const w of widget.linkedWidgets) {
|
|
hideWidgetForGood(node, w, ':' + widget.name)
|
|
}
|
|
}
|
|
} |