Init atlas-baker tool with source-dir scan and pair-detection
Creates the sporel-tool-atlas-baker Node CLI scaffold and the first phase of the bake pipeline: scan a source directory for *_diffuse.png files, pair them with optional *_height.png companions, and return alphabetically-sorted sources for deterministic re-bakes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
67
src/scan.js
Normal file
67
src/scan.js
Normal file
@@ -0,0 +1,67 @@
|
||||
// src/scan.js
|
||||
// Scans a source directory for *_diffuse.{png,webp,jpg,jpeg} files and
|
||||
// pairs them with optional *_height.{png,webp,jpg,jpeg} companions.
|
||||
//
|
||||
// Returns: { sources: Array<{name, diffusePath, heightPath?}>, errors: Array<string> }
|
||||
// `name` = filename without "_diffuse.<ext>" suffix.
|
||||
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const SUPPORTED_EXT = ['.png', '.webp', '.jpg', '.jpeg'];
|
||||
|
||||
function stripSuffix(filename, suffix) {
|
||||
for (const ext of SUPPORTED_EXT) {
|
||||
const full = `${suffix}${ext}`;
|
||||
if (filename.toLowerCase().endsWith(full)) {
|
||||
return filename.slice(0, -full.length);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function scanSourceDir(srcDir) {
|
||||
if (!fs.existsSync(srcDir)) {
|
||||
return { sources: [], errors: [`scan: source dir does not exist: ${srcDir}`] };
|
||||
}
|
||||
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
|
||||
const diffuseMap = new Map(); // name -> path
|
||||
const heightMap = new Map(); // name -> path
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.isFile()) continue;
|
||||
const fn = entry.name;
|
||||
const dName = stripSuffix(fn, '_diffuse');
|
||||
if (dName !== null) {
|
||||
diffuseMap.set(dName, path.join(srcDir, fn));
|
||||
continue;
|
||||
}
|
||||
const hName = stripSuffix(fn, '_height');
|
||||
if (hName !== null) {
|
||||
heightMap.set(hName, path.join(srcDir, fn));
|
||||
}
|
||||
}
|
||||
|
||||
const errors = [];
|
||||
for (const hName of heightMap.keys()) {
|
||||
if (!diffuseMap.has(hName)) {
|
||||
errors.push(`scan: height without paired diffuse: ${hName}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort alphabetically by name for determinism
|
||||
const names = [...diffuseMap.keys()].sort((a, b) => a.localeCompare(b));
|
||||
const sources = names.map(name => ({
|
||||
name,
|
||||
diffusePath: diffuseMap.get(name),
|
||||
heightPath: heightMap.get(name) || null,
|
||||
}));
|
||||
|
||||
if (sources.length === 0) {
|
||||
errors.push(`scan: no *_diffuse.{png,webp,jpg,jpeg} found in: ${srcDir}`);
|
||||
}
|
||||
|
||||
return { sources, errors };
|
||||
}
|
||||
|
||||
module.exports = { scanSourceDir };
|
||||
Reference in New Issue
Block a user