drawable feature templates
This commit is contained in:
+96
-35
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user