Squashed commit of the following:

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)
This commit is contained in:
kijai 2024-04-20 21:22:44 +03:00
parent 6d24bf322f
commit 1959a4de55
3 changed files with 213 additions and 234 deletions

View File

@ -4626,7 +4626,8 @@ class SplineEditor:
def INPUT_TYPES(cls):
return {
"required": {
"coordinates": ("STRING", {"multiline": True}),
"points_store": ("STRING", {"multiline": False}),
"coordinates": ("STRING", {"multiline": False}),
"mask_width": ("INT", {"default": 512, "min": 8, "max": MAX_RESOLUTION, "step": 8}),
"mask_height": ("INT", {"default": 512, "min": 8, "max": MAX_RESOLUTION, "step": 18}),
"points_to_sample": ("INT", {"default": 4, "min": 2, "max": 1000, "step": 1}),
@ -4638,10 +4639,14 @@ class SplineEditor:
'linear',
'step-before',
'step-after',
'polar',
'polar-reverse',
],
{
"default": 'cardinal'
}),
"tension": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
"segmented": ("BOOLEAN", {"default": False}),
},
}
@ -4650,8 +4655,8 @@ class SplineEditor:
CATEGORY = "KJNodes/experimental"
def splinedata(self, mask_width, mask_height, coordinates, interpolation, points_to_sample):
def splinedata(self, mask_width, mask_height, coordinates, interpolation, points_to_sample, points_store, tension, segmented):
print(coordinates)
coordinates = json.loads(coordinates)
print(coordinates)

View File

@ -5,9 +5,6 @@ app.registerExtension({
async beforeRegisterNodeDef(nodeType, nodeData, app) {
switch (nodeData.name) {
case "ConditioningMultiCombine":
nodeType.prototype.onNodeMoved = function () {
console.log(this.pos[0])
}
nodeType.prototype.onNodeCreated = function () {
//this.inputs_offset = nodeData.name.includes("selective")?1:0
this.cond_type = "CONDITIONING"

View File

@ -1,5 +1,15 @@
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,
@ -48,20 +58,14 @@ export const loadScript = (
styleTag.id = tag
styleTag.innerHTML = `
.spline-editor {
background: var(--comfy-menu-bg);
position: absolute;
color: var(--fg-color);
font: 12px monospace;
line-height: 1.5em;
padding: 10px;
z-index: 5;
z-index: 0;
overflow: hidden;
border-radius: 10px;
border-style: solid;
border-width: medium;
border-color: var(--border-color);
height: 544px;
width: 544px;
}
`
document.head.appendChild(styleTag)
@ -76,238 +80,187 @@ loadScript('/kjweb_async/protovis.min.js').catch((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.curves',
name: 'KJNodes.SplineEditor',
async beforeRegisterNodeDef(nodeType, nodeData) {
if (nodeData.name == 'SplineEditor') {
addElement(nodeData, nodeType);
}
},
})
if (nodeData?.name == 'SplineEditor') {
chainCallback(nodeType.prototype, "onNodeCreated", function () {
hideWidgetForGood(this, this.widgets.find(w => w.name === "coordinates"))
export const addElement = (nodeData,nodeType) => {
console.log("Creating spline editor")
const iconSize = 24
const iconMargin = 4
let splineEditor = null
let vis = null
const drawFg = nodeType.prototype.onDrawForeground
nodeType.prototype.onNodeCreated = function () {
console.log("Node created")
this.coordWidget = this.widgets.find(w => w.name === "coordinates");
this.interpolationWidget = this.widgets.find(w => w.name === "interpolation");
this.pointsWidget = this.widgets.find(w => w.name === "points_to_sample");
}
nodeType.prototype.onRemoved = function () {
console.log("Node removed")
if (splineEditor !== null) {
splineEditor.parentNode.removeChild(splineEditor)
splineEditor = null
}
}
nodeType.prototype.onDrawForeground = function (ctx) {
console.log("Drawing foreground")
const r = drawFg ? drawFg.apply(this, arguments) : undefined
if (this.flags.collapsed) return r
const x = this.size[0] - iconSize - iconMargin
var element = document.createElement("div");
this.uuid = makeUUID()
element.id = `spline-editor-${this.uuid}`
if (this.show_doc && splineEditor === null) {
console.log("Drawing spline editor")
splineEditor = document.createElement('div');
splineEditor.classList.add('spline-editor');
// close button
const closeButton = document.createElement('div');
closeButton.textContent = '❌';
closeButton.style.position = 'absolute';
closeButton.style.top = '0';
closeButton.style.right = '0';
closeButton.style.cursor = 'pointer';
closeButton.style.padding = '5px';
closeButton.style.color = 'red';
closeButton.style.fontSize = '12px';
closeButton.addEventListener('mousedown', (e) => {
e.stopPropagation();
this.show_doc = !this.show_doc
splineEditor.parentNode.removeChild(splineEditor)
splineEditor = null
});
splineEditor.appendChild(closeButton)
var w = 512
var h = 512
var i = 3
if (points == null) {
var points = pv.range(1, 5).map(i => ({
x: i * w / 5,
y: 50 + Math.random() * (h - 100)
}));
}
var segmented = false
vis = new pv.Panel()
.width(w)
.height(h)
.fillStyle("var(--comfy-menu-bg)")
.strokeStyle("orange")
.lineWidth(0)
.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;
}
});
vis.add(pv.Rule)
.data(pv.range(0, 8, .5))
.bottom(d => d * 70 + 9.5)
.strokeStyle("gray")
.lineWidth(1)
vis.add(pv.Line)
.data(() => points)
.left(d => d.x)
.top(d => d.y)
.interpolate(() => this.interpolationWidget.value)
.segmented(() => segmented)
.strokeStyle(pv.Colors.category10().by(pv.index))
.tension(0.5)
.lineWidth(3)
vis.add(pv.Dot)
.data(() => points)
.left(d => d.x)
.top(d => d.y)
.radius(7)
.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;
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")
//disable context menu on right click
document.addEventListener('contextmenu', function(e) {
if (e.button === 2) { // Right mouse button
e.preventDefault();
e.stopPropagation();
}
})
//right click remove dot
pv.listen(window, "mousedown", () => {
window.focus();
if (pv.event.button === 2) {
points.splice(i--, 1);
vis.render();
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)
}
});
//send coordinates to node on mouseup
pv.listen(window, "mouseup", () => {
if (pathElements !== null) {
let coords = samplePoints(pathElements[0], this.pointsWidget.value);
let coordsString = JSON.stringify(coords);
if (this.coordWidget) {
this.coordWidget.value = coordsString;
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();
}
}
});
vis.render();
var svgElement = vis.canvas();
splineEditor.appendChild(svgElement);
// this.addDOMWidget("videopreview", "preview", splineEditor, {
// serialize: false,
// hideOnZoom: false,
// });
document.body.appendChild(splineEditor)
var pathElements = svgElement.getElementsByTagName('path'); // Get all path elements
}
// close the popup
else if (!this.show_doc && splineEditor !== null) {
splineEditor.parentNode.removeChild(splineEditor)
splineEditor = null
}
if (this.show_doc && splineEditor !== null && vis !== null) {
const rect = ctx.canvas.getBoundingClientRect()
const scaleX = rect.width / ctx.canvas.width
const scaleY = rect.height / ctx.canvas.height
const transform = new DOMMatrix()
.scaleSelf(scaleX, scaleY)
.translateSelf(this.size[0] * scaleX, 0)
.multiplySelf(ctx.getTransform())
.translateSelf(10, -32)
const scale = new DOMMatrix()
.scaleSelf(transform.a, transform.d);
})
chainCallback(this, "onGraphConfigured", function() {
createSplineEditor(this)
});
}); // onAfterGraphConfigured
}//node created
} //before register
})//register
const styleObject = {
transformOrigin: '0 0',
transform: scale,
left: `${transform.a + transform.e}px`,
top: `${transform.d + transform.f}px`,
};
Object.assign(splineEditor.style, styleObject);
}
ctx.save()
ctx.translate(x - 2, iconSize - 45)
ctx.scale(iconSize / 32, iconSize / 32)
ctx.strokeStyle = 'rgba(255,255,255,0.3)'
ctx.lineCap = 'round'
ctx.lineJoin = 'round'
ctx.lineWidth = 2.4
ctx.font = 'bold 36px monospace'
ctx.fillStyle = 'orange';
ctx.fillText('📈', 0, 24)
ctx.restore()
return r
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)
};
}
// handle clicking of the icon
const mouseDown = nodeType.prototype.onMouseDown
nodeType.prototype.onMouseDown = function (e, localPos, canvas) {
const r = mouseDown ? mouseDown.apply(this, arguments) : undefined
const iconX = this.size[0] - iconSize - iconMargin
const iconY = iconSize - 45
if (
localPos[0] > iconX &&
localPos[0] < iconX + iconSize &&
localPos[1] > iconY &&
localPos[1] < iconY + iconSize
) {
if (this.show_doc === undefined) {
this.show_doc = true
} else {
this.show_doc = !this.show_doc
});
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;
}
return true;
}
return r;
}
});
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 = [];
@ -324,4 +277,28 @@ function samplePoints(svgPathElement, numSamples) {
}
//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)
}
}
}