drawable feature templates

This commit is contained in:
ryanontheinstide
2025-01-20 11:27:24 -05:00
parent 426b419586
commit b2d36e5d2a
2 changed files with 298 additions and 35 deletions
+96 -35
View File
@@ -1,5 +1,6 @@
import { app } from "../../scripts/app.js";
import { ComfyWidgets } from "../../scripts/widgets.js";
import { FeatureTemplates } from "./feature_templates.js";
//TODO: add handling for maximum frame count change
@@ -257,13 +258,61 @@ app.registerExtension({
// console.error("Failed to restore points:", e);
}
// Add template dropdown and controls
const templateWidget = this.addWidget("combo", "Template", "selected_template", (v) => {}, {
values: [
"none",
// Basic waveforms
"sine",
"square",
"triangle",
"sawtooth",
// Animation curves
"easeInOut",
"easeIn",
"easeOut",
// Special patterns
"bounce",
"pulse",
"random",
"smoothRandom",
"heartbeat",
"steps"
],
value: "none"
});
templateWidget.name = "template";
// Add cycles control
const cyclesWidget = this.addWidget("number", "Template Cycles", "template_cycles", function(v) {
return Math.max(0.1, Math.min(200, v));
}, {
value: 1.0,
min: 0.1,
max: 200,
step: 0.1,
precision: 2
});
cyclesWidget.name = "template_cycles";
cyclesWidget.value = 1.0; // Explicitly set initial value
// Add load template button
const loadButton = this.addWidget("button", "Load Template", "load", () => {
const templateWidget = this.widgets.find(w => w.name === "template");
if (templateWidget && templateWidget.value && templateWidget.value !== "none") {
this.loadTemplate(templateWidget.value);
}
});
// Add clear button widget
this.addWidget("button", "Clear Graph", "clear", () => {
this.points = [];
this.updatePointsValue();
this.setDirtyCanvas(true, true);
});
// Add handlers for min/max value changes
const minValueWidget = this.widgets.find(w => w.name === "min_value");
const maxValueWidget = this.widgets.find(w => w.name === "max_value");
@@ -596,8 +645,17 @@ app.registerExtension({
const pointIndex = this.findNearPoint(x, y);
if (pointIndex !== null) {
this.selectedPoint = pointIndex;
this.dragStartPos = [...pos];
if (this.isDragging && this.selectedPoint !== null) {
// If already dragging and clicked, drop the point
this.isDragging = false;
this.selectedPoint = null;
this.dragStartPos = null;
} else {
// Start dragging
this.selectedPoint = pointIndex;
this.dragStartPos = [...pos];
this.isDragging = true;
}
} else {
// Not on a point - add new point
const [frame, value] = this.coordsToGraphValues(x, y);
@@ -636,30 +694,22 @@ app.registerExtension({
return false;
}
if (this.dragStartPos && this.selectedPoint !== null) {
// If we've moved more than 5 pixels, start dragging
const dragDist = Math.sqrt(
Math.pow(pos[0] - this.dragStartPos[0], 2) +
Math.pow(pos[1] - this.dragStartPos[1], 2)
);
if (dragDist > 5) {
this.isDragging = true;
const [frame, value] = this.coordsToGraphValues(x, y);
const frameCount = this.widgets.find(w => w.name === "frame_count")?.value || 30;
const maxFrame = frameCount - 1;
// Keep strict < frameCount for dragging to prevent edge issues
if (frame >= 0 && frame <= maxFrame) {
// Check if there's already a point at the target frame (except selected point)
const existingIndex = this.points.findIndex((p, i) =>
i !== this.selectedPoint && p[0] === frame);
if (existingIndex === -1) {
this.points[this.selectedPoint] = [frame, value];
this.points.sort((a, b) => a[0] - b[0]);
this.selectedPoint = this.points.findIndex(p => p[0] === frame);
this.updatePointsValue();
}
if (this.isDragging && this.selectedPoint !== null) {
const [frame, value] = this.coordsToGraphValues(x, y);
const frameCount = this.widgets.find(w => w.name === "frame_count")?.value || 30;
const maxFrame = frameCount - 1;
// Keep strict < frameCount for dragging to prevent edge issues
if (frame >= 0 && frame <= maxFrame) {
// Check if there's already a point at the target frame (except selected point)
const existingIndex = this.points.findIndex((p, i) =>
i !== this.selectedPoint && p[0] === frame);
if (existingIndex === -1) {
this.points[this.selectedPoint] = [frame, value];
this.points.sort((a, b) => a[0] - b[0]);
this.selectedPoint = this.points.findIndex(p =>
p[0] === frame && p[1] === value);
this.updatePointsValue();
}
}
} else {
@@ -673,15 +723,11 @@ app.registerExtension({
};
nodeType.prototype.onMouseUp = function(e, pos) {
// If we have a selected point but never started dragging, it's a click - delete the point
if (this.selectedPoint !== null && !this.isDragging) {
this.points.splice(this.selectedPoint, 1);
this.updatePointsValue();
// Only clear states if we're not in dragging mode
if (!this.isDragging) {
this.selectedPoint = null;
this.dragStartPos = null;
}
this.isDragging = false;
this.selectedPoint = null;
this.dragStartPos = null;
this.mouseDownTime = null;
this.setDirtyCanvas(true, true);
return false;
@@ -735,6 +781,21 @@ app.registerExtension({
this.setDirtyCanvas(true, true);
return false;
};
// Template loading method
nodeType.prototype.loadTemplate = function(templateName) {
const frameCount = this.getCurrentFrameCount();
const minValue = this.widgets.find(w => w.name === "min_value")?.value ?? 0;
const maxValue = this.widgets.find(w => w.name === "max_value")?.value ?? 1;
const cyclesWidget = this.widgets.find(w => w.name === "template_cycles");
const cycles = cyclesWidget?.value ?? 1;
if (templateName in FeatureTemplates) {
this.points = FeatureTemplates[templateName](frameCount, minValue, maxValue, cycles);
this.updatePointsValue();
this.setDirtyCanvas(true, true);
}
};
}
}
});
+202
View File
@@ -0,0 +1,202 @@
// Feature template generation functions
export const FeatureTemplates = {
// Basic waveforms
sine: (frameCount, minValue, maxValue, cycles = 1) => {
const amplitude = (maxValue - minValue) / 2;
const center = minValue + amplitude;
const points = [];
const numPoints = Math.min(frameCount, 30);
for (let i = 0; i < numPoints; i++) {
const frame = Math.round((i / (numPoints - 1)) * (frameCount - 1));
const value = center + amplitude * Math.sin((i / (numPoints - 1)) * Math.PI * 2 * cycles);
points.push([frame, value]);
}
return points;
},
square: (frameCount, minValue, maxValue, cycles = 1) => {
const points = [];
const numPoints = Math.min(frameCount, 30);
for (let i = 0; i < numPoints; i++) {
const frame = Math.round((i / (numPoints - 1)) * (frameCount - 1));
const phase = (i / (numPoints - 1)) * cycles;
const value = Math.floor(phase % 1 * 2) ? minValue : maxValue;
points.push([frame, value]);
}
return points;
},
triangle: (frameCount, minValue, maxValue, cycles = 1) => {
const points = [];
const numPoints = Math.min(frameCount, 30);
for (let i = 0; i < numPoints; i++) {
const frame = Math.round((i / (numPoints - 1)) * (frameCount - 1));
const phase = (i / (numPoints - 1)) * cycles % 1;
const triangleValue = phase < 0.5
? 2 * phase
: 2 * (1 - phase);
const value = minValue + (maxValue - minValue) * triangleValue;
points.push([frame, value]);
}
return points;
},
sawtooth: (frameCount, minValue, maxValue, cycles = 1) => {
const points = [];
const numPoints = Math.min(frameCount, 30);
for (let i = 0; i < numPoints; i++) {
const frame = Math.round((i / (numPoints - 1)) * (frameCount - 1));
const phase = (i / (numPoints - 1)) * cycles % 1;
const value = minValue + (maxValue - minValue) * phase;
points.push([frame, value]);
}
return points;
},
// Animation curves
easeInOut: (frameCount, minValue, maxValue, cycles = 1) => {
const points = [];
const numPoints = Math.min(frameCount, 30);
for (let i = 0; i < numPoints; i++) {
const frame = Math.round((i / (numPoints - 1)) * (frameCount - 1));
const t = (i / (numPoints - 1)) * cycles % 1;
// Cubic ease in-out
const easeValue = t < 0.5
? 4 * t * t * t
: 1 - Math.pow(-2 * t + 2, 3) / 2;
const value = minValue + (maxValue - minValue) * easeValue;
points.push([frame, value]);
}
return points;
},
easeIn: (frameCount, minValue, maxValue, cycles = 1) => {
const points = [];
const numPoints = Math.min(frameCount, 30);
for (let i = 0; i < numPoints; i++) {
const frame = Math.round((i / (numPoints - 1)) * (frameCount - 1));
const t = (i / (numPoints - 1)) * cycles % 1;
// Cubic ease in
const easeValue = t * t * t;
const value = minValue + (maxValue - minValue) * easeValue;
points.push([frame, value]);
}
return points;
},
easeOut: (frameCount, minValue, maxValue, cycles = 1) => {
const points = [];
const numPoints = Math.min(frameCount, 30);
for (let i = 0; i < numPoints; i++) {
const frame = Math.round((i / (numPoints - 1)) * (frameCount - 1));
const t = (i / (numPoints - 1)) * cycles % 1;
// Cubic ease out
const easeValue = 1 - Math.pow(1 - t, 3);
const value = minValue + (maxValue - minValue) * easeValue;
points.push([frame, value]);
}
return points;
},
// Bounce patterns
bounce: (frameCount, minValue, maxValue, cycles = 1) => {
const points = [];
const numPoints = Math.min(frameCount, 30);
for (let i = 0; i < numPoints; i++) {
const frame = Math.round((i / (numPoints - 1)) * (frameCount - 1));
const t = (i / (numPoints - 1)) * cycles % 1;
// Bounce calculation with bounds check
const bounceValue = Math.abs(Math.sin(t * Math.PI * 3) * (1 - t));
const value = minValue + (maxValue - minValue) * (1 - bounceValue);
points.push([frame, Math.max(minValue, Math.min(maxValue, value))]);
}
return points;
},
// Pulse patterns
pulse: (frameCount, minValue, maxValue, cycles = 1) => {
const points = [];
const numPoints = Math.min(frameCount, 30);
for (let i = 0; i < numPoints; i++) {
const frame = Math.round((i / (numPoints - 1)) * (frameCount - 1));
const t = (i / (numPoints - 1)) * cycles % 1;
// Gaussian pulse
const pulseValue = Math.exp(-Math.pow((Math.sin(t * Math.PI * 2) * 2), 2));
const value = minValue + (maxValue - minValue) * pulseValue;
points.push([frame, Math.max(minValue, Math.min(maxValue, value))]);
}
return points;
},
// Random patterns
random: (frameCount, minValue, maxValue) => {
const points = [];
const numPoints = Math.min(frameCount, 30);
for (let i = 0; i < numPoints; i++) {
const frame = Math.round((i / (numPoints - 1)) * (frameCount - 1));
const value = minValue + (maxValue - minValue) * Math.random();
points.push([frame, value]);
}
return points;
},
smoothRandom: (frameCount, minValue, maxValue) => {
const points = [];
const numPoints = Math.min(frameCount, 30);
let lastValue = minValue + (maxValue - minValue) * Math.random();
const maxStep = (maxValue - minValue) * 0.2; // Maximum change per step
for (let i = 0; i < numPoints; i++) {
const frame = Math.round((i / (numPoints - 1)) * (frameCount - 1));
const randomStep = (Math.random() * 2 - 1) * maxStep;
lastValue = Math.max(minValue, Math.min(maxValue, lastValue + randomStep));
points.push([frame, lastValue]);
}
return points;
},
// Special patterns
heartbeat: (frameCount, minValue, maxValue, cycles = 1) => {
const points = [];
const numPoints = Math.min(frameCount, 30);
for (let i = 0; i < numPoints; i++) {
const frame = Math.round((i / (numPoints - 1)) * (frameCount - 1));
const t = (i / (numPoints - 1)) * cycles % 1;
// Double-pulse heartbeat pattern with bounds check
const x = t * 4;
const heartbeatValue = Math.min(1, Math.max(0,
Math.pow(Math.E, -Math.pow(x % 1 * 6 - 1, 2)) * 0.8 +
Math.pow(Math.E, -Math.pow(x % 1 * 6 - 2, 2))
));
const value = minValue + (maxValue - minValue) * heartbeatValue;
points.push([frame, value]);
}
return points;
},
steps: (frameCount, minValue, maxValue, cycles = 1) => {
const points = [];
const numPoints = Math.min(frameCount, 30);
const numSteps = Math.max(2, Math.round(cycles * 5)); // 5 steps per cycle
for (let i = 0; i < numPoints; i++) {
const frame = Math.round((i / (numPoints - 1)) * (frameCount - 1));
const t = (i / (numPoints - 1)) * cycles % 1;
const step = Math.floor(t * numSteps) / (numSteps - 1);
const value = minValue + (maxValue - minValue) * Math.min(1, Math.max(0, step));
points.push([frame, value]);
}
return points;
}
};