fix
This commit is contained in:
@@ -18,3 +18,4 @@ text_embed_cache/
|
||||
web/power_spline_editor/bg/ref_image_0.png
|
||||
web/power_spline_editor/ref/
|
||||
/web/power_spline_editor/bg
|
||||
utility/matanyone2/checkpoints/matanyone2.pth
|
||||
|
||||
+18
-20
@@ -8,7 +8,7 @@ import torch
|
||||
from PIL import Image
|
||||
|
||||
from ..utility.utility import pil2tensor, tensor2pil
|
||||
from ..config.constants import BOX_BASE_SIZE
|
||||
from ..config.constants import BOX_BASE_RADIUS
|
||||
|
||||
|
||||
class DrawImageOnPath:
|
||||
@@ -179,25 +179,24 @@ Draws an input image along a coordinate path for each frame, returning the rende
|
||||
|
||||
def _compute_target_size(self, base_w, base_h, scale_factor, frame_width, frame_height, use_box_scale_size, editor_scale=1.0):
|
||||
if use_box_scale_size:
|
||||
# Apply editor_scale to match what was shown in the Power Spline Editor canvas
|
||||
# The editor displays boxes at: BOX_BASE_RADIUS * scale * editor_scale
|
||||
# So the final size should be: BOX_BASE_SIZE * scale * editor_scale
|
||||
# Match the editor's formula: box_radius = BOX_BASE_RADIUS * pointScale * canvasScale
|
||||
# BOX_BASE_RADIUS = 200, so diameter = 400 * pointScale * canvasScale
|
||||
# We map this from canvas space to video space using editor_scale
|
||||
adjusted_scale = scale_factor * editor_scale
|
||||
target_short = BOX_BASE_SIZE * adjusted_scale
|
||||
ratio = target_short / max(1.0, min(base_w, base_h))
|
||||
new_w = int(round(base_w * ratio))
|
||||
new_h = int(round(base_h * ratio))
|
||||
box_diameter = (BOX_BASE_RADIUS * 2) * adjusted_scale
|
||||
# Calculate the scale factor to fit the image within the box diameter
|
||||
# This matches the editor's: scale = Math.min(boxSize / imgW, boxSize / imgH)
|
||||
scale_w = box_diameter / max(1.0, base_w)
|
||||
scale_h = box_diameter / max(1.0, base_h)
|
||||
fit_scale = min(scale_w, scale_h)
|
||||
new_w = int(round(base_w * fit_scale))
|
||||
new_h = int(round(base_h * fit_scale))
|
||||
else:
|
||||
new_w = int(round(base_w * scale_factor))
|
||||
new_h = int(round(base_h * scale_factor))
|
||||
|
||||
max_size_fraction = 1.0
|
||||
max_w = int(frame_width * max_size_fraction)
|
||||
max_h = int(frame_height * max_size_fraction)
|
||||
if new_w > max_w or new_h > max_h:
|
||||
clamp_ratio = min(max_w / max(1, new_w), max_h / max(1, new_h))
|
||||
new_w = max(1, int(new_w * clamp_ratio))
|
||||
new_h = max(1, int(new_h * clamp_ratio))
|
||||
# REMOVED: max_size_fraction clamp that was preventing scale variation
|
||||
# The clamp was forcing all large images to the same size, making scale changes invisible
|
||||
|
||||
new_w = max(1, new_w)
|
||||
new_h = max(1, new_h)
|
||||
@@ -380,7 +379,6 @@ Draws an input image along a coordinate path for each frame, returning the rende
|
||||
|
||||
pos_x, pos_y = self._scale_point(point, frame_width, frame_height, coord_width, coord_height)
|
||||
scale_factor = self._get_scale(point, fallback_scale)
|
||||
|
||||
new_w, new_h = self._compute_target_size(
|
||||
base_w, base_h, scale_factor, frame_width, frame_height, use_box_scale_size, editor_scale
|
||||
)
|
||||
@@ -439,9 +437,9 @@ Draws an input image along a coordinate path for each frame, returning the rende
|
||||
rotation_deg = -math.degrees(rotation_rad)
|
||||
|
||||
if abs(rotation_deg) > 1e-4:
|
||||
ref_img = ref_img.rotate(rotation_deg, expand=True)
|
||||
ref_img = ref_img.rotate(rotation_deg, resample=Image.Resampling.BICUBIC, expand=True)
|
||||
if mask_img is not None:
|
||||
mask_img = mask_img.rotate(rotation_deg, expand=True)
|
||||
mask_img = mask_img.rotate(rotation_deg, resample=Image.Resampling.BICUBIC, expand=True)
|
||||
|
||||
paste_x = int(round(pos_x - ref_img.width / 2))
|
||||
paste_y = int(round(pos_y - ref_img.height / 2))
|
||||
@@ -548,9 +546,9 @@ Draws an input image along a coordinate path for each frame, returning the rende
|
||||
rotation_deg = -math.degrees(rotation_rad)
|
||||
|
||||
if abs(rotation_deg) > 1e-4:
|
||||
ref_img = ref_img.rotate(rotation_deg, expand=True)
|
||||
ref_img = ref_img.rotate(rotation_deg, resample=Image.Resampling.BICUBIC, expand=True)
|
||||
if mask_img is not None:
|
||||
mask_img = mask_img.rotate(rotation_deg, expand=True)
|
||||
mask_img = mask_img.rotate(rotation_deg, resample=Image.Resampling.BICUBIC, expand=True)
|
||||
|
||||
paste_x = int(round(pos_x - ref_img.width / 2))
|
||||
paste_y = int(round(pos_y - ref_img.height / 2))
|
||||
|
||||
@@ -27,8 +27,8 @@ class PowerSplineEditor:
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "STRING", "INT",)
|
||||
RETURN_NAMES = ("bg_image", "coord_out", "frames",)
|
||||
RETURN_TYPES = ("IMAGE", "STRING", "INT", "STRING",)
|
||||
RETURN_NAMES = ("bg_image", "coord_out", "frames", "debug_scale",)
|
||||
FUNCTION = "splinedata"
|
||||
CATEGORY = "WanVideoWrapper_QQ"
|
||||
DESCRIPTION = """WIP"""
|
||||
@@ -1038,7 +1038,28 @@ class PowerSplineEditor:
|
||||
# Create blank image tensor in BHWC format (Batch, Height, Width, Channels)
|
||||
result_image = torch.zeros((1, int(bg_h), int(bg_w), 3), dtype=torch.float32)
|
||||
|
||||
result = (result_image, coord_out, frames)
|
||||
# Create debug_scale output to show keyframe scale values
|
||||
debug_scale_list = []
|
||||
if all_coord_paths:
|
||||
# Extract scale values from coordinates for debugging
|
||||
for coord_path in all_coord_paths:
|
||||
if isinstance(coord_path, list) and len(coord_path) > 0:
|
||||
frame_scales = []
|
||||
for point in coord_path:
|
||||
if isinstance(point, dict):
|
||||
frame_num = point.get('frame', '?')
|
||||
scale_val = point.get('scale', point.get('boxScale', point.get('pointScale', 1.0)))
|
||||
frame_scales.append(f"F{frame_num}:{scale_val:.4f}")
|
||||
if frame_scales:
|
||||
# Show all frames, but limit display to first 20 frames for readability
|
||||
display_scales = frame_scales[:20] if len(frame_scales) > 20 else frame_scales
|
||||
if len(frame_scales) > 20:
|
||||
display_scales.append(f"... (+{len(frame_scales) - 20} more)")
|
||||
debug_scale_list.append(", ".join(display_scales))
|
||||
|
||||
debug_scale = " | ".join(debug_scale_list) if debug_scale_list else "No scale data found"
|
||||
|
||||
result = (result_image, coord_out, frames, debug_scale)
|
||||
|
||||
# Add video metadata to ui_out if video was created
|
||||
if video_metadata:
|
||||
|
||||
@@ -59,21 +59,14 @@ def get_info_file(file_path: str, model_type: str, force=False):
|
||||
# Create a mirrored folder structure in loras/_power_preview
|
||||
power_preview_dir = os.path.join(base_dir, '_power_preview')
|
||||
|
||||
relative_path = os.path.relpath(file_path, base_dir)
|
||||
# Get the filename without extension and change to .json
|
||||
filename_without_ext = os.path.splitext(os.path.basename(file_path))[0]
|
||||
filename_without_ext = os.path.splitext(os.path.basename(relative_path))[0]
|
||||
relative_dir = os.path.dirname(relative_path)
|
||||
|
||||
# Handle cross-drive paths (Windows) - os.path.relpath fails across different drives
|
||||
try:
|
||||
relative_path = os.path.relpath(file_path, base_dir)
|
||||
relative_dir = os.path.dirname(relative_path)
|
||||
if relative_dir:
|
||||
info_path = os.path.join(power_preview_dir, relative_dir, f'{filename_without_ext}.json')
|
||||
else:
|
||||
info_path = os.path.join(power_preview_dir, f'{filename_without_ext}.json')
|
||||
except ValueError:
|
||||
# Cross-drive path on Windows - use the file's own directory for _power_preview
|
||||
file_dir = os.path.dirname(file_path)
|
||||
power_preview_dir = os.path.join(file_dir, '_power_preview')
|
||||
if relative_dir:
|
||||
info_path = os.path.join(power_preview_dir, relative_dir, f'{filename_without_ext}.json')
|
||||
else:
|
||||
info_path = os.path.join(power_preview_dir, f'{filename_without_ext}.json')
|
||||
|
||||
# Create the directory if it doesn't exist
|
||||
|
||||
@@ -288,10 +288,6 @@ export function attachBoxTimelineHelpers(editor) {
|
||||
const currentBoxPositionNormalized = editor._computeBoxLayerPosition(activeWidget, frame); // This is the 0-1 normalized position
|
||||
const currentBoxPositionDenorm = editor.denormalizePoints([{ x: currentBoxPositionNormalized.x, y: currentBoxPositionNormalized.y }])[0];
|
||||
const currentBoxPositionCanvas = transformVideoToCanvasSpace(editor, currentBoxPositionDenorm.x, currentBoxPositionDenorm.y);
|
||||
|
||||
console.log(`[DEBUG] New Key Point Creation (S+Click):`);
|
||||
console.log(` Clicked Canvas Location: x=${clickCanvasCoords.x.toFixed(2)}, y=${clickCanvasCoords.y.toFixed(2)}`);
|
||||
console.log(` Created Box Center (Canvas): x=${currentBoxPositionCanvas.x.toFixed(2)}, y=${currentBoxPositionCanvas.y.toFixed(2)}`);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -307,6 +303,7 @@ export function attachBoxTimelineHelpers(editor) {
|
||||
if (!editor._isBoxLayerWidget(widget)) return false;
|
||||
const keys = editor._ensureBoxLayerData(widget) || [];
|
||||
const stored = editor._getBoxLayerStoredPoint(widget);
|
||||
|
||||
const normalized = (() => {
|
||||
// Stored point (from _getBoxLayerStoredPoint) is already normalized (0-1 range).
|
||||
// Always use this. No re-normalization needed here.
|
||||
@@ -319,6 +316,7 @@ export function attachBoxTimelineHelpers(editor) {
|
||||
rotation: (typeof stored?.rotation === 'number' && !Number.isNaN(stored.rotation)) ? stored.rotation : 0,
|
||||
};
|
||||
})();
|
||||
|
||||
const targetFrame = Math.max(1, Math.min(editor._getMaxFrames(), Math.round(frame || widget.value.box_timeline_point || 1)));
|
||||
const payload = {
|
||||
frame: targetFrame,
|
||||
|
||||
@@ -554,11 +554,13 @@ export class BoxLayerWidget extends RgthreeBaseWidget {
|
||||
|
||||
onScaleDec() {
|
||||
this.value.scale = Math.max(0.01, parseFloat((this.value.scale || 1.00) - 0.01).toFixed(2));
|
||||
this._updatePointsStoreWithCurrentScale();
|
||||
this.parent.setDirtyCanvas(true, true);
|
||||
return true;
|
||||
}
|
||||
onScaleInc() {
|
||||
this.value.scale = Math.min(8.00, parseFloat((this.value.scale || 1.00) + 0.01).toFixed(2));
|
||||
this._updatePointsStoreWithCurrentScale();
|
||||
this.parent.setDirtyCanvas(true, true);
|
||||
return true;
|
||||
}
|
||||
@@ -566,6 +568,7 @@ export class BoxLayerWidget extends RgthreeBaseWidget {
|
||||
const canvas = app.canvas;
|
||||
canvas.prompt('Scale', this.value.scale || 1.00, (v) => {
|
||||
this.value.scale = Math.max(0.01, Math.min(8.00, parseFloat(Number(v).toFixed(2))));
|
||||
this._updatePointsStoreWithCurrentScale();
|
||||
this.parent.setDirtyCanvas(true, true);
|
||||
});
|
||||
return true;
|
||||
@@ -574,10 +577,36 @@ export class BoxLayerWidget extends RgthreeBaseWidget {
|
||||
if (event.deltaX) {
|
||||
this.haveMouseMovedValue = true;
|
||||
this.value.scale = Math.max(0.01, Math.min(8.00, parseFloat(((this.value.scale || 1.00) + (event.deltaX / 1000)).toFixed(2))));
|
||||
this._updatePointsStoreWithCurrentScale();
|
||||
node.setDirtyCanvas(true, true);
|
||||
}
|
||||
}
|
||||
|
||||
_updatePointsStoreWithCurrentScale() {
|
||||
// Update points_store with the current scale value so keyframes use the latest scale
|
||||
try {
|
||||
let points = [];
|
||||
try {
|
||||
points = JSON.parse(this.value.points_store || '[]');
|
||||
} catch {
|
||||
points = [];
|
||||
}
|
||||
|
||||
if (points && points.length > 0) {
|
||||
// Update all scale fields in points_store with current scale value
|
||||
const currentScale = parseFloat(this.value.scale || 1.00);
|
||||
points.forEach(point => {
|
||||
point.scale = currentScale;
|
||||
point.boxScale = currentScale;
|
||||
point.pointScale = currentScale;
|
||||
});
|
||||
this.value.points_store = JSON.stringify(points);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to update points_store with current scale:', e);
|
||||
}
|
||||
}
|
||||
|
||||
onKeyClick(event, pos, node) {
|
||||
this._triggerButtonPress('key', node);
|
||||
this._activateLayer(node);
|
||||
|
||||
@@ -791,8 +791,6 @@ app.registerExtension({
|
||||
let coord_in = message['coord_in'];
|
||||
let ref_image_dims = message['ref_image_dims'];
|
||||
|
||||
console.log('[PowerSplineEditor onExecuted] Received message:', message);
|
||||
|
||||
// ============================================================
|
||||
// == HANDLE VIDEO
|
||||
// ============================================================
|
||||
@@ -881,6 +879,37 @@ app.registerExtension({
|
||||
this.editor.drawPreviousSpline(coord_in_str);
|
||||
}
|
||||
|
||||
// ✅ Update box widgets with ref_image if available
|
||||
if (ref_image && this.editor?.refImageManager) {
|
||||
const dataUrl = ref_image.startsWith('data:') ? ref_image : `data:image/png;base64,${ref_image}`;
|
||||
const dims = await new Promise(resolve => {
|
||||
const img = new Image();
|
||||
img.onload = () => resolve({ width: img.width, height: img.height });
|
||||
img.onerror = () => resolve({ width: 1, height: 1 });
|
||||
img.src = dataUrl;
|
||||
});
|
||||
|
||||
const attachment = {
|
||||
base64: ref_image.startsWith('data:') ? ref_image.split(',')[1] : ref_image,
|
||||
type: 'image/png',
|
||||
width: dims.width,
|
||||
height: dims.height
|
||||
};
|
||||
|
||||
// Update all box widgets with the new ref_attachment
|
||||
const boxWidgets = this.layerManager?.getSplineWidgets?.().filter(w => w?.value?.type === 'box_layer') || [];
|
||||
boxWidgets.forEach(widget => {
|
||||
widget.value.ref_attachment = { entries: [attachment] };
|
||||
// Set selection to ref_1 if it's currently no_ref
|
||||
if (!widget.value.ref_selection || widget.value.ref_selection === 'no_ref') {
|
||||
widget.value.ref_selection = 'ref_1';
|
||||
}
|
||||
});
|
||||
|
||||
// Clear cache and render
|
||||
this.editor.layerRenderer?.clearRefImageCache?.();
|
||||
}
|
||||
|
||||
// ✅ WAIT for video scale to be set before loading coordinates
|
||||
if (message.bg_video?.length > 0 && this.editor?.videoMetadata) {
|
||||
console.log('[PowerSplineEditor] Video detected, waiting for scale to be set...');
|
||||
@@ -913,6 +942,11 @@ app.registerExtension({
|
||||
} else if (this.editor) {
|
||||
this.editor?.vis?.render();
|
||||
}
|
||||
|
||||
// ✅ Force render layer renderer to show ref images
|
||||
if (this.editor?.layerRenderer) {
|
||||
this.editor.layerRenderer.render();
|
||||
}
|
||||
};
|
||||
|
||||
// Call the async function
|
||||
|
||||
Reference in New Issue
Block a user