Zum Inhalt

scripts/export-native-presets.ts

// scripts/export-native-presets.ts
//
// Exports every built-in preset to JSON for the native Swift/Metal engine.
//
// The web app's BUILT_IN_PRESETS is the single source of truth: its literals
// run through mk()/sp()/hex() and the attraction/repulsion → interactionMatrix
// fold, so the *final* numbers exist only after construction — they cannot be
// hand-copied faithfully. This script writes the fully-resolved presets out as
// JSON the native `Preset` Codable model reads verbatim, so web ⇄ native can
// never drift.
//
// Run: npx tsx scripts/export-native-presets.ts
//
// Targets (both written, since SwiftPM bundles resources from the target tree):
//   apple/presets/<id>.json                              — canonical set (+ README/thumbs)
//   apple/PhysarumEngine/Sources/PhysarumViewer/Presets/  — bundled into the viewer/.app
//
// `author`/`thumbnail` are dropped: thumbnail is a web asset path, author is
// metadata the native engine ignores — matching the existing native JSONs.
// Output is run through `biome format` so it matches the repo's canonical JSON
// style (short arrays inline, long ones wrapped) and the pre-commit hook is a
// no-op on the generated files.

import { execSync } from 'node:child_process';
import { copyFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { BUILT_IN_PRESETS } from '../src/presets/builtIn';

const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = resolve(__dirname, '..');
const TARGETS = [
  resolve(ROOT, 'apple/presets'),
  resolve(ROOT, 'apple/PhysarumEngine/Sources/PhysarumViewer/Presets'),
  resolve(ROOT, 'apple/PhysarumApp/Resources/Presets'),
];

for (const dir of TARGETS) mkdirSync(dir, { recursive: true });

let count = 0;
for (const preset of BUILT_IN_PRESETS) {
  // Explicit field selection — drop author/thumbnail, keep engine-relevant data.
  const out = {
    id: preset.id,
    name: preset.name,
    builtIn: preset.builtIn,
    tags: preset.tags,
    tagSignal: preset.tagSignal,
    params: preset.params,
  };
  const json = `${JSON.stringify(out, null, 2)}\n`;
  for (const dir of TARGETS) {
    writeFileSync(resolve(dir, `${preset.id}.json`), json, 'utf8');
  }
  count++;
}

// Copy thumbnails into the app bundle (Resources/Thumbnails), from the canonical
// apple/presets/thumbs set, so the macOS app's library grid has its images.
const thumbsSrc = resolve(ROOT, 'apple/presets/thumbs');
const thumbsDst = resolve(ROOT, 'apple/PhysarumApp/Resources/Thumbnails');
mkdirSync(thumbsDst, { recursive: true });
for (const preset of BUILT_IN_PRESETS) {
  const png = `${preset.id}.png`;
  const from = resolve(thumbsSrc, png);
  if (existsSync(from)) copyFileSync(from, resolve(thumbsDst, png));
}

// Canonicalise formatting via biome (the same formatter the pre-commit hook
// enforces), so short arrays collapse to one line and over-long ones wrap.
execSync(
  `npx @biomejs/biome format --write --files-ignore-unknown=true ${TARGETS.map((t) => `"${t}"`).join(' ')}`,
  {
    cwd: ROOT,
    stdio: 'inherit',
  },
);

console.log(`exported ${count} presets → ${TARGETS.length} targets`);
for (const dir of TARGETS) console.log(`  ${dir.replace(`${ROOT}/`, '')}`);