← 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
M data/designs.jsonM output/designs/desert-choir-v1-nano.pngM output/designs/desert-choir-v5-nano.pngM output/designs/desert-choir-v6-nano.pngA scripts/make_seamless.py
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 →