@needle-tools/gltf-build-pipeline 2.1.0-alpha.6 → 2.2.0-alpha.1

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.
Files changed (88) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/cli/index.js +118 -0
  3. package/dist/{src/config → config}/index.d.ts +8 -2
  4. package/dist/{src/config → config}/index.js +31 -15
  5. package/dist/{src/transforms → transforms}/needle_mesh_transform.js +1 -1
  6. package/dist/{src/transforms → transforms}/needle_progressive.js +20 -18
  7. package/dist/{src/transforms → transforms}/needle_toktx.js +1 -1
  8. package/dist/{src/transforms → transforms}/toktx.d.ts +1 -1
  9. package/dist/{src/transforms → transforms}/toktx.js +2 -6
  10. package/dist/transforms/toktx_types.d.ts +78 -0
  11. package/dist/transforms/toktx_types.js +62 -0
  12. package/dist/{src/utils → utils}/version.js +1 -1
  13. package/package.json +10 -9
  14. package/tsconfig.json +7 -3
  15. package/dist/___config.json +0 -3
  16. package/dist/package.json +0 -75
  17. package/dist/src/cli/index.js +0 -192
  18. /package/dist/{src/cache → cache}/cache.d.ts +0 -0
  19. /package/dist/{src/cache → cache}/cache.js +0 -0
  20. /package/dist/{src/cache → cache}/index.d.ts +0 -0
  21. /package/dist/{src/cache → cache}/index.js +0 -0
  22. /package/dist/{src/cli → cli}/index.d.ts +0 -0
  23. /package/dist/{src/constants.d.ts → constants.d.ts} +0 -0
  24. /package/dist/{src/constants.js → constants.js} +0 -0
  25. /package/dist/{src/extensions → extensions}/NEEDLE_compression_texture/NEEDLE_compression_texture.d.ts +0 -0
  26. /package/dist/{src/extensions → extensions}/NEEDLE_compression_texture/NEEDLE_compression_texture.js +0 -0
  27. /package/dist/{src/extensions → extensions}/NEEDLE_compression_texture/index.d.ts +0 -0
  28. /package/dist/{src/extensions → extensions}/NEEDLE_compression_texture/index.js +0 -0
  29. /package/dist/{src/extensions → extensions}/NEEDLE_mesh_compression/NEEDLE_mesh_compression.d.ts +0 -0
  30. /package/dist/{src/extensions → extensions}/NEEDLE_mesh_compression/NEEDLE_mesh_compression.js +0 -0
  31. /package/dist/{src/extensions → extensions}/NEEDLE_mesh_compression/index.d.ts +0 -0
  32. /package/dist/{src/extensions → extensions}/NEEDLE_mesh_compression/index.js +0 -0
  33. /package/dist/{src/extensions → extensions}/NEEDLE_opaque/NEEDLE_opaque.d.ts +0 -0
  34. /package/dist/{src/extensions → extensions}/NEEDLE_opaque/NEEDLE_opaque.js +0 -0
  35. /package/dist/{src/extensions → extensions}/NEEDLE_opaque/index.d.ts +0 -0
  36. /package/dist/{src/extensions → extensions}/NEEDLE_opaque/index.js +0 -0
  37. /package/dist/{src/extensions → extensions}/NEEDLE_progressive/NEEDLE_progressive.d.ts +0 -0
  38. /package/dist/{src/extensions → extensions}/NEEDLE_progressive/NEEDLE_progressive.js +0 -0
  39. /package/dist/{src/extensions → extensions}/NEEDLE_progressive/index.d.ts +0 -0
  40. /package/dist/{src/extensions → extensions}/NEEDLE_progressive/index.js +0 -0
  41. /package/dist/{src/extensions → extensions}/NEEDLE_progressive_mesh_settings/index.d.ts +0 -0
  42. /package/dist/{src/extensions → extensions}/NEEDLE_progressive_mesh_settings/index.js +0 -0
  43. /package/dist/{src/extensions → extensions}/NEEDLE_progressive_texture_settings/index.d.ts +0 -0
  44. /package/dist/{src/extensions → extensions}/NEEDLE_progressive_texture_settings/index.js +0 -0
  45. /package/dist/{src/extensions → extensions}/index.d.ts +0 -0
  46. /package/dist/{src/extensions → extensions}/index.js +0 -0
  47. /package/dist/{src/extensions → extensions}/utils.d.ts +0 -0
  48. /package/dist/{src/extensions → extensions}/utils.js +0 -0
  49. /package/dist/{src/index.d.ts → index.d.ts} +0 -0
  50. /package/dist/{src/index.js → index.js} +0 -0
  51. /package/dist/{src/scripts → scripts}/index.d.ts +0 -0
  52. /package/dist/{src/scripts → scripts}/index.js +0 -0
  53. /package/dist/{src/scripts → scripts}/pack-gltf.d.ts +0 -0
  54. /package/dist/{src/scripts → scripts}/pack-gltf.js +0 -0
  55. /package/dist/{src/transforms → transforms}/index.d.ts +0 -0
  56. /package/dist/{src/transforms → transforms}/index.js +0 -0
  57. /package/dist/{src/transforms → transforms}/needle_animation_transform.d.ts +0 -0
  58. /package/dist/{src/transforms → transforms}/needle_animation_transform.js +0 -0
  59. /package/dist/{src/transforms → transforms}/needle_asset.d.ts +0 -0
  60. /package/dist/{src/transforms → transforms}/needle_asset.js +0 -0
  61. /package/dist/{src/transforms → transforms}/needle_common.d.ts +0 -0
  62. /package/dist/{src/transforms → transforms}/needle_common.js +0 -0
  63. /package/dist/{src/transforms → transforms}/needle_mesh_transform.d.ts +0 -0
  64. /package/dist/{src/transforms → transforms}/needle_progressive.d.ts +0 -0
  65. /package/dist/{src/transforms → transforms}/needle_texture_transform.d.ts +0 -0
  66. /package/dist/{src/transforms → transforms}/needle_texture_transform.js +0 -0
  67. /package/dist/{src/transforms → transforms}/needle_toktx.d.ts +0 -0
  68. /package/dist/{src/transforms → transforms}/needle_webp.d.ts +0 -0
  69. /package/dist/{src/transforms → transforms}/needle_webp.js +0 -0
  70. /package/dist/{src/transforms → transforms}/util.d.ts +0 -0
  71. /package/dist/{src/transforms → transforms}/util.js +0 -0
  72. /package/dist/{src/utils → utils}/compression_utils.d.ts +0 -0
  73. /package/dist/{src/utils → utils}/compression_utils.js +0 -0
  74. /package/dist/{src/utils → utils}/fileutils.d.ts +0 -0
  75. /package/dist/{src/utils → utils}/fileutils.js +0 -0
  76. /package/dist/{src/utils → utils}/guid.d.ts +0 -0
  77. /package/dist/{src/utils → utils}/guid.js +0 -0
  78. /package/dist/{src/utils → utils}/index.d.ts +0 -0
  79. /package/dist/{src/utils → utils}/index.js +0 -0
  80. /package/dist/{src/utils → utils}/mesh.d.ts +0 -0
  81. /package/dist/{src/utils → utils}/mesh.js +0 -0
  82. /package/dist/{src/utils → utils}/nodeio.d.ts +0 -0
  83. /package/dist/{src/utils → utils}/nodeio.js +0 -0
  84. /package/dist/{src/utils → utils}/stats.d.ts +0 -0
  85. /package/dist/{src/utils → utils}/stats.js +0 -0
  86. /package/dist/{src/utils → utils}/texture.d.ts +0 -0
  87. /package/dist/{src/utils → utils}/texture.js +0 -0
  88. /package/dist/{src/utils → utils}/version.d.ts +0 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,13 @@ All notable changes to this package will be documented in this file.
4
4
  The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5
5
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [2.2.0-alpha] - 2023-06-06
8
+ - Add: CLI `--progressive` option to `transform` command (default is enabled)
9
+ - Add: config `meshes: { lods: boolean }` option
10
+ - Remove: CLI "compress" command
11
+ - Change: enable texture LODs by default
12
+ - Fix: `--config` path can now point to a directory or a `needle.config.json` file as well
13
+
7
14
  ## [2.1.0-alpha.6] - 2023-06-04
8
15
  - fix: errors when invoking npm scripts caused by the cli program
9
16
  - fix: remove cache directory error
@@ -0,0 +1,118 @@
1
+ #! /usr/bin/env node
2
+ import { program } from '@donmccurdy/caporal';
3
+ import { packGLTF } from "../scripts/index.js";
4
+ import { printStats, writeFileStatsToFile } from '../utils/stats.js';
5
+ import { getVersion } from '../utils/version.js';
6
+ import { UsecaseOptions, createConfig, getConfig } from '../config/index.js';
7
+ import { make_progressive } from '../transforms/needle_progressive.js';
8
+ import { cacheSizeLimit, clearCache } from '../cache/cache.js';
9
+ import { statSync } from 'fs';
10
+ import { foreachGLTF, getDirName } from '../utils/fileutils.js';
11
+ import path, { dirname } from 'path';
12
+ import { ERROR_CODES } from '../constants.js';
13
+ // For testing / dev you can run `npm link` in the package directory
14
+ // For removing the link run `npm rm --global @needle-tools/gltf-build-pipeline`
15
+ program
16
+ .command("version", "Print current verison")
17
+ .action(async ({ logger }) => {
18
+ logger.info(getVersion());
19
+ })
20
+ .command("clear-caches", "Clear all caches")
21
+ .help(`Clears all cached files
22
+ Caches are limited to ${cacheSizeLimit} MB disc space by default.
23
+ `)
24
+ .action(async ({ logger }) => {
25
+ logger.info("Clearing caches");
26
+ clearCache(logger);
27
+ })
28
+ .command("transform", "Transform (progressive loading) and compress glTF, GLB or VRM files")
29
+ .help(`
30
+ This will produce multiple versions of the input file, each with a different level of optimization.
31
+ Each version will be compressed and written to the output directory.
32
+ `)
33
+ .argument('<input>', "The input glTF, GLB or VRM file that should be compressed OR a directory that contains files to be compressed")
34
+ // .argument('<output>', "The output file. If not provided, the input file will be overwritten.", { default: "" })
35
+ .option("--progressive", `When enabled files will processed to be progressively loaded`, { validator: program.BOOLEAN, default: true })
36
+ .option("--usecase <usecase>", `The usecase for the compression. This will set the compression settings. Possible options: [${UsecaseOptions.join(", ")}]`, { validator: program.STRING })
37
+ .option("--config <config>", "The path to a JSON file containing the compression settings. This can also be a needle.config.json file with a \"compression\" object or usecase string.", { validator: program.STRING })
38
+ .option("--stats", "Output a stats file next to input file (or inside the input directory). It will be named as the input file/directory \"<input>.needle.stats.json\"", { validator: program.BOOLEAN, default: false })
39
+ .option("--cache", "Use \"--cache False\" to disable caching.\nWhen caching is enabled then previously processed files will be detected and loaded from disc cache instead of being processed again if settings or the file contents have not changed.", { validator: program.BOOLEAN, default: true })
40
+ .option("--debug", "Enable for more verbose outout with \"--debug True\"", { validator: program.BOOLEAN, default: false })
41
+ .action(async ({ logger, args, options }) => {
42
+ logger.info("Transforming glTF file: " + args.input);
43
+ const stats = {
44
+ startTime: Date.now(),
45
+ totalGPUMemoryInMB: 0,
46
+ totalFilesProcessed: 0,
47
+ totalFileSizeInMB: 0,
48
+ totalFileSizeInMBBefore: 0,
49
+ };
50
+ const file = args.input.toString();
51
+ let config = getConfig(options.config?.toString(), file, {
52
+ logger
53
+ });
54
+ if (options.usecase) {
55
+ logger.info("Using usecase: " + options.usecase);
56
+ config = { ...createConfig(options.usecase.toString()), ...config, };
57
+ if (config.usecase != "default" && config.usecase != options.usecase.toString()) {
58
+ logger.warn(`Conflicting usecase. Your specific config at \"${options.config}\" has a different usecase than the one provided by the CLI: \"${options.usecase}\". Using the usecase from the config file.`);
59
+ }
60
+ else {
61
+ config.usecase = options.usecase.toString();
62
+ }
63
+ }
64
+ const output = options.output?.toString() || file;
65
+ const outputIsDirectory = statSync(output).isDirectory();
66
+ const outDir = outputIsDirectory ? output : dirname(output);
67
+ const file_stats = [];
68
+ const set = new Set();
69
+ const useCache = options.cache !== false;
70
+ const debug = options.debug === true;
71
+ await foreachGLTF(file, async (file) => {
72
+ logger.info("→ Transform " + file);
73
+ const progressive_results = new Array();
74
+ if (options.progressive === true) {
75
+ await make_progressive({
76
+ path: file,
77
+ results: progressive_results,
78
+ config,
79
+ debug,
80
+ useCache,
81
+ }).catch(err => {
82
+ logger.error(err);
83
+ process.exit(ERROR_CODES.PROGRESSIVE_FAILED);
84
+ });
85
+ }
86
+ const opts = {
87
+ config,
88
+ stats,
89
+ packedFiles: set,
90
+ debug,
91
+ useCache,
92
+ logger: logger,
93
+ file_stats,
94
+ };
95
+ for (const res of progressive_results) {
96
+ if (await packGLTF(res, res, opts) === false) {
97
+ process.exit(ERROR_CODES.PACKING_FAILED);
98
+ }
99
+ }
100
+ if (await packGLTF(file, file, opts) === false) {
101
+ process.exit(ERROR_CODES.PACKING_FAILED);
102
+ }
103
+ });
104
+ printStats(stats, logger, config);
105
+ if (options.stats) {
106
+ const statsFile = (outputIsDirectory ? (path.join(file, getDirName(file))) : file) + ".needle.stats.json";
107
+ writeFileStatsToFile(config, file_stats, statsFile);
108
+ }
109
+ });
110
+ program.run().catch(err => {
111
+ // Since we still invoke this package with paths when running npm scripts we need to ignore this error
112
+ // We might want to move this code here into an extra package.
113
+ if (err.message.includes("Unknown command") && !err.message.includes("Did you mean")) {
114
+ // ignore
115
+ return;
116
+ }
117
+ throw err;
118
+ });
@@ -1,3 +1,4 @@
1
+ import { ILogger } from "@gltf-transform/core";
1
2
  declare type Usecase = "default" | "product" | "world";
2
3
  export declare const UsecaseOptions: string[];
3
4
  /**
@@ -9,6 +10,9 @@ export type Config = {
9
10
  textures?: {
10
11
  lods: boolean;
11
12
  };
13
+ meshes?: {
14
+ lods: boolean;
15
+ };
12
16
  };
13
17
  /**
14
18
  * Create a config object with the given usecase.
@@ -42,6 +46,8 @@ export declare function createConfig(usecase: string): {
42
46
  *
43
47
  *
44
48
  */
45
- export declare function getConfig(configPath?: string, fallbackSearchDirectory?: string): Config;
46
- export declare function searchConfig(directory: string): Config | null;
49
+ export declare function getConfig(configPath?: string, fallbackSearchDirectory?: string, opts?: {
50
+ logger: ILogger;
51
+ }): Config;
52
+ export declare function searchConfig(directory: string, logger: ILogger): Config | null;
47
53
  export {};
@@ -1,5 +1,6 @@
1
+ import { Logger, Verbosity } from "@gltf-transform/core";
1
2
  import { existsSync, readFileSync, statSync } from "fs";
2
- import path, { dirname } from "path";
3
+ import path, { dirname, resolve } from "path";
3
4
  export const UsecaseOptions = ["product", "world"];
4
5
  const defaultConfig = {
5
6
  usecase: "default",
@@ -41,12 +42,27 @@ export function createConfig(usecase) {
41
42
  *
42
43
  *
43
44
  */
44
- export function getConfig(configPath, fallbackSearchDirectory) {
45
+ export function getConfig(configPath, fallbackSearchDirectory, opts) {
46
+ let logger = opts?.logger;
47
+ if (!logger) {
48
+ logger = new Logger(Verbosity.WARN);
49
+ }
45
50
  if (configPath != null) {
51
+ configPath = resolve(process.cwd(), configPath);
46
52
  if (!existsSync(configPath)) {
47
53
  throw new Error(`No config found at \"${configPath}\"`);
48
54
  }
49
- const res = resolveConfig(configPath, readFileSync(configPath, "utf8"));
55
+ const isDirectory = statSync(configPath).isDirectory();
56
+ if (isDirectory) {
57
+ const res = searchConfig(configPath, logger);
58
+ if (res) {
59
+ return res;
60
+ }
61
+ else {
62
+ throw new Error(`No config found in directory \"${configPath}\"`);
63
+ }
64
+ }
65
+ const res = resolveConfig(configPath, readFileSync(configPath, "utf8"), logger);
50
66
  if (res) {
51
67
  return res;
52
68
  }
@@ -60,21 +76,21 @@ export function getConfig(configPath, fallbackSearchDirectory) {
60
76
  // Resolve the config once and cache it
61
77
  // Check if a needle.config.json exists in the directory from which the script is run (or any parent directory)
62
78
  if (fallbackSearchDirectory) {
63
- const result = searchConfig(fallbackSearchDirectory);
79
+ const result = searchConfig(fallbackSearchDirectory, logger);
64
80
  if (result) {
65
81
  _cachedConfig = { ...defaultConfig, ...result };
66
- console.log("INFO: Using config with usecase: " + _cachedConfig.usecase);
82
+ logger.info("Using config with usecase: " + _cachedConfig.usecase);
67
83
  }
68
84
  }
69
85
  // If no config was found, use the default config
70
86
  if (!_cachedConfig) {
71
- console.log("INFO: No config found. Using default config.");
87
+ logger.info("No config found. Using default config.");
72
88
  _cachedConfig = defaultConfig;
73
89
  }
74
90
  return _cachedConfig;
75
91
  }
76
92
  let _cachedConfig = undefined;
77
- export function searchConfig(directory) {
93
+ export function searchConfig(directory, logger) {
78
94
  if (statSync(directory).isFile()) {
79
95
  directory = dirname(directory);
80
96
  }
@@ -83,7 +99,7 @@ export function searchConfig(directory) {
83
99
  const configPath = path.join(directory, "needle.config.json");
84
100
  if (existsSync(configPath)) {
85
101
  const content = readFileSync(configPath, "utf8");
86
- const config = resolveConfig(configPath, content);
102
+ const config = resolveConfig(configPath, content, logger);
87
103
  if (config) {
88
104
  return config;
89
105
  }
@@ -96,13 +112,13 @@ export function searchConfig(directory) {
96
112
  }
97
113
  return null;
98
114
  }
99
- function resolveConfig(filename, content) {
115
+ function resolveConfig(filename, content, logger) {
100
116
  // Handle config being part of `needle.config.json`
101
117
  if (filename.endsWith("needle.config.json")) {
102
118
  try {
103
119
  const config = JSON.parse(content);
104
120
  if (config.gltf) {
105
- console.log("INFO: Found \"gltf\" config at \"" + filename + "\".");
121
+ logger.info("Found \"gltf\" config at \"" + filename + "\".");
106
122
  // If the optimization config is a string, it's the usecase
107
123
  if (typeof config.gltf === "string") {
108
124
  return createConfig(config.gltf);
@@ -110,12 +126,12 @@ function resolveConfig(filename, content) {
110
126
  return config.gltf;
111
127
  }
112
128
  else {
113
- console.log("INFO: No \"gltf\" config found in needle.config.json. Using default config.");
129
+ logger.info("No \"gltf\" config found in needle.config.json. Using default config.");
114
130
  return null;
115
131
  }
116
132
  }
117
133
  catch (err) {
118
- console.error(`ERR: Failed parsing config \"${err.message}\" at ${filename}`);
134
+ logger.error(`Failed parsing config \"${err.message}\" at ${filename}`);
119
135
  }
120
136
  }
121
137
  // Handle config being the content of the file
@@ -125,12 +141,12 @@ function resolveConfig(filename, content) {
125
141
  }
126
142
  catch (err) {
127
143
  if (err instanceof Error)
128
- console.error(`ERR: Failed parsing config \"${err.message}\" at ${filename}`);
144
+ logger.error(`Failed parsing config \"${err.message}\" at ${filename}`);
129
145
  else
130
- console.error(`ERR: Failed parsing config \"${err}\" at ${filename}`);
146
+ logger.error(`Failed parsing config \"${err}\" at ${filename}`);
131
147
  }
132
148
  }
133
149
  else {
134
- console.error("ERR: Invalid config file format. Expected JSON object at " + filename);
150
+ logger.error("Invalid config file format. Expected JSON object at " + filename);
135
151
  }
136
152
  }
@@ -37,7 +37,7 @@ async function applyMeshCompression(document, transform_context, context) {
37
37
  // return transform(document, transform_context);
38
38
  // }
39
39
  // by default we still want to run draco if the root doesnt have a compression model configured
40
- console.log(`[${NAME}]: → will apply draco mesh compression (default)`);
40
+ document.getLogger().info(`[${NAME}]: → will apply draco mesh compression (default)`);
41
41
  return draco()(document, transform_context);
42
42
  }
43
43
  async function simplifierTransform(document, _context, _options) {
@@ -13,7 +13,6 @@ import { MeshoptDecoder, MeshoptSimplifier } from 'meshoptimizer';
13
13
  import { compressPrimitive } from './needle_mesh_transform.js';
14
14
  import { calculateMeshDensity, getMeshInformation } from "../utils/mesh.js";
15
15
  export async function make_progressive(opts) {
16
- console.log(`INFO: Run progressive transform: ${opts.path}`);
17
16
  const file = opts.path;
18
17
  return foreachGLTF(file, async (f) => {
19
18
  // Don't re-process files that are already LODs
@@ -64,15 +63,17 @@ async function onProcess(file, opts) {
64
63
  }
65
64
  const transforms = [
66
65
  needle_asset(),
67
- make_progressive_textures(file, { size: [smallesTextureSize, smallesTextureSize] }, opts),
68
- sparse(),
69
66
  ];
67
+ if (opts.config?.textures?.lods !== false) {
68
+ transforms.push(make_progressive_textures(file, { size: [smallesTextureSize, smallesTextureSize] }, opts));
69
+ }
70
70
  // TODO: progressive meshes aren't supported yet for VRMs
71
- if (!file.endsWith(".vrm")) {
71
+ if (!file.endsWith(".vrm") && opts.config?.meshes?.lods !== false) {
72
72
  transforms.push(make_progressive_meshes(file, {}, opts));
73
73
  }
74
74
  else
75
75
  logger.warn("WARN: Progressive meshes are not supported for VRM files yet");
76
+ transforms.push(sparse());
76
77
  logger.info("→ Transforming: " + file);
77
78
  await document.transform(...transforms);
78
79
  let filename = file;
@@ -174,11 +175,11 @@ export function make_progressive_textures(filePath, _options = TEXTURE_RESIZE_DE
174
175
  const currentSize = Math.min(...texture.getSize());
175
176
  const LOD_LEVEL = new Array();
176
177
  LOD_LEVEL.push({ size: currentSize });
177
- let generateLods = opts.config?.usecase === "world";
178
+ let generateLods = true; //opts.config?.usecase === "world";
178
179
  // If lods are enabled in the config
179
- generateLods ||= opts.config?.textures?.lods === true;
180
- // Ensure that texture lods are not disabled by { textures: { lods: false } }
181
- generateLods &&= opts.config?.textures?.lods !== false;
180
+ // generateLods ||= opts.config?.textures?.lods === true;
181
+ // // Ensure that texture lods are not disabled by { textures: { lods: false } }
182
+ // generateLods &&= opts.config?.textures?.lods !== false;
182
183
  if (generateLods) {
183
184
  const LEVEL = 6;
184
185
  let lastSize = currentSize;
@@ -619,7 +620,7 @@ export function make_progressive_meshes(filePath, _options, opts) {
619
620
  const lodLevelInFile = generatedLODInfos.length;
620
621
  const clonedData = { primitives: [], weights: [...mesh.getWeights()] };
621
622
  for (const prim of mesh.listPrimitives()) {
622
- const data = clonePrimitiveData(prim);
623
+ const data = clonePrimitiveData(prim, logger);
623
624
  if (data != null) {
624
625
  clonedData.primitives.push(data);
625
626
  }
@@ -689,7 +690,7 @@ export function make_progressive_meshes(filePath, _options, opts) {
689
690
  }
690
691
  });
691
692
  }
692
- function clonePrimitiveData(prim) {
693
+ function clonePrimitiveData(prim, logger) {
693
694
  const semantics = prim.listSemantics();
694
695
  if (semantics.some(s => s.includes("JOINTS"))) {
695
696
  return null;
@@ -697,7 +698,7 @@ function clonePrimitiveData(prim) {
697
698
  const indices = new Uint32Array(prim.getIndices().getArray());
698
699
  const attributes = {};
699
700
  for (const attr of semantics) {
700
- console.log(`[${NAME}] Clone attribute ${attr}`);
701
+ logger.info(`[${NAME}] Clone attribute ${attr}`);
701
702
  const accessor = prim.getAttribute(attr);
702
703
  if (accessor) {
703
704
  attributes[attr] = new Float32Array(accessor.getArray());
@@ -725,6 +726,7 @@ function setPrimitiveData(prim, data) {
725
726
  * @param error the error to use for simplification. Higher means more simplification
726
727
  */
727
728
  async function createMeshLod(doc, basePath, level, mesh, meshInfo, guid, lodLevel, opts) {
729
+ const logger = doc.getLogger();
728
730
  const fileExtension = ".glb";
729
731
  const fileName_withoutExtension = "mesh_lod_" + level + "_" + guid;
730
732
  const fileName = fileName_withoutExtension + fileExtension;
@@ -744,7 +746,7 @@ async function createMeshLod(doc, basePath, level, mesh, meshInfo, guid, lodLeve
744
746
  const cachedValue = tryGetFromCache(cacheKey);
745
747
  const meta = tryGetMetaFromCache(cacheKey);
746
748
  if (cachedValue && meta) {
747
- console.log(`[${NAME}] LOD ${level} loaded ${(cachedValue.length / 1024).toFixed()} KB from cache for \"${mesh.getName()}\"`);
749
+ logger.info(`[${NAME}] LOD ${level} loaded ${(cachedValue.length / 1024).toFixed()} KB from cache for \"${mesh.getName()}\"`);
748
750
  writeFileSync(outPath, cachedValue);
749
751
  return { level, path: fileName, success: true, cacheKey: cacheKey, content_hash: hash, lodMeshInfo: meta.meshInfo, density: meta.density };
750
752
  }
@@ -783,7 +785,7 @@ async function createMeshLod(doc, basePath, level, mesh, meshInfo, guid, lodLeve
783
785
  newPrim.setAttribute(semantics[i], newAttr);
784
786
  }
785
787
  else {
786
- console.warn(`[${NAME}] Missing attribute ${semantics[i]} in mesh LOD ${level}`);
788
+ logger.warn(`[${NAME}] Missing attribute ${semantics[i]} in mesh LOD ${level}`);
787
789
  }
788
790
  }
789
791
  const indices = prim.getIndices();
@@ -818,7 +820,7 @@ async function createMeshLod(doc, basePath, level, mesh, meshInfo, guid, lodLeve
818
820
  newPrim.setExtension(name, copy);
819
821
  }
820
822
  catch (err) {
821
- console.error(`WARN: [${NAME}] Failed to copy extension ${ext.extensionName} for mesh LOD ${level} → ${err}`);
823
+ logger.error(`[${NAME}] Failed to copy extension ${ext.extensionName} for mesh LOD ${level} → ${err}`);
822
824
  }
823
825
  }
824
826
  }
@@ -836,7 +838,7 @@ async function createMeshLod(doc, basePath, level, mesh, meshInfo, guid, lodLeve
836
838
  tasks.length = 0;
837
839
  const newMeshInformation = getMeshInformation(newMesh);
838
840
  if (newMeshInformation.totalVertexCount < 4) {
839
- console.error(`[${NAME}] mesh LOD ${level} is has less than 4 vertices → skipping`);
841
+ logger.error(`[${NAME}] mesh LOD ${level} is has less than 4 vertices → skipping`);
840
842
  return { level, path: fileName, success: false, cacheKey: cacheKey, content_hash: hash, lodMeshInfo: null, density: null, };
841
843
  }
842
844
  let density = 0;
@@ -846,7 +848,7 @@ async function createMeshLod(doc, basePath, level, mesh, meshInfo, guid, lodLeve
846
848
  }
847
849
  const progressiveExt = newDoc.createExtension(NEEDLE_progressive);
848
850
  if (!progressiveExt || !progressiveExt.createMesh) {
849
- console.error("Failed to create progressive extension");
851
+ logger.error("Failed to create progressive extension");
850
852
  return;
851
853
  }
852
854
  newMesh.setExtension(NEEDLE_PROGRESSIVE_EXTENSION_NAME, progressiveExt.createMesh({
@@ -859,13 +861,13 @@ async function createMeshLod(doc, basePath, level, mesh, meshInfo, guid, lodLeve
859
861
  if (compressionExt) {
860
862
  const newExt = newDoc.createExtension(NEEDLE_mesh_compression);
861
863
  const newProp = newExt.create(compressionExt.model);
862
- console.log(`[${NAME}] Pass compression format to mesh LOD ${level} → ${JSON.stringify(compressionExt.model)}`);
864
+ logger.info(`[${NAME}] Pass compression format to mesh LOD ${level} → ${JSON.stringify(compressionExt.model)}`);
863
865
  newDoc.getRoot().setExtension(NEEDLE_mesh_compression.EXTENSION_NAME, newProp);
864
866
  }
865
867
  await newDoc.transform(sparse());
866
868
  const newIo = new NodeIO();
867
869
  registerExtensions(newIo);
868
- console.log(`[${NAME}] Write mesh LOD ${level} with to \"${fileName}\"`);
870
+ logger.info(`[${NAME}] Write mesh LOD ${level} with to \"${fileName}\"`);
869
871
  await newIo.write(outPath, newDoc);
870
872
  // const hasChanged = meshInfo.maxVertexCount == newMeshInformation.maxVertexCount && meshInfo.totalVertexCount == newMeshInformation.totalVertexCount;
871
873
  // add to cache
@@ -1,10 +1,10 @@
1
1
  import { BufferUtils, FileUtils, ImageUtils, uuid } from "@gltf-transform/core";
2
2
  import { listTextureSlots, getTextureChannelMask, fitPowerOfTwo, TextureResizeFilter } from "@gltf-transform/functions";
3
- import { ETC1S_DEFAULTS, UASTC_DEFAULTS } from "@gltf-transform/cli";
4
3
  import { KHRTextureBasisu } from "@gltf-transform/extensions";
5
4
  import { checkKTXSoftware, createParams, Mode } from "./toktx.js";
6
5
  import { existsSync, mkdirSync } from "fs";
7
6
  import { formatBytes, spawn, waitExit } from './util.js';
7
+ import { ETC1S_DEFAULTS, UASTC_DEFAULTS } from "./toktx_types.js";
8
8
  import fs from 'fs/promises';
9
9
  import os from 'os';
10
10
  import tmp from 'tmp';
@@ -1,5 +1,5 @@
1
- import { ETC1SOptions, UASTCOptions } from "@gltf-transform/cli";
2
1
  import { Document, ILogger, vec2, Texture } from '@gltf-transform/core';
2
+ import { ETC1SOptions, UASTCOptions } from './toktx_types.js';
3
3
  export declare const Filter: {
4
4
  BOX: string;
5
5
  TENT: string;
@@ -1,17 +1,13 @@
1
+ /* eslint-disable @typescript-eslint/no-var-requires */
1
2
  import micromatch from 'micromatch';
2
3
  import os from 'os';
3
4
  import semver from 'semver';
4
5
  import tmp from 'tmp';
5
- import { ETC1S_DEFAULTS, UASTC_DEFAULTS } from "@gltf-transform/cli";
6
6
  import { TextureChannel } from '@gltf-transform/core';
7
- // import { TextureBasisu } from '@gltf-transform/extensions';
8
- // import { getTextureChannelMask, listTextureSlots } from '@gltf-transform/functions';
9
7
  import { execSync } from 'child_process';
10
8
  import { spawn, commandExists, waitExit, MICROMATCH_OPTIONS } from './util.js';
11
9
  import { TextureResizeFilter, getTextureColorSpace } from '@gltf-transform/functions';
12
- // import { existsSync, mkdirSync } from 'fs';
13
- // import { tryGetFromCache, getKey, addToCache } from '../cache';
14
- // import { resizeTextureIfNecessary } from "./needle_texture_transform"
10
+ import { ETC1S_DEFAULTS, UASTC_DEFAULTS } from './toktx_types.js';
15
11
  tmp.setGracefulCleanup();
16
12
  const NUM_CPUS = os.cpus().length || 1; // microsoft/vscode#112122
17
13
  const KTX_SOFTWARE_VERSION_MIN = '4.0.0-rc1';
@@ -0,0 +1,78 @@
1
+ import { vec2 } from "@gltf-transform/core";
2
+ import { TextureResizeFilter } from "@gltf-transform/functions";
3
+ /**********************************************************************************************
4
+ * Interfaces.
5
+ */
6
+ export declare const Mode: {
7
+ ETC1S: string;
8
+ UASTC: string;
9
+ };
10
+ export declare const Filter: {
11
+ BOX: string;
12
+ TENT: string;
13
+ BELL: string;
14
+ BSPLINE: string;
15
+ MITCHELL: string;
16
+ LANCZOS3: string;
17
+ LANCZOS4: string;
18
+ LANCZOS6: string;
19
+ LANCZOS12: string;
20
+ BLACKMAN: string;
21
+ KAISER: string;
22
+ GAUSSIAN: string;
23
+ CATMULLROM: string;
24
+ QUADRATIC_INTERP: string;
25
+ QUADRATIC_APPROX: string;
26
+ QUADRATIC_MIX: string;
27
+ };
28
+ interface GlobalOptions {
29
+ /** Instance of the Sharp encoder, required if resizing textures. */
30
+ encoder: unknown;
31
+ mode: string;
32
+ /** Pattern identifying textures to compress, matched to name or URI. */
33
+ pattern?: RegExp | null;
34
+ /**
35
+ * Pattern matching the material texture slot(s) to be compressed or converted.
36
+ * Passing a string (glob) is deprecated; use a RegExp instead.
37
+ */
38
+ slots?: RegExp | null;
39
+ /** Interpolation used for generating mipmaps. Default: 'lanczos4'. */
40
+ filter?: string;
41
+ filterScale?: number;
42
+ resize?: vec2 | 'nearest-pot' | 'ceil-pot' | 'floor-pot';
43
+ /** Interpolation used if resizing. Default: TextureResizeFilter.LANCZOS3. */
44
+ resizeFilter?: TextureResizeFilter;
45
+ jobs?: number;
46
+ /**
47
+ * Whether to clean up temporary files created during texture compression. See
48
+ * verbose log output for temporary file paths. Default: true.
49
+ */
50
+ cleanup?: boolean;
51
+ /**
52
+ * Attempts to avoid processing images that could exceed memory or other other
53
+ * limits, throwing an error instead. Default: true.
54
+ * @experimental
55
+ */
56
+ limitInputPixels?: boolean;
57
+ }
58
+ export interface ETC1SOptions extends GlobalOptions {
59
+ quality?: number;
60
+ compression?: number;
61
+ maxEndpoints?: number;
62
+ maxSelectors?: number;
63
+ rdo?: boolean;
64
+ rdoThreshold?: number;
65
+ }
66
+ export interface UASTCOptions extends GlobalOptions {
67
+ level?: number;
68
+ rdo?: boolean;
69
+ rdoLambda?: number;
70
+ rdoDictionarySize?: number;
71
+ rdoBlockScale?: number;
72
+ rdoStdDev?: number;
73
+ rdoMultithreading?: boolean;
74
+ zstd?: number;
75
+ }
76
+ export declare const ETC1S_DEFAULTS: Omit<ETC1SOptions, 'encoder' | 'mode'>;
77
+ export declare const UASTC_DEFAULTS: Omit<UASTCOptions, 'encoder' | 'mode'>;
78
+ export {};
@@ -0,0 +1,62 @@
1
+ import os from 'os';
2
+ import { TextureChannel } from "@gltf-transform/core";
3
+ import { TextureResizeFilter } from "@gltf-transform/functions";
4
+ const NUM_CPUS = os.cpus().length || 1; // microsoft/vscode#112122
5
+ const KTX_SOFTWARE_VERSION_MIN = '4.3.0';
6
+ const { R, G, A } = TextureChannel;
7
+ /**********************************************************************************************
8
+ * Interfaces.
9
+ */
10
+ export const Mode = {
11
+ ETC1S: 'etc1s',
12
+ UASTC: 'uastc',
13
+ };
14
+ export const Filter = {
15
+ BOX: 'box',
16
+ TENT: 'tent',
17
+ BELL: 'bell',
18
+ BSPLINE: 'b-spline',
19
+ MITCHELL: 'mitchell',
20
+ LANCZOS3: 'lanczos3',
21
+ LANCZOS4: 'lanczos4',
22
+ LANCZOS6: 'lanczos6',
23
+ LANCZOS12: 'lanczos12',
24
+ BLACKMAN: 'blackman',
25
+ KAISER: 'kaiser',
26
+ GAUSSIAN: 'gaussian',
27
+ CATMULLROM: 'catmullrom',
28
+ QUADRATIC_INTERP: 'quadratic_interp',
29
+ QUADRATIC_APPROX: 'quadratic_approx',
30
+ QUADRATIC_MIX: 'quadratic_mix',
31
+ };
32
+ const GLOBAL_DEFAULTS = {
33
+ resizeFilter: TextureResizeFilter.LANCZOS3,
34
+ filter: Filter.LANCZOS4,
35
+ filterScale: 1,
36
+ pattern: null,
37
+ slots: null,
38
+ // See: https://github.com/donmccurdy/glTF-Transform/pull/389#issuecomment-1089842185
39
+ jobs: 2 * NUM_CPUS,
40
+ cleanup: true,
41
+ limitInputPixels: true,
42
+ };
43
+ export const ETC1S_DEFAULTS = {
44
+ quality: 128,
45
+ compression: 1,
46
+ rdo: true,
47
+ rdoThreshold: 1.25,
48
+ maxSelectors: 0,
49
+ maxEndpoints: 0,
50
+ ...GLOBAL_DEFAULTS,
51
+ };
52
+ export const UASTC_DEFAULTS = {
53
+ level: 2,
54
+ rdo: false,
55
+ rdoLambda: 1.0,
56
+ rdoDictionarySize: 32768,
57
+ rdoBlockScale: 10.0,
58
+ rdoStdDev: 18.0,
59
+ rdoMultithreading: true,
60
+ zstd: 18,
61
+ ...GLOBAL_DEFAULTS,
62
+ };
@@ -1,4 +1,4 @@
1
- import packageJson from "../../package.json" assert { type: "json" };
1
+ import packageJson from "#package.json" assert { type: "json" };
2
2
  export function getVersion(_marklocal = true) {
3
3
  let res = packageJson.version;
4
4
  return res;
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@needle-tools/gltf-build-pipeline",
3
- "version": "2.1.0-alpha.6",
3
+ "version": "2.2.0-alpha.1",
4
4
  "description": "Pipeline and tools for optimizing gltf files using gltf-transform and compression settings within glTF extensions",
5
- "main": "index.mjs",
5
+ "main": "src/index.js",
6
6
  "type": "module",
7
7
  "readme": "README.md",
8
8
  "author": {
@@ -15,14 +15,17 @@
15
15
  "url": "git+https://github.com/needle-tools/needle-gltf-extensions.git"
16
16
  },
17
17
  "bin": {
18
- "gltf-build-pipeline": "./dist/src/cli/index.js",
19
- "needle-gltf": "./dist/src/cli/index.js"
18
+ "gltf-build-pipeline": "dist/cli/index.js",
19
+ "needle-gltf": "dist/cli/index.js"
20
+ },
21
+ "imports": {
22
+ "#package.json": "./package.json"
20
23
  },
21
24
  "scripts": {
22
25
  "start": "node scripts/index.mjs",
23
26
  "dev": "npm-watch compile",
24
- "compile": "tsc || exit 1",
25
- "clear-caches": "node scripts/clear-caches.mjs",
27
+ "compile": "node tools/prebuild.mjs && (tsc || exit 1) && node tools/postbuild.mjs",
28
+ "clear-caches": "node scripts/clear-caches.mjs",
26
29
  "limit-caches": "node scripts/limit-caches.mjs",
27
30
  "transform": "node scripts/index.mjs",
28
31
  "transform:make-progressive": "node scripts/make-progressive.mjs",
@@ -31,7 +34,6 @@
31
34
  "dev:pack-gltf": "npm run compile && node scripts/pack-gltf.mjs"
32
35
  },
33
36
  "devDependencies": {
34
- "@img/sharp-win32-x64": "^0.33.4",
35
37
  "@types/node": "^18.7.23",
36
38
  "npm-watch": "^0.13.0",
37
39
  "typescript": "^4.9.5"
@@ -44,7 +46,6 @@
44
46
  },
45
47
  "dependencies": {
46
48
  "@donmccurdy/caporal": "~0.0.10",
47
- "@gltf-transform/cli": "^4.0.1",
48
49
  "@gltf-transform/core": "^4.0.1",
49
50
  "@gltf-transform/extensions": "^4.0.1",
50
51
  "@gltf-transform/functions": "^4.0.1",
@@ -72,4 +73,4 @@
72
73
  "quiet": false
73
74
  }
74
75
  }
75
- }
76
+ }
package/tsconfig.json CHANGED
@@ -1,16 +1,20 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "target": "ESNext",
4
+ "rootDir": "src",
4
5
  "outDir": "dist",
5
6
  "declaration": true,
6
7
  "moduleResolution": "NodeNext",
7
8
  "module": "NodeNext",
8
9
  "esModuleInterop": true,
9
10
  "skipLibCheck": true,
10
- "resolveJsonModule": true
11
-
11
+ "resolveJsonModule": true,
12
+ "lib": [
13
+ "ESNext",
14
+ "DOM"
15
+ ],
12
16
  },
13
17
  "include": [
14
- "src/index.ts"
18
+ "src/index.ts",
15
19
  ]
16
20
  }
@@ -1,3 +0,0 @@
1
- {
2
- "usecase": "test"
3
- }
package/dist/package.json DELETED
@@ -1,75 +0,0 @@
1
- {
2
- "name": "@needle-tools/gltf-build-pipeline",
3
- "version": "2.1.0-alpha.6",
4
- "description": "Pipeline and tools for optimizing gltf files using gltf-transform and compression settings within glTF extensions",
5
- "main": "index.mjs",
6
- "type": "module",
7
- "readme": "README.md",
8
- "author": {
9
- "name": "Needle",
10
- "email": "help@needle.tools",
11
- "url": "https://needle.tools/"
12
- },
13
- "repository": {
14
- "type": "git",
15
- "url": "git+https://github.com/needle-tools/needle-gltf-extensions.git"
16
- },
17
- "bin": {
18
- "gltf-build-pipeline": "./dist/src/cli/index.js",
19
- "needle-gltf": "./dist/src/cli/index.js"
20
- },
21
- "scripts": {
22
- "start": "node scripts/index.mjs",
23
- "dev": "npm-watch compile",
24
- "compile": "tsc || exit 1",
25
- "clear-caches": "node scripts/clear-caches.mjs",
26
- "limit-caches": "node scripts/limit-caches.mjs",
27
- "transform": "node scripts/index.mjs",
28
- "transform:make-progressive": "node scripts/make-progressive.mjs",
29
- "transform:pack-gltf": "node --no-experimental-fetch scripts/pack-gltf.mjs",
30
- "pack-gltf": "node --no-experimental-fetch scripts/pack-gltf.mjs",
31
- "dev:pack-gltf": "npm run compile && node scripts/pack-gltf.mjs"
32
- },
33
- "devDependencies": {
34
- "@img/sharp-win32-x64": "^0.33.4",
35
- "@types/node": "^18.7.23",
36
- "npm-watch": "^0.13.0",
37
- "typescript": "^4.9.5"
38
- },
39
- "optionalDependencies": {
40
- "@img/sharp-linux-arm": "^0.33.4",
41
- "@img/sharp-win32-x64": "^0.33.4",
42
- "@img/sharp-darwin-arm64": "^0.33.4",
43
- "@img/sharp-darwin-x64": "^0.33.4"
44
- },
45
- "dependencies": {
46
- "@donmccurdy/caporal": "~0.0.10",
47
- "@gltf-transform/cli": "^4.0.1",
48
- "@gltf-transform/core": "^4.0.1",
49
- "@gltf-transform/extensions": "^4.0.1",
50
- "@gltf-transform/functions": "^4.0.1",
51
- "command-exists": "^1.2.9",
52
- "command-line-args": "^5.2.1",
53
- "draco3dgltf": "^1.5.3",
54
- "glob": "^7.2.0",
55
- "meshoptimizer": "^0.18.1",
56
- "micromatch": "^4.0.5",
57
- "ndarray": "^1.0.19",
58
- "ndarray-lanczos": "^0.3.0",
59
- "ndarray-pixels": "^2.0.1",
60
- "p-limit": "3.1.0",
61
- "semver": "^7.3.7",
62
- "sharp": "^0.33.4",
63
- "tmp": "^0.2.1",
64
- "uuid": "^9.0.1"
65
- },
66
- "watch": {
67
- "compile": {
68
- "patterns": [
69
- "src"
70
- ],
71
- "extensions": "ts",
72
- "quiet": false
73
- }
74
- }
75
- }
@@ -1,192 +0,0 @@
1
- #! /usr/bin/env node
2
- import { program } from '@donmccurdy/caporal';
3
- import { packGLTF } from "../scripts/index.js";
4
- import { printStats, writeFileStatsToFile } from '../utils/stats.js';
5
- import { getVersion } from '../utils/version.js';
6
- import { UsecaseOptions, createConfig, getConfig } from '../config/index.js';
7
- import { make_progressive } from '../transforms/needle_progressive.js';
8
- import { cacheSizeLimit, clearCache } from '../cache/cache.js';
9
- import { existsSync, statSync } from 'fs';
10
- import { foreachGLTF, getDirName } from '../utils/fileutils.js';
11
- import path, { dirname } from 'path';
12
- import { ERROR_CODES } from '../constants.js';
13
- // For testing / dev you can run `npm link` in the package directory
14
- // For removing the link run `npm rm --global @needle-tools/gltf-build-pipeline`
15
- program
16
- .command("version", "Print current verison")
17
- .action(async ({ logger }) => {
18
- logger.info(getVersion());
19
- })
20
- .command("clear-caches", "Clear all caches")
21
- .help(`Clears all cached files
22
- Caches are limited to ${cacheSizeLimit} MB disc space by default.
23
- `)
24
- .action(async ({ logger }) => {
25
- logger.info("Clearing caches");
26
- clearCache(logger);
27
- })
28
- .command("compress", "Compress glTF, GLB or VRM files")
29
- .help(`
30
- Compress a single glTF file. The input file is compressed and written to the output file.
31
- `)
32
- .argument('<input>', "The input glTF, GLB or VRM file that should be compressed OR a directory that contains files to be compressed")
33
- .argument('<output>', "The output file. If not provided, the input file will be overwritten.", { default: "" })
34
- .option("--usecase <usecase>", `The usecase for the compression allows you to determine how your asset is supposed to be used:\nThis will set compression and transform settings on a high level if no other settings are found.\nPossible options: [${UsecaseOptions.join(", ")}] e.g. \"--usecase ${UsecaseOptions[0]}\"`, { validator: program.STRING })
35
- .option("--config <config>", "The path to a JSON file containing the compression settings. This can also be a needle.config.json file with a \"compression\" object or usecase string.", { validator: program.STRING })
36
- .option("--stats", "Output a stats file next to input file (or inside the input directory). It will be named as the input file/directory \"<input>.needle.stats.json\"", { validator: program.BOOLEAN, default: false })
37
- .option("--cache", "Use \"--cache False\" to disable caching.\nWhen caching is enabled then previously processed files will be detected and loaded from disc cache instead of being processed again if settings or the file contents have not changed.", { validator: program.BOOLEAN, default: true })
38
- .option("--debug", "Enable for more verbose outout with \"--debug True\"", { validator: program.BOOLEAN, default: false })
39
- .action(async ({ logger, args, options }) => {
40
- logger.info("Transforming glTF file: " + args.input);
41
- const stats = {
42
- startTime: Date.now(),
43
- totalGPUMemoryInMB: 0,
44
- totalFilesProcessed: 0,
45
- totalFileSizeInMB: 0,
46
- totalFileSizeInMBBefore: 0,
47
- };
48
- const file = args.input.toString();
49
- let config = getConfig(options.config?.toString(), file);
50
- if (options.usecase) {
51
- logger.info("Using usecase: " + options.usecase);
52
- config = { ...createConfig(options.usecase.toString()), ...config, };
53
- if (config.usecase != "default" && config.usecase != options.usecase.toString()) {
54
- logger.warn(`Conflicting usecase. Your specific config at \"${options.config}\" has a different usecase than the one provided by the CLI: \"${options.usecase}\". Using the usecase from the config file.`);
55
- }
56
- else {
57
- config.usecase = options.usecase.toString();
58
- }
59
- }
60
- const debug = options.debug === true;
61
- const isDirectory = statSync(file).isDirectory();
62
- const outDir = isDirectory ? file : dirname(file);
63
- let outputIsDirectory = false;
64
- if (statSync(file).isDirectory() && options.output) {
65
- if (statSync(options.output.toString()).isDirectory()) {
66
- outputIsDirectory = true;
67
- if (!existsSync(options.output.toString())) {
68
- logger.error("Output directory does not exist: " + options.output.toString());
69
- return;
70
- }
71
- }
72
- else {
73
- logger.error("--output must be a directory if input is a directory");
74
- return;
75
- }
76
- }
77
- const file_stats = [];
78
- const set = new Set();
79
- await foreachGLTF(file, async (file) => {
80
- let outputFile = args.output?.toString();
81
- if (!outputFile?.length) {
82
- outputFile = file;
83
- }
84
- if (outputIsDirectory) {
85
- const filename = file.replaceAll("\\", "/").split("/").pop();
86
- outputFile = args.output?.toString() + "/" + filename;
87
- }
88
- const res = await packGLTF(file, outputFile, {
89
- config,
90
- packedFiles: set,
91
- stats,
92
- debug,
93
- useCache: options.cache !== false,
94
- logger: logger,
95
- file_stats: file_stats,
96
- });
97
- if (res === false) {
98
- process.exit(ERROR_CODES.PACKING_FAILED);
99
- }
100
- });
101
- printStats(stats, logger, config);
102
- console.log("Stats: ", options);
103
- if (options.stats) {
104
- const statsFile = (isDirectory ? (path.join(file, getDirName(file))) : file) + ".needle.stats.json";
105
- writeFileStatsToFile(config, file_stats, statsFile);
106
- }
107
- })
108
- .command("transform", "Transform (progressive loading) and compress glTF, GLB or VRM files")
109
- .help(`
110
- This will produce multiple versions of the input file, each with a different level of optimization.
111
- Each version will be compressed and written to the output directory.
112
- `)
113
- .argument('<input>', "The input glTF, GLB or VRM file that should be compressed OR a directory that contains files to be compressed")
114
- // .argument('<output>', "The output file. If not provided, the input file will be overwritten.", { default: "" })
115
- .option("--usecase <usecase>", `The usecase for the compression. This will set the compression settings. Possible options: [${UsecaseOptions.join(", ")}]`, { validator: program.STRING })
116
- .option("--config <config>", "The path to a JSON file containing the compression settings. This can also be a needle.config.json file with a \"compression\" object or usecase string.", { validator: program.STRING })
117
- .option("--stats", "Output a stats file next to input file (or inside the input directory). It will be named as the input file/directory \"<input>.needle.stats.json\"", { validator: program.BOOLEAN, default: false })
118
- .option("--cache", "Use \"--cache False\" to disable caching.\nWhen caching is enabled then previously processed files will be detected and loaded from disc cache instead of being processed again if settings or the file contents have not changed.", { validator: program.BOOLEAN, default: true })
119
- .option("--debug", "Enable for more verbose outout with \"--debug True\"", { validator: program.BOOLEAN, default: false })
120
- .action(async ({ logger, args, options }) => {
121
- logger.info("Transforming glTF file: " + args.input);
122
- const stats = {
123
- startTime: Date.now(),
124
- totalGPUMemoryInMB: 0,
125
- totalFilesProcessed: 0,
126
- totalFileSizeInMB: 0,
127
- totalFileSizeInMBBefore: 0,
128
- };
129
- const file = args.input.toString();
130
- let config = getConfig(options.config?.toString(), file);
131
- if (options.usecase) {
132
- logger.info("Using usecase: " + options.usecase);
133
- config = { ...createConfig(options.usecase.toString()), ...config, };
134
- if (config.usecase != "default" && config.usecase != options.usecase.toString()) {
135
- logger.warn(`Conflicting usecase. Your specific config at \"${options.config}\" has a different usecase than the one provided by the CLI: \"${options.usecase}\". Using the usecase from the config file.`);
136
- }
137
- else {
138
- config.usecase = options.usecase.toString();
139
- }
140
- }
141
- const isDirectory = statSync(file).isDirectory();
142
- const outDir = isDirectory ? file : dirname(file);
143
- const file_stats = [];
144
- const set = new Set();
145
- const useCache = options.cache !== false;
146
- const debug = options.debug === true;
147
- await foreachGLTF(file, async (file) => {
148
- logger.info("→ Transform " + file);
149
- const progressive_results = new Array();
150
- await make_progressive({
151
- path: file,
152
- results: progressive_results,
153
- config,
154
- debug,
155
- useCache,
156
- }).catch(err => {
157
- logger.error(err);
158
- process.exit(ERROR_CODES.PROGRESSIVE_FAILED);
159
- });
160
- const opts = {
161
- config,
162
- stats,
163
- packedFiles: set,
164
- debug,
165
- useCache,
166
- logger: logger,
167
- file_stats,
168
- };
169
- for (const res of progressive_results) {
170
- if (await packGLTF(res, res, opts) === false) {
171
- process.exit(ERROR_CODES.PACKING_FAILED);
172
- }
173
- }
174
- if (await packGLTF(file, file, opts) === false) {
175
- process.exit(ERROR_CODES.PACKING_FAILED);
176
- }
177
- });
178
- printStats(stats, logger, config);
179
- if (options.stats) {
180
- const statsFile = (isDirectory ? (path.join(file, getDirName(file))) : file) + ".needle.stats.json";
181
- writeFileStatsToFile(config, file_stats, statsFile);
182
- }
183
- });
184
- program.run().catch(err => {
185
- // Since we still invoke this package with paths when running npm scripts we need to ignore this error
186
- // We might want to move this code here into an extra package.
187
- if (err.message.includes("Unknown command") && !err.message.includes("Did you mean")) {
188
- // ignore
189
- return;
190
- }
191
- throw err;
192
- });
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes