Keep accumulator previews stable when reordered
This commit is contained in:
+16
-1
@@ -54,6 +54,14 @@ INDEX_SWITCH_MISSING_BEHAVIORS = ["fallback", "none", "clamp", "wrap"]
|
|||||||
_ACCUMULATOR_STORES: dict[str, list[dict[str, Any]]] = {}
|
_ACCUMULATOR_STORES: dict[str, list[dict[str, Any]]] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def _entry_preview_key(entry: dict[str, Any]) -> str:
|
||||||
|
key = str(entry.get("_sxcp_preview_key") or "").strip()
|
||||||
|
if not key:
|
||||||
|
key = f"entry_{random.getrandbits(64):016x}"
|
||||||
|
entry["_sxcp_preview_key"] = key
|
||||||
|
return key
|
||||||
|
|
||||||
|
|
||||||
class AnyType(str):
|
class AnyType(str):
|
||||||
def __ne__(self, _other: object) -> bool:
|
def __ne__(self, _other: object) -> bool:
|
||||||
return False
|
return False
|
||||||
@@ -218,6 +226,7 @@ def _entry_infos(store: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|||||||
{
|
{
|
||||||
"index": index,
|
"index": index,
|
||||||
"id": str(entry.get("id") or ""),
|
"id": str(entry.get("id") or ""),
|
||||||
|
"preview_key": _entry_preview_key(entry),
|
||||||
"has_image": image is not None,
|
"has_image": image is not None,
|
||||||
"has_metadata": has_metadata,
|
"has_metadata": has_metadata,
|
||||||
"shape": list(shape) if shape is not None else [],
|
"shape": list(shape) if shape is not None else [],
|
||||||
@@ -424,7 +433,13 @@ def _preview_image_results(
|
|||||||
metadata = _metadata_for_entry(entry, prompt, extra_pnginfo)
|
metadata = _metadata_for_entry(entry, prompt, extra_pnginfo)
|
||||||
file = f"{filename}_{counter:05}_.png"
|
file = f"{filename}_{counter:05}_.png"
|
||||||
_image_to_pil(image).save(os.path.join(full_output_folder, file), pnginfo=metadata, compress_level=1)
|
_image_to_pil(image).save(os.path.join(full_output_folder, file), pnginfo=metadata, compress_level=1)
|
||||||
results.append({"filename": file, "subfolder": subfolder, "type": "temp"})
|
results.append({
|
||||||
|
"filename": file,
|
||||||
|
"subfolder": subfolder,
|
||||||
|
"type": "temp",
|
||||||
|
"preview_key": _entry_preview_key(entry),
|
||||||
|
"entry_id": str(entry.get("id") or ""),
|
||||||
|
})
|
||||||
counter += 1
|
counter += 1
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,40 @@ function imageEntries(node) {
|
|||||||
return entries.filter((entry) => entry?.has_image).slice(0, previewLimit(node));
|
return entries.filter((entry) => entry?.has_image).slice(0, previewLimit(node));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function entryKey(entry) {
|
||||||
|
const key = String(entry?.preview_key || "").trim();
|
||||||
|
if (key) return `key:${key}`;
|
||||||
|
const id = String(entry?.id || "").trim();
|
||||||
|
if (id) return `id:${id}`;
|
||||||
|
return `index:${entry?.index ?? ""}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildImageMap(entries, images, previous = new Map()) {
|
||||||
|
const next = new Map(previous);
|
||||||
|
const imageEntries = asArray(entries).filter((entry) => entry?.has_image);
|
||||||
|
asArray(images).filter(Boolean).forEach((image, index) => {
|
||||||
|
const directKey = String(image?.preview_key || "").trim();
|
||||||
|
if (directKey) {
|
||||||
|
next.set(`key:${directKey}`, image);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const entryId = String(image?.entry_id || "").trim();
|
||||||
|
if (entryId) {
|
||||||
|
next.set(`id:${entryId}`, image);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const entry = imageEntries[index];
|
||||||
|
if (entry) next.set(entryKey(entry), image);
|
||||||
|
});
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
function imageParamsForEntry(node, entry, fallbackIndex) {
|
||||||
|
const keyed = node._sxapImageByKey?.get(entryKey(entry));
|
||||||
|
if (keyed) return keyed;
|
||||||
|
return (node._sxapImages || [])[fallbackIndex];
|
||||||
|
}
|
||||||
|
|
||||||
function imageUrl(params) {
|
function imageUrl(params) {
|
||||||
const query = new URLSearchParams(params).toString();
|
const query = new URLSearchParams(params).toString();
|
||||||
const preview = app.getPreviewFormatParam?.() || "";
|
const preview = app.getPreviewFormatParam?.() || "";
|
||||||
@@ -322,7 +356,6 @@ function renderGrid(node) {
|
|||||||
const grid = node._sxapGridEl;
|
const grid = node._sxapGridEl;
|
||||||
if (!grid) return;
|
if (!grid) return;
|
||||||
const entries = imageEntries(node);
|
const entries = imageEntries(node);
|
||||||
const images = node._sxapImages || [];
|
|
||||||
grid.replaceChildren();
|
grid.replaceChildren();
|
||||||
|
|
||||||
if (!entries.length) {
|
if (!entries.length) {
|
||||||
@@ -332,7 +365,7 @@ function renderGrid(node) {
|
|||||||
grid.appendChild(empty);
|
grid.appendChild(empty);
|
||||||
} else {
|
} else {
|
||||||
entries.forEach((entry, index) => {
|
entries.forEach((entry, index) => {
|
||||||
grid.appendChild(renderCell(node, entry, images[index], index));
|
grid.appendChild(renderCell(node, entry, imageParamsForEntry(node, entry, index), index));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,6 +397,7 @@ function applyData(node, data, status = "") {
|
|||||||
if (Object.prototype.hasOwnProperty.call(data || {}, "images")) {
|
if (Object.prototype.hasOwnProperty.call(data || {}, "images")) {
|
||||||
node._sxapImages = asArray(data.images).filter(Boolean);
|
node._sxapImages = asArray(data.images).filter(Boolean);
|
||||||
}
|
}
|
||||||
|
node._sxapImageByKey = buildImageMap(node._sxapEntries, node._sxapImages, node._sxapImageByKey);
|
||||||
setStatus(node, status || data?.status || "");
|
setStatus(node, status || data?.status || "");
|
||||||
renderGrid(node);
|
renderGrid(node);
|
||||||
}
|
}
|
||||||
@@ -532,6 +566,7 @@ function setupNode(node) {
|
|||||||
node._sxapStatusEl = status;
|
node._sxapStatusEl = status;
|
||||||
node._sxapEntries = node._sxapEntries || [];
|
node._sxapEntries = node._sxapEntries || [];
|
||||||
node._sxapImages = node._sxapImages || [];
|
node._sxapImages = node._sxapImages || [];
|
||||||
|
node._sxapImageByKey = node._sxapImageByKey || new Map();
|
||||||
node._sxapLastCount = -1;
|
node._sxapLastCount = -1;
|
||||||
recomputeSize(node);
|
recomputeSize(node);
|
||||||
node._sxapWidget = node.addDOMWidget("accumulator_grid", "div", wrap, {
|
node._sxapWidget = node.addDOMWidget("accumulator_grid", "div", wrap, {
|
||||||
|
|||||||
Reference in New Issue
Block a user