@needle-tools/gltf-build-pipeline 2.5.0 → 2.6.0-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -7,7 +7,7 @@ import { MeshoptDecoder, MeshoptEncoder } from 'meshoptimizer';
|
|
|
7
7
|
import { dedup, flatten, join, prune, resample } from '@gltf-transform/functions';
|
|
8
8
|
import { ALL_EXTENSIONS as NEEDLE_EXTENSIONS, NEEDLE_compression_texture, NEEDLE_mesh_compression, } from '../extensions/index.js';
|
|
9
9
|
import { isLOD, isMeshLOD, needle_animation_transform, needle_asset, needle_mesh_transform, needle_texture_transform, } from '../transforms/index.js';
|
|
10
|
-
import { getOutputPath, writeNodeIO } from "../utils/index.js";
|
|
10
|
+
import { getOutputPath, tryRead, writeNodeIO } from "../utils/index.js";
|
|
11
11
|
import { addToCache, getHash, tryGetFromCache, } from "../cache/index.js";
|
|
12
12
|
/** Compress a glTF file
|
|
13
13
|
*/
|
|
@@ -42,6 +42,11 @@ export async function packGLTF(inputFile, outputFile, options) {
|
|
|
42
42
|
}
|
|
43
43
|
try {
|
|
44
44
|
const config = options.config;
|
|
45
|
+
if (!outputFile)
|
|
46
|
+
outputFile = inputFile;
|
|
47
|
+
else {
|
|
48
|
+
outputFile = getOutputPath(inputFile, outputFile);
|
|
49
|
+
}
|
|
45
50
|
const prevSizeInMB = statSync(inputFile).size / 1024 / 1024;
|
|
46
51
|
logger.info(`→ Compressing ${prevSizeInMB.toFixed(1)} MB: ${inputFile}`);
|
|
47
52
|
const startTime = Date.now();
|
|
@@ -58,7 +63,7 @@ export async function packGLTF(inputFile, outputFile, options) {
|
|
|
58
63
|
'meshopt.decoder': MeshoptDecoder,
|
|
59
64
|
'meshopt.encoder': MeshoptEncoder,
|
|
60
65
|
});
|
|
61
|
-
const document = await io.read(inputFile);
|
|
66
|
+
const document = await tryRead(io, inputFile); // await io.read(inputFile);
|
|
62
67
|
/** @ts-ignore */
|
|
63
68
|
document.getLogger().verbosity = options.verbose ? Verbosity.DEBUG : options.debug ? Verbosity.INFO : Verbosity.WARN;
|
|
64
69
|
let totalGPUTextureMemory = 0;
|
|
@@ -67,6 +72,7 @@ export async function packGLTF(inputFile, outputFile, options) {
|
|
|
67
72
|
needle_asset(),
|
|
68
73
|
needle_texture_transform({
|
|
69
74
|
file: inputFile,
|
|
75
|
+
outfile: outputFile,
|
|
70
76
|
useCache,
|
|
71
77
|
noToktx: false,
|
|
72
78
|
debug: debug,
|
|
@@ -96,12 +102,7 @@ export async function packGLTF(inputFile, outputFile, options) {
|
|
|
96
102
|
transforms.push(join({ keepNamed: false }));
|
|
97
103
|
}
|
|
98
104
|
await document.transform(...transforms);
|
|
99
|
-
|
|
100
|
-
outputFile = inputFile;
|
|
101
|
-
else {
|
|
102
|
-
outputFile = getOutputPath(inputFile, outputFile);
|
|
103
|
-
logger.info(`⇄ Writing to ${outputFile}`);
|
|
104
|
-
}
|
|
105
|
+
logger.info(`⇄ Writing to ${outputFile}`);
|
|
105
106
|
await writeNodeIO(io, outputFile, document);
|
|
106
107
|
if (useCache && cacheKey != undefined && existsSync(outputFile))
|
|
107
108
|
addToCache(cacheKey, readFileSync(outputFile));
|
|
@@ -119,7 +120,7 @@ export async function packGLTF(inputFile, outputFile, options) {
|
|
|
119
120
|
return true;
|
|
120
121
|
}
|
|
121
122
|
catch (err) {
|
|
122
|
-
logger.error(`
|
|
123
|
+
logger.error(`Packing \"${path.basename(inputFile)}\" failed. Error: ${err.message}`);
|
|
123
124
|
if (debug)
|
|
124
125
|
throw err;
|
|
125
126
|
return false;
|
|
@@ -195,10 +195,15 @@ async function processTexture(texture, index, document, logger, context) {
|
|
|
195
195
|
if (successFullyProcessed) {
|
|
196
196
|
const newUri = texture.getURI();
|
|
197
197
|
if (originalUri && originalUri != newUri) {
|
|
198
|
-
const
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
198
|
+
const inDirectory = dirname(context.file);
|
|
199
|
+
const outDirectory = dirname(context.outfile);
|
|
200
|
+
const originalPath = inDirectory + "/" + originalUri;
|
|
201
|
+
// if the file output directory is the same as the input directory
|
|
202
|
+
// (meaning compression runs in the same directory)
|
|
203
|
+
// we remove the original file (since it effectively gets replaced)
|
|
204
|
+
// otherwise we dont touch the original file
|
|
205
|
+
if (inDirectory == outDirectory && existsSync(originalPath)) {
|
|
206
|
+
console.log(`INFO: Remove old texture \"${originalUri}\" → \"${newUri}\"`);
|
|
202
207
|
rmSync(originalPath);
|
|
203
208
|
}
|
|
204
209
|
}
|
package/dist/utils/nodeio.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { NodeIO, Document } from "@gltf-transform/core";
|
|
1
|
+
import { NodeIO, Document, PlatformIO } from "@gltf-transform/core";
|
|
2
2
|
/**
|
|
3
3
|
* Handle writing glTf, GLB and VRM files
|
|
4
4
|
*/
|
|
5
5
|
export declare function writeNodeIO(nodeio: NodeIO, filename: string, document: Document): Promise<string>;
|
|
6
|
+
export declare function tryRead(io: PlatformIO, filepath: string, iteration?: number): Promise<Document>;
|
package/dist/utils/nodeio.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { writeFile } from "fs/promises";
|
|
2
|
+
import sharp from "sharp";
|
|
2
3
|
/**
|
|
3
4
|
* Handle writing glTf, GLB and VRM files
|
|
4
5
|
*/
|
|
@@ -12,3 +13,68 @@ export async function writeNodeIO(nodeio, filename, document) {
|
|
|
12
13
|
await nodeio.write(filename, document);
|
|
13
14
|
return filename;
|
|
14
15
|
}
|
|
16
|
+
const openFileRegex = /open \'(?<path>.+?)'/;
|
|
17
|
+
const createdFiles = new Set();
|
|
18
|
+
export async function tryRead(io, filepath, iteration = 0) {
|
|
19
|
+
const document = await io.read(filepath)
|
|
20
|
+
.catch(err => { return err; });
|
|
21
|
+
if (document instanceof Error) {
|
|
22
|
+
const err = document;
|
|
23
|
+
// Handle glTF files where resource are missing
|
|
24
|
+
if (iteration < 100 && err.message?.includes("ENOENT")) {
|
|
25
|
+
try {
|
|
26
|
+
const match = err.message.match(openFileRegex);
|
|
27
|
+
if (match.groups?.path) {
|
|
28
|
+
let missingPath = match.groups.path;
|
|
29
|
+
// check if it's an URL
|
|
30
|
+
if (missingPath.startsWith("http")) {
|
|
31
|
+
throw err;
|
|
32
|
+
}
|
|
33
|
+
if (createdFiles.has(missingPath)) {
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
36
|
+
createdFiles.add(missingPath);
|
|
37
|
+
console.warn(`Creating empty file for missing resource: ${missingPath}`);
|
|
38
|
+
const ext = missingPath.split('.').pop();
|
|
39
|
+
if (!ext) {
|
|
40
|
+
console.warn(`Could not determine extension for ${missingPath}`);
|
|
41
|
+
throw err;
|
|
42
|
+
}
|
|
43
|
+
const extLower = ext?.toLowerCase();
|
|
44
|
+
let retry = false;
|
|
45
|
+
// https://sharp.pixelplumbing.com/api-output#tofile
|
|
46
|
+
switch (extLower) {
|
|
47
|
+
case "jpeg":
|
|
48
|
+
case "jpg":
|
|
49
|
+
case "png":
|
|
50
|
+
case "webp":
|
|
51
|
+
case "tiff":
|
|
52
|
+
case "gif":
|
|
53
|
+
{
|
|
54
|
+
await sharp({
|
|
55
|
+
create: {
|
|
56
|
+
width: 64,
|
|
57
|
+
height: 64,
|
|
58
|
+
channels: 4,
|
|
59
|
+
background: { r: 255, g: 255, b: 255, alpha: 1 }
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
.toFormat(ext)
|
|
63
|
+
.toFile(missingPath);
|
|
64
|
+
retry = true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (retry) {
|
|
68
|
+
console.warn(`Created empty file for missing resource: ${missingPath}`);
|
|
69
|
+
return tryRead(io, filepath, iteration + 1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (internal_error) {
|
|
74
|
+
console.warn(internal_error);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
79
|
+
return document;
|
|
80
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needle-tools/gltf-build-pipeline",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0-alpha",
|
|
4
4
|
"description": "Pipeline and tools for optimizing gltf files using gltf-transform and compression settings within glTF extensions",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|