@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
- if (!outputFile)
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(`packing ${path.basename(inputFile)} failed: ${err}`);
123
+ logger.error(`Packing \"${path.basename(inputFile)}\" failed. Error: ${err.message}`);
123
124
  if (debug)
124
125
  throw err;
125
126
  return false;
@@ -4,6 +4,7 @@ import { Config } from '../config/index.js';
4
4
  export interface NeedleTransformContext {
5
5
  /** full path to the file being processed */
6
6
  file: string;
7
+ outfile: string;
7
8
  config: Config;
8
9
  debug?: boolean;
9
10
  useCache: boolean;
@@ -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 fileDirectory = dirname(context.file);
199
- const originalPath = fileDirectory + "/" + originalUri;
200
- if (existsSync(originalPath)) {
201
- console.log(`INFO: Texture moved \"${originalUri}\" → \"${newUri}\"`);
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
  }
@@ -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>;
@@ -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.5.0",
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",