[object Object]

← back to Wild Wallcoverings

auto-save: 2026-07-22T18:15:55 (5 files) — data/designs.json output/designs/desert-choir-v1-nano.png output/designs/desert-choir-v5-nano.png output/designs/desert-choir-v6-nano.png scripts/make_seamless.py

a84333caf9fd42041368f9cbe2e98416c382fa0c · 2026-07-22 18:15:59 -0700 · Steve Abrams

Files touched

Diff

commit a84333caf9fd42041368f9cbe2e98416c382fa0c
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Jul 22 18:15:59 2026 -0700

    auto-save: 2026-07-22T18:15:55 (5 files) — data/designs.json output/designs/desert-choir-v1-nano.png output/designs/desert-choir-v5-nano.png output/designs/desert-choir-v6-nano.png scripts/make_seamless.py
---
 data/designs.json                       |  45 +-----------
 output/designs/desert-choir-v1-nano.png | Bin 5339 -> 89839 bytes
 output/designs/desert-choir-v5-nano.png | Bin 5408 -> 90286 bytes
 output/designs/desert-choir-v6-nano.png | Bin 6604 -> 48127 bytes
 scripts/make_seamless.py                | 118 ++++++++++++++++++++++++++++++++
 5 files changed, 121 insertions(+), 42 deletions(-)

diff --git a/data/designs.json b/data/designs.json
index dd989c3..c714396 100644
--- a/data/designs.json
+++ b/data/designs.json
@@ -708,20 +708,7 @@
    {
     "file": "desert-choir-v1-nano.png",
     "engine": "nano-banana",
-    "created_at": "2026-07-23T00:44:06.344407+00:00",
-    "copycheck": "CLEAN",
-    "settlement": "OK",
-    "settlement_detail": {
-     "a1_directional_foliage": false,
-     "a2_open_space": false,
-     "a3_multi_color_leaves": false,
-     "b_prohibited": false,
-     "b_matched": "",
-     "acceptable": false
-    },
-    "tile": {
-     "verdict": "PASS"
-    }
+    "created_at": "2026-07-23T00:44:06.344407+00:00"
    },
    {
     "file": "desert-choir-v4-nano.png",
@@ -744,38 +731,12 @@
    {
     "file": "desert-choir-v5-nano.png",
     "engine": "nano-banana",
-    "created_at": "2026-07-23T00:44:06.349626+00:00",
-    "copycheck": "CLEAN",
-    "settlement": "OK",
-    "settlement_detail": {
-     "a1_directional_foliage": false,
-     "a2_open_space": false,
-     "a3_multi_color_leaves": false,
-     "b_prohibited": false,
-     "b_matched": "",
-     "acceptable": false
-    },
-    "tile": {
-     "verdict": "PASS"
-    }
+    "created_at": "2026-07-23T00:44:06.349626+00:00"
    },
    {
     "file": "desert-choir-v6-nano.png",
     "engine": "nano-banana",
-    "created_at": "2026-07-23T00:44:06.352173+00:00",
-    "copycheck": "CLEAN",
-    "settlement": "OK",
-    "settlement_detail": {
-     "a1_directional_foliage": false,
-     "a2_open_space": false,
-     "a3_multi_color_leaves": false,
-     "b_prohibited": false,
-     "b_matched": "empty",
-     "acceptable": false
-    },
-    "tile": {
-     "verdict": "PASS"
-    }
+    "created_at": "2026-07-23T00:44:06.352173+00:00"
    }
   ],
   "reference": {
diff --git a/output/designs/desert-choir-v1-nano.png b/output/designs/desert-choir-v1-nano.png
index 0f1cfd6..bbca66e 100644
Binary files a/output/designs/desert-choir-v1-nano.png and b/output/designs/desert-choir-v1-nano.png differ
diff --git a/output/designs/desert-choir-v5-nano.png b/output/designs/desert-choir-v5-nano.png
index 0e45d0c..22ad043 100644
Binary files a/output/designs/desert-choir-v5-nano.png and b/output/designs/desert-choir-v5-nano.png differ
diff --git a/output/designs/desert-choir-v6-nano.png b/output/designs/desert-choir-v6-nano.png
index 8a731c0..a93667e 100644
Binary files a/output/designs/desert-choir-v6-nano.png and b/output/designs/desert-choir-v6-nano.png differ
diff --git a/scripts/make_seamless.py b/scripts/make_seamless.py
new file mode 100644
index 0000000..cdff34d
--- /dev/null
+++ b/scripts/make_seamless.py
@@ -0,0 +1,118 @@
+#!/usr/bin/env python3
+"""Make a render seamless via SDXL img2img with CIRCULAR padding on Mac1.
+
+The SeamlessTile node patches the UNet convs to circular padding and
+CircularVAEDecode does the same for the VAE, so the regenerated image is
+mathematically continuous across all four edges while low denoise preserves
+the original design. Then palette-snap + edges-agent verify; escalate denoise
+once if still failing. $0 local.
+
+Usage: make_seamless.py <file.png> <design_id> [denoise]
+       make_seamless.py --sweep    (all non-PASS variants in designs.json)
+"""
+import json, os, subprocess, sys, time, urllib.request, urllib.parse, uuid
+
+ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+HOST = "http://192.168.1.133:8188"
+SCAN = os.path.expanduser("~/.claude/skills/edges-agent/scripts/scan.py")
+NEG = ("photograph, 3d render, watermark, text, frame, border, banana, grapes, "
+       "bird, butterfly, blurry, low quality")
+
+def upload(path):
+    import base64, secrets
+    boundary = "----wild" + secrets.token_hex(8)
+    name = os.path.basename(path)
+    data = open(path, "rb").read()
+    body = (f"--{boundary}\r\nContent-Disposition: form-data; name=\"image\"; "
+            f"filename=\"{name}\"\r\nContent-Type: image/png\r\n\r\n").encode() + data + \
+           f"\r\n--{boundary}\r\nContent-Disposition: form-data; name=\"overwrite\"\r\n\r\ntrue\r\n--{boundary}--\r\n".encode()
+    req = urllib.request.Request(HOST + "/upload/image", data=body,
+                                 headers={"Content-Type": f"multipart/form-data; boundary={boundary}"})
+    return json.load(urllib.request.urlopen(req, timeout=60))["name"]
+
+def workflow(img_name, prompt, denoise, seed):
+    return {
+        "1": {"class_type": "CheckpointLoaderSimple",
+              "inputs": {"ckpt_name": "sd_xl_base_1.0.safetensors"}},
+        "2": {"class_type": "SeamlessTile",
+              "inputs": {"model": ["1", 0], "tiling": "enable", "copy_model": "Make a copy"}},
+        "3": {"class_type": "LoadImage", "inputs": {"image": img_name}},
+        "4": {"class_type": "VAEEncode", "inputs": {"pixels": ["3", 0], "vae": ["1", 2]}},
+        "5": {"class_type": "CLIPTextEncode", "inputs": {"text": prompt, "clip": ["1", 1]}},
+        "6": {"class_type": "CLIPTextEncode", "inputs": {"text": NEG, "clip": ["1", 1]}},
+        "7": {"class_type": "KSampler",
+              "inputs": {"seed": seed, "steps": 20, "cfg": 7.0,
+                         "sampler_name": "dpmpp_2m", "scheduler": "karras",
+                         "denoise": denoise, "model": ["2", 0],
+                         "positive": ["5", 0], "negative": ["6", 0],
+                         "latent_image": ["4", 0]}},
+        "8": {"class_type": "CircularVAEDecode",
+              "inputs": {"samples": ["7", 0], "vae": ["1", 2], "tiling": "enable"}},
+        "9": {"class_type": "SaveImage",
+              "inputs": {"filename_prefix": "wildseam", "images": ["8", 0]}},
+    }
+
+def run_job(path, prompt, denoise, seed=42):
+    img_name = upload(path)
+    body = {"prompt": workflow(img_name, prompt, denoise, seed), "client_id": str(uuid.uuid4())}
+    req = urllib.request.Request(HOST + "/prompt", data=json.dumps(body).encode(),
+                                 headers={"Content-Type": "application/json"})
+    pid = json.load(urllib.request.urlopen(req, timeout=30))["prompt_id"]
+    deadline = time.time() + 600
+    while time.time() < deadline:
+        time.sleep(4)
+        hist = json.load(urllib.request.urlopen(f"{HOST}/history/{pid}", timeout=30))
+        if pid in hist:
+            if hist[pid].get("status", {}).get("status_str") == "error":
+                raise RuntimeError("comfy error")
+            for node in hist[pid].get("outputs", {}).values():
+                for img in node.get("images", []):
+                    url = (f"{HOST}/view?filename={urllib.parse.quote(img['filename'])}"
+                           f"&subfolder={urllib.parse.quote(img.get('subfolder',''))}&type={img.get('type','output')}")
+                    with urllib.request.urlopen(url, timeout=60) as r, open(path, "wb") as f:
+                        f.write(r.read())
+                    return
+    raise RuntimeError("timeout")
+
+def scan_verdict(path):
+    r = subprocess.run([sys.executable, SCAN, "--path", path, "--json"],
+                       capture_output=True, text=True, timeout=60)
+    try:
+        return json.loads(r.stdout).get("verdict")
+    except Exception:
+        return "ERROR"
+
+def heal(fname, did, prompt, denoise=0.35):
+    path = os.path.join(ROOT, "output", "designs", fname)
+    for d in (denoise, 0.5):
+        run_job(path, prompt, d)
+        subprocess.run([sys.executable, os.path.join(ROOT, "scripts", "snap_palette.py"),
+                        path, did], capture_output=True)
+        v = scan_verdict(path)
+        print(f"{fname} denoise={d} -> {v}", flush=True)
+        if v == "PASS":
+            return "PASS"
+    return v
+
+def main():
+    briefs = {b["id"]: b for b in json.load(open(os.path.join(ROOT, "data", "briefs.json")))}
+    if sys.argv[1] == "--sweep":
+        d = json.load(open(os.path.join(ROOT, "data", "designs.json")))
+        todo = [(x["id"], v) for x in d for v in x["variants"]
+                if v.get("tile", {}).get("verdict") not in ("PASS",)]
+        print(f"{len(todo)} files to heal", flush=True)
+        for did, v in todo:
+            try:
+                verdict = heal(v["file"], did, briefs[did]["prompt"])
+                v["tile"] = {"verdict": verdict, "healed": True}
+            except Exception as e:
+                print(v["file"], "ERROR", e, flush=True)
+                v["tile"] = {"verdict": "ERROR"}
+            json.dump(d, open(os.path.join(ROOT, "data", "designs.json"), "w"), indent=1)
+    else:
+        fname, did = sys.argv[1], sys.argv[2]
+        heal(fname, did, briefs[did]["prompt"],
+             float(sys.argv[3]) if len(sys.argv) > 3 else 0.35)
+
+if __name__ == "__main__":
+    main()

← 33fa61f all 159 clean: copy-check, settlement, tile scans complete;  ·  back to Wild Wallcoverings  ·  seamless-heal pipeline: circular-padding img2img on Mac1, ed b073496 →