← back to Designer Wallcoverings
Add REGEN mode to room generator: replace our 557 rooms at corrected scale
144635ea45518444dddc5482a9b426f25ef445c3 · 2026-06-23 13:20:52 -0700 · Steve
REGEN=1 re-does ONLY the rooms we generated (gated on _done.ledger + a
'Room Setting' alt/url match) so the ~35 original-cohort _interior1 vendor
photos are never touched. Per product: delete the old wrong-scale room image,
regenerate at the repeat-driven scale (reads custom.pattern_repeat), upload the
replacement. Separate _regen.ledger for resume. Dry-run verified.
Files touched
M shopify/scripts/tres-tintas-room-settings.js
Diff
commit 144635ea45518444dddc5482a9b426f25ef445c3
Author: Steve <steve@designerwallcoverings.com>
Date: Tue Jun 23 13:20:52 2026 -0700
Add REGEN mode to room generator: replace our 557 rooms at corrected scale
REGEN=1 re-does ONLY the rooms we generated (gated on _done.ledger + a
'Room Setting' alt/url match) so the ~35 original-cohort _interior1 vendor
photos are never touched. Per product: delete the old wrong-scale room image,
regenerate at the repeat-driven scale (reads custom.pattern_repeat), upload the
replacement. Separate _regen.ledger for resume. Dry-run verified.
---
shopify/scripts/tres-tintas-room-settings.js | 40 +++++++++++++++++++++-------
1 file changed, 31 insertions(+), 9 deletions(-)
diff --git a/shopify/scripts/tres-tintas-room-settings.js b/shopify/scripts/tres-tintas-room-settings.js
index b610af32..3402aaaa 100644
--- a/shopify/scripts/tres-tintas-room-settings.js
+++ b/shopify/scripts/tres-tintas-room-settings.js
@@ -30,6 +30,9 @@ const { execFileSync } = require('child_process');
const STORE = 'designer-laboratory-sandbox.myshopify.com';
const API = '2024-01';
const APPLY = process.env.APPLY === '1';
+// REGEN=1: re-generate rooms for products that ALREADY have a room image, at the
+// corrected repeat-driven scale — deletes the old room image, uploads the new.
+const REGEN = process.env.REGEN === '1';
const LIMIT = process.env.LIMIT ? parseInt(process.env.LIMIT, 10) : Infinity;
const ROOM = process.env.ROOM || 'living_room';
const REPLICATE_VERSION = 'a5b13068cc81a89a4fbeefeccc774869fcb34df4dbc92c1555e0f2771d49dde7';
@@ -49,9 +52,12 @@ const H = { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json'
const TMP = '/tmp/tt-rooms';
fs.mkdirSync(TMP, { recursive: true });
const BACKUP_DIR = path.join(__dirname, '..', 'tres-tintas-room-backups');
-const LEDGER = path.join(BACKUP_DIR, '_done.ledger');
+const LEDGER = path.join(BACKUP_DIR, REGEN ? '_regen.ledger' : '_done.ledger');
fs.mkdirSync(BACKUP_DIR, { recursive: true });
const done = new Set(fs.existsSync(LEDGER) ? fs.readFileSync(LEDGER, 'utf8').split('\n').filter(Boolean) : []);
+// In REGEN we ONLY re-do rooms WE generated (the original _done.ledger) — never
+// the ~35 original-cohort products that carry real vendor _interior1 photos.
+const GENERATED = REGEN ? new Set((fs.existsSync(path.join(BACKUP_DIR, '_done.ledger')) ? fs.readFileSync(path.join(BACKUP_DIR, '_done.ledger'), 'utf8').split('\n').filter(Boolean) : [])) : null;
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
// Per-request timeout so a half-open socket can never wedge the batch (the
@@ -96,6 +102,10 @@ async function replicateInpaint(initPath, maskPath, outPath) {
throw new Error('replicate poll timeout');
}
+async function deleteImage(productId, imageId) {
+ const r = await fetchRetry(`https://${STORE}/admin/api/${API}/products/${productId}/images/${imageId}.json`, { method: 'DELETE', headers: H }, 3, 60000);
+ if (!r.ok && r.status !== 404) throw new Error(`delete image ${imageId}: HTTP ${r.status}`);
+}
async function uploadImage(productId, filePath, alt) {
const b64 = fs.readFileSync(filePath).toString('base64');
const r = await fetchRetry(`https://${STORE}/admin/api/${API}/products/${productId}/images.json`, {
@@ -107,11 +117,11 @@ async function uploadImage(productId, filePath, alt) {
async function main() {
if (!TOKEN || !RTOKEN) throw new Error('missing SHOPIFY_DRAFT_TOKEN or REPLICATE_API_TOKEN');
- console.log(`MODE: ${APPLY ? 'APPLY (live)' : 'DRY RUN'} room=${ROOM}${LIMIT !== Infinity ? ` LIMIT=${LIMIT}` : ''} est $${COST_PER_IMG}/img`);
+ console.log(`MODE: ${APPLY ? 'APPLY (live)' : 'DRY RUN'}${REGEN ? ' REGEN (replace existing rooms)' : ''} room=${ROOM}${LIMIT !== Infinity ? ` LIMIT=${LIMIT}` : ''} est $${COST_PER_IMG}/img`);
// first:100 (matches the desc/spec scripts that enumerate all 592 cleanly).
// first:60 hit a Shopify frozen-endCursor pagination bug -> only saw 120.
const q = `query($c:String){products(first:100,query:"vendor:'Tres Tintas'",after:$c){pageInfo{hasNextPage endCursor}
- edges{node{id title images(first:10){edges{node{url}}}} } }}`;
+ edges{node{id title images(first:10){edges{node{id url altText}}}} } }}`;
let cursor = null, scanned = 0, need = 0, processed = 0, ok = 0, errs = 0, spend = 0;
while (true) {
@@ -119,14 +129,25 @@ async function main() {
for (const e of conn.edges) {
const n = e.node; scanned++;
const id = n.id.split('/').pop();
- const imgs = n.images.edges.map(x => x.node.url);
- const hasRoom = imgs.some(u => /interior|_int|room|ambiente|setting/i.test(u));
- if (imgs.length > 1 || hasRoom) continue; // already has a 2nd/room image
+ const imgEdges = n.images.edges.map(x => x.node);
+ const imgs = imgEdges.map(x => x.url);
+ const roomImg = imgEdges.find(x => /interior|_int|room|ambiente|setting/i.test(x.url + (x.altText || '')));
+ const hasRoom = imgs.length > 1 || !!roomImg;
+
+ // REGEN: only the rooms WE generated (in _done.ledger) that have a "Room
+ // Setting"-tagged image — never the original-cohort _interior1 photos.
+ // NORMAL: only products that LACK a room image (add one).
+ if (REGEN) {
+ const ourRoom = roomImg && /room-setting|[-_]room[-_]/i.test((roomImg.altText || '') + roomImg.url);
+ if (!GENERATED.has(id) || !ourRoom) continue;
+ } else if (hasRoom) continue;
need++;
if (done.has(id)) continue;
if (processed >= LIMIT) continue;
- const patternUrl = imgs[0];
+ // pattern = the FIRST non-room image (the swatch)
+ const patternUrl = (imgEdges.find(x => x !== roomImg) || imgEdges[0] || {}).url;
+ const oldRoomId = roomImg ? roomImg.id.split('/').pop() : null;
if (!patternUrl) { console.log(` !! ${id} no pattern image — SKIP`); continue; }
try {
@@ -144,14 +165,15 @@ async function main() {
execFileSync(PY, [COMPOSITE_PY, patPath, initPath, maskPath, String(rep)], { stdio: 'pipe' });
processed++;
- if (!APPLY) { console.log(` [dry] ${id} ${n.title} repeat=${rep}in -> would inpaint + upload`); continue; }
+ if (!APPLY) { console.log(` [dry] ${id} ${n.title} repeat=${rep}in${REGEN ? ` (regen, del old ${oldRoomId})` : ''} -> would inpaint + upload`); continue; }
await replicateInpaint(initPath, maskPath, outPath);
spend += COST_PER_IMG;
+ if (REGEN && oldRoomId) await deleteImage(id, oldRoomId); // remove the old wrong-scale room
const imgId = await uploadImage(id, outPath, `Room Setting - ${n.title.replace(/\s*\|.*$/, '')}`);
ok++;
fs.appendFileSync(LEDGER, id + '\n'); done.add(id);
- console.log(` ✓ ${id} ${n.title} img=${imgId} [${ok} done · ~$${spend.toFixed(2)}]`);
+ console.log(` ✓ ${id} ${n.title} img=${imgId}${REGEN ? ` (replaced ${oldRoomId})` : ''} [${ok} done · ~$${spend.toFixed(2)}]`);
await sleep(300);
} catch (err) {
errs++;
← ffd94410 Graft native-grid infinite scroll onto LIVE theme (newwall-i
·
back to Designer Wallcoverings
·
hero manifest finalizer: socket-timeout hardening reuse + pr 8bb35f8f →