chore(skills): Projekt-Skill zum Platzieren von three-d-stage-Modellen
Ablauf wie beim Hood Yard mit den gefundenen Fallstricken (Dreiecke je Knoten, Bodenplatte +6 cm, globale Materiallisten, Asset-Test je Ort, launch.json nur im Hauptordner, gl.finish-Messung, Vorwärmen prüfen) und Werkzeugen: Modell vermessen, Kartendaten eines Weltfensters, Vergleichsbild Satellit/Spiel, Screenshot-Dekodierung, erprobte Browser-Ausschnitte. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7e4af1d0bc
commit
6a056262a8
6 files changed
+560
No files matched your search
@@ -0,0 +1,109 @@
|
||||
// Vermisst eine .glb aus three-d-stage (Quelle oder aufbereitete Datei) für die Platzierung:
|
||||
//
|
||||
// node .claude/skills/placing-editor-models/scripts/measure-model.mjs <modell.glb> [bericht.json]
|
||||
//
|
||||
// Gibt eine kurze Übersicht aus und schreibt optional das volle JSON, das render-site.py für die
|
||||
// Grundriss-Überlagerung liest. Dreiecke werden je Knoten gezählt: three-d-stage benutzt Meshes
|
||||
// mehrfach, die Summe über die Meshes allein unterschätzt, was gezeichnet wird (Hood Yard: 0,75 statt
|
||||
// 2,9 Mio.).
|
||||
import { createHash } from "node:crypto";
|
||||
import { open, stat, writeFile } from "node:fs/promises";
|
||||
import { NodeIO } from "@gltf-transform/core";
|
||||
import { ALL_EXTENSIONS } from "@gltf-transform/extensions";
|
||||
import { getBounds } from "@gltf-transform/functions";
|
||||
import { MeshoptDecoder } from "meshoptimizer";
|
||||
import { EXT_MATERIALS_BUMP, EXTMaterialsBump } from "../../../../tools/assets/bumpExtension.mjs";
|
||||
|
||||
const [source, reportPath] = process.argv.slice(2);
|
||||
if (!source) {
|
||||
console.error("Aufruf: node measure-model.mjs <modell.glb> [bericht.json]");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
/** asset.generator aus dem JSON-Block der .glb; NodeIO überschreibt ihn beim Einlesen. */
|
||||
async function readGenerator(path) {
|
||||
const file = await open(path, "r");
|
||||
try {
|
||||
const header = Buffer.alloc(20);
|
||||
await file.read(header, 0, 20, 0);
|
||||
const json = Buffer.alloc(header.readUInt32LE(12));
|
||||
await file.read(json, 0, json.length, 20);
|
||||
return JSON.parse(json.toString("utf8")).asset?.generator ?? "(unbekannt)";
|
||||
} finally {
|
||||
await file.close();
|
||||
}
|
||||
}
|
||||
|
||||
await MeshoptDecoder.ready;
|
||||
const io = new NodeIO()
|
||||
.registerExtensions([...ALL_EXTENSIONS, EXTMaterialsBump])
|
||||
.registerDependencies({ "meshopt.decoder": MeshoptDecoder });
|
||||
const document = await io.read(source);
|
||||
const root = document.getRoot();
|
||||
const round = (values) => values.map((value) => Number(value.toFixed(2)));
|
||||
|
||||
const materials = new Map();
|
||||
for (const node of root.listNodes()) {
|
||||
for (const primitive of node.getMesh()?.listPrimitives() ?? []) {
|
||||
const material = primitive.getMaterial();
|
||||
const name = material?.getName() ?? "(ohne Material)";
|
||||
const entry = materials.get(name) ?? {
|
||||
name, drawnTriangles: 0, primitives: 0,
|
||||
alphaMode: material?.getAlphaMode() ?? "OPAQUE",
|
||||
bump: Boolean(material?.getExtension(EXT_MATERIALS_BUMP)),
|
||||
};
|
||||
entry.drawnTriangles += (primitive.getIndices()?.getCount() ?? primitive.getAttribute("POSITION").getCount()) / 3;
|
||||
entry.primitives++;
|
||||
materials.set(name, entry);
|
||||
}
|
||||
}
|
||||
|
||||
const textureSizes = {};
|
||||
const hashes = new Set();
|
||||
let gpuBytes = 0;
|
||||
for (const texture of root.listTextures()) {
|
||||
const [width, height] = texture.getSize() ?? [0, 0];
|
||||
const key = `${width}x${height} ${texture.getMimeType()}`;
|
||||
textureSizes[key] = (textureSizes[key] ?? 0) + 1;
|
||||
hashes.add(createHash("md5").update(texture.getImage() ?? new Uint8Array()).digest("hex"));
|
||||
gpuBytes += (width * height * 4 * 4) / 3;
|
||||
}
|
||||
|
||||
// Baugruppen: die Kinder des obersten Knotens mit Unterbaum (bei three-d-stage z. B. ground, container_row,
|
||||
// fence). Ihre Außenmaße ergeben den Grundriss für die Überlagerung auf Karte und Satellitenbild.
|
||||
const scene = root.listScenes()[0];
|
||||
const top = scene.listChildren();
|
||||
const groupParents = top.length === 1 && top[0].listChildren().length > 0 ? top[0].listChildren() : top;
|
||||
const groups = groupParents
|
||||
.filter((node) => node.getMesh() || node.listChildren().length > 0)
|
||||
.map((node) => {
|
||||
const { min, max } = getBounds(node);
|
||||
return { name: node.getName() || "(unbenannt)", min: round(min), max: round(max) };
|
||||
});
|
||||
|
||||
const { min, max } = getBounds(scene);
|
||||
const report = {
|
||||
file: source,
|
||||
megabytes: Number(((await stat(source)).size / 1e6).toFixed(1)),
|
||||
generator: await readGenerator(source),
|
||||
extensions: root.listExtensionsUsed().map((extension) => extension.extensionName),
|
||||
rootTranslation: top.length === 1 ? round(top[0].getTranslation()) : null,
|
||||
bounds: { min: round(min), max: round(max) },
|
||||
textures: { count: root.listTextures().length, unique: hashes.size, sizes: textureSizes, gpuMB: Math.round(gpuBytes / 1e6) },
|
||||
drawnTriangles: [...materials.values()].reduce((sum, entry) => sum + entry.drawnTriangles, 0),
|
||||
materials: [...materials.values()].sort((a, b) => b.drawnTriangles - a.drawnTriangles),
|
||||
groups,
|
||||
};
|
||||
|
||||
console.log(`${report.file}: ${report.megabytes} MB, ${report.generator}`);
|
||||
console.log(`Erweiterungen: ${report.extensions.join(", ")}`);
|
||||
console.log(`Außenmaße: x ${report.bounds.min[0]} … ${report.bounds.max[0]}, y ${report.bounds.min[1]} … ${report.bounds.max[1]}, z ${report.bounds.min[2]} … ${report.bounds.max[2]}`);
|
||||
console.log(`Wurzel-Verschiebung: ${JSON.stringify(report.rootTranslation)}`);
|
||||
console.log(`Texturen: ${report.textures.count} (${report.textures.unique} verschieden), ${JSON.stringify(report.textures.sizes)}, ~${report.textures.gpuMB} MB Grafikspeicher`);
|
||||
console.log(`Gezeichnete Dreiecke: ${report.drawnTriangles}`);
|
||||
console.table(report.materials.map(({ name, drawnTriangles, primitives, alphaMode, bump }) => ({ name, drawnTriangles, primitives, alphaMode, bump })));
|
||||
console.table(report.groups.map(({ name, min: lo, max: hi }) => ({ name, x: `${lo[0]} … ${hi[0]}`, y: `${lo[1]} … ${hi[1]}`, z: `${lo[2]} … ${hi[2]}` })));
|
||||
if (reportPath) {
|
||||
await writeFile(reportPath, JSON.stringify(report, null, 2));
|
||||
console.log(`JSON: ${reportPath}`);
|
||||
}
|
||||
Reference in new issue
Block a user