// 对比同一画板的新旧 JSON:尺寸、导出标记数、切图文件、标题区结构、字体、色板 import { readFileSync, readdirSync, existsSync } from 'node:fs' import { join } from 'node:path' const oldDir = '.scratch/lanhu-cockpit-asset-replacement/research/raw' const newDir = '.scratch/lanhu-cockpit-asset-replacement/research/raw-v2' function load(dir, file) { return JSON.parse(readFileSync(join(dir, file), 'utf8')) } function hex(c) { const h = (v) => Math.round(v * 255).toString(16).padStart(2, '0') return `#${h(c.r)}${h(c.g)}${h(c.b)}` } function summarize(j) { const ab = j.artboard let exportMarked = 0 let ddsMarked = 0 const exported = [] const fonts = new Map() const colors = new Map() const images = [] function walk(node, path) { const p = path ? `${path}/${node.name}` : node.name if (node.hasExportImage) { exportMarked += 1 exported.push({ path: p, w: Math.round(node.frame?.width ?? 0), h: Math.round(node.frame?.height ?? 0), type: node.type }) } if (node.hasExportDDSImage) ddsMarked += 1 if (node.type === 'textLayer' && node.text?.style?.font?.name) { const f = node.text.style.font const k = `${f.name}|${f.size}` fonts.set(k, (fonts.get(k) || 0) + 1) } for (const f of node.style?.fills || []) { if (f.type === 'color' && f.color) { const c = hex(f.color) colors.set(c, (colors.get(c) || 0) + 1) } const url = f.ddsImage?.imageUrl || f.image?.imageUrl || f.imageUrl if (f.type === 'image' && url) images.push({ p, url }) } for (const ch of node.layers || []) walk(ch, p) } for (const l of ab.layers || []) walk(l, '') const titleNode = (function find(n) { if (n.name === 'title') return n for (const c of n.layers || []) { const r = find(c) if (r) return r } return null })(ab) return { name: ab.name, frame: `${ab.frame.width}x${ab.frame.height}`, exportMarked, ddsMarked, assets: (j.assets || []).length, assetUrls: j.assets || [], fonts: fonts.size, fontList: [...fonts.keys()].slice(0, 8), colors: colors.size, topColors: [...colors.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8).map(([c]) => c), images: images.length, titleChildren: titleNode ? (titleNode.layers || []).map((c) => `${c.type}:${c.name} ${Math.round(c.frame?.width ?? 0)}x${Math.round(c.frame?.height ?? 0)}`) : [], exported: exported.slice(0, 40), } } const files = readdirSync(newDir).filter((f) => f.endsWith('.json')) for (const file of files) { const n = summarize(load(newDir, file)) console.log(`\n=== NEW ${n.name} ${n.frame} | exportMarked=${n.exportMarked} dds=${n.ddsMarked} assets=${n.assets} fonts=${n.fonts} colors=${n.colors} images=${n.images}`) console.log(' fonts:', n.fontList.join(', ')) console.log(' colors:', n.topColors.join(' ')) console.log(' title:', n.titleChildren.join(' | ')) console.log(' exported layers:') for (const e of n.exported) console.log(` ${e.w}x${e.h} ${e.type} | ${e.path}`) const oldPath = join(oldDir, file) if (existsSync(oldPath)) { const o = summarize(load(oldDir, file)) console.log(` --- OLD ${o.name} ${o.frame} | exportMarked=${o.exportMarked} dds=${o.ddsMarked} assets=${o.assets} fonts=${o.fonts} colors=${o.colors} images=${o.images}`) console.log(' --- OLD title:', o.titleChildren.join(' | ')) const oldSet = new Set(o.exported.map((e) => e.path)) const added = n.exported.filter((e) => !oldSet.has(e.path)) console.log(' --- NEW export layers (not in old):', added.length) for (const e of added) console.log(` + ${e.w}x${e.h} ${e.type} | ${e.path}`) } }