@needle-tools/gltf-build-pipeline 1.6.0-alpha.7 → 2.1.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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,45 @@ 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.1.0-alpha] - 2023-06-03
8
+ - add: compression config support for high-level usecases that can be passed via commandline arguments or via config. Available options are currently `default`, `world` and `product`.
9
+ - add: texture LODs can now be generated by passing in the path to a compression config. `--config <path/to/config.json>`
10
+ - change: VRMs now use draco mesh compression by default
11
+
12
+ ### Config Examples
13
+ - `default` - use default compression. When not explictly configured textures will be compressed with ETC1S
14
+ - `product` - when not explictly configured textures will use UASTC for `metallicRoughnessTexture` and `normalTexture`
15
+ - `world` - generate texture LODs when enabled (and not explictly disabled by `textures: { lods: false }`)
16
+ ```json
17
+ {
18
+ "usecase": "default" // optional
19
+ "textures" : {
20
+ "lods": true
21
+ }
22
+ }
23
+ ```
24
+ The config can also be part of a `needle.config.json`:
25
+ ```json
26
+ {
27
+ ... regular needle.config
28
+ "gltf": {
29
+ "usecase": "default" // optional
30
+ "textures": {
31
+ "lods": true
32
+ }
33
+ }
34
+ }
35
+ ```
36
+
37
+ ## [2.0.0-alpha] - 2023-06-01
38
+ - update gltf-transform dependency to 4.x
39
+ - change: ensure ETC1S and UASTC textures are power of 2
40
+
41
+ - CLI: commands can be run with `needle-gltf <command>`
42
+ - CLI: add `--cache` to allow disabling cache with `--cache False` (default true)
43
+ - CLI: add `--stats` to produce `.needle.stats.json` (default false)
44
+ - CLI: change `--output` to be the optional second argument
45
+
7
46
  ## [1.6.0-alpha.7] - 2023-05-30
8
47
  - fix: npx cli command return wrong error due to `packGLTF` not returning a booelan
9
48
  - fix: mjs script import paths
package/README.md CHANGED
@@ -8,6 +8,24 @@ Includes support for generating LOD quality level for textures and meshes that c
8
8
  - [Example on Github](https://github.com/needle-engine/progressive-meshes-sample)
9
9
 
10
10
 
11
+ ## Usage
12
+
13
+ In general all files that have been processed by this package are valid glTF/GLB files.
14
+ The glTF Build Pipeline package is meant to be used as a last step before delivery.
15
+ That means:
16
+ - files are meant to be loaded e.g. in a web runtime like three.js
17
+ - files are not to be opened in Blender again to be further edited
18
+
19
+ To load progressively enhanced glTF files in any three.js based engine see examples at [@needle-tools/gltf-progressive](https://www.npmjs.com/package/@needle-tools/gltf-progressive)
20
+
21
+ ### Installation
22
+
23
+ `npx @needle-tools/gltf-build-pipeline@latest`
24
+
25
+ ### Commands
26
+ - `gltf-build-pipeline help` - use help to see options for each command
27
+ - `gltf-build-pipeline compress <input>`
28
+ - `gltf-build-pipeline transform <input>`
11
29
 
12
30
  # Contact ✒️
13
31
  <b>[🌵 needle — tools for creators](https://needle.tools)</b> •
@@ -0,0 +1,3 @@
1
+ {
2
+ "usecase": "test"
3
+ }
package/dist/package.json CHANGED
@@ -1,30 +1,51 @@
1
1
  {
2
2
  "name": "@needle-tools/gltf-build-pipeline",
3
- "version": "1.6.0-alpha.7",
3
+ "version": "2.1.0-alpha",
4
4
  "description": "Pipeline and tools for optimizing gltf files using gltf-transform and compression settings within glTF extensions",
5
5
  "main": "index.mjs",
6
6
  "type": "module",
7
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
+ },
8
17
  "bin": {
9
- "gltf-build-pipeline": "./dist/src/cli/index.js"
18
+ "gltf-build-pipeline": "./dist/src/cli/index.js",
19
+ "needle-gltf": "./dist/src/cli/index.js"
10
20
  },
11
21
  "scripts": {
12
22
  "start": "node scripts/index.mjs",
13
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",
14
27
  "transform": "node scripts/index.mjs",
15
28
  "transform:make-progressive": "node scripts/make-progressive.mjs",
16
29
  "transform:pack-gltf": "node --no-experimental-fetch scripts/pack-gltf.mjs",
17
30
  "pack-gltf": "node --no-experimental-fetch scripts/pack-gltf.mjs",
18
- "dev:pack-gltf": "npm run compile && node scripts/pack-gltf.mjs",
19
- "clear-caches": "node scripts/clear-caches.mjs",
20
- "limit-caches": "node scripts/limit-caches.mjs",
21
- "compile": "tsc || exit 1"
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"
22
42
  },
23
43
  "dependencies": {
24
44
  "@donmccurdy/caporal": "~0.0.10",
25
- "@gltf-transform/core": "^3.10.1",
26
- "@gltf-transform/extensions": "^3.10.1",
27
- "@gltf-transform/functions": "^3.10.1",
45
+ "@gltf-transform/cli": "^4.0.1",
46
+ "@gltf-transform/core": "^4.0.1",
47
+ "@gltf-transform/extensions": "^4.0.1",
48
+ "@gltf-transform/functions": "^4.0.1",
28
49
  "command-exists": "^1.2.9",
29
50
  "command-line-args": "^5.2.1",
30
51
  "draco3dgltf": "^1.5.3",
@@ -36,7 +57,7 @@
36
57
  "ndarray-pixels": "^2.0.1",
37
58
  "p-limit": "3.1.0",
38
59
  "semver": "^7.3.7",
39
- "sharp": "^0.32.6",
60
+ "sharp": "^0.33.4",
40
61
  "tmp": "^0.2.1",
41
62
  "uuid": "^9.0.1"
42
63
  },
@@ -48,20 +69,5 @@
48
69
  "extensions": "ts",
49
70
  "quiet": false
50
71
  }
51
- },
52
- "devDependencies": {
53
- "@types/node": "^18.7.23",
54
- "nodemon": "^3.1.2",
55
- "npm-watch": "^0.13.0",
56
- "typescript": "^4.9.5"
57
- },
58
- "author": {
59
- "name": "Needle",
60
- "email": "help@needle.tools",
61
- "url": "https://needle.tools/"
62
- },
63
- "repository": {
64
- "type": "git",
65
- "url": "git+https://github.com/needle-tools/needle-gltf-extensions.git"
66
72
  }
67
73
  }
@@ -48,6 +48,7 @@ export declare function getKeyWithHash(property: ExtensibleProperty): {
48
48
  /**
49
49
  * Get the hash for a property or an object.
50
50
  * @param property The property to get the hash for.
51
+ * @param level The current level of recursion.
51
52
  * @returns The hash as a number.
52
53
  */
53
54
  export declare function getHash(property: ExtensibleProperty | object, level?: number): number;
@@ -167,6 +167,7 @@ export function getKeyWithHash(property) {
167
167
  /**
168
168
  * Get the hash for a property or an object.
169
169
  * @param property The property to get the hash for.
170
+ * @param level The current level of recursion.
170
171
  * @returns The hash as a number.
171
172
  */
172
173
  export function getHash(property, level = 0) {
@@ -7,8 +7,8 @@ import { UsecaseOptions, createConfig, getConfig } from '../config/index.js';
7
7
  import { make_progressive } from '../transforms/needle_progressive.js';
8
8
  import { cacheSizeLimit, clearCache } from '../cache/cache.js';
9
9
  import { existsSync, statSync } from 'fs';
10
- import { foreachGLTF } from '../utils/fileutils.js';
11
- import { dirname } from 'path';
10
+ import { foreachGLTF, getDirName } from '../utils/fileutils.js';
11
+ import path, { dirname } from 'path';
12
12
  import { ERROR_CODES } from '../constants.js';
13
13
  // For testing / dev you can run `npm link` in the package directory
14
14
  // For removing the link run `npm rm --global @needle-tools/gltf-build-pipeline`
@@ -30,10 +30,12 @@ Caches are limited to ${cacheSizeLimit} MB disc space by default.
30
30
  Compress a single glTF file. The input file is compressed and written to the output file.
31
31
  `)
32
32
  .argument('<input>', "The input glTF, GLB or VRM file that should be compressed OR a directory that contains files to be compressed")
33
- .option("--usecase <usecase>", `The usecase for the compression. This will set the compression settings. Possible options: [${UsecaseOptions.join(", ")}] e.g. \`--usecase ${UsecaseOptions[0]}\"`, { validator: program.STRING })
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 })
34
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 })
35
- .option("--output <output>", "The output file. If not provided, the input file will be overwritten.", { validator: program.STRING })
36
- .option("--output-stats", "The path to a JSON file where the stats will be written to.", { 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 })
37
39
  .action(async ({ logger, args, options }) => {
38
40
  logger.info("Transforming glTF file: " + args.input);
39
41
  const stats = {
@@ -43,19 +45,21 @@ Compress a single glTF file. The input file is compressed and written to the out
43
45
  totalFileSizeInMB: 0,
44
46
  totalFileSizeInMBBefore: 0,
45
47
  };
46
- let config = getConfig(options.config?.toString());
48
+ const file = args.input.toString();
49
+ let config = getConfig(options.config?.toString(), file);
47
50
  if (options.usecase) {
48
51
  logger.info("Using usecase: " + options.usecase);
49
52
  config = { ...createConfig(options.usecase.toString()), ...config, };
50
- if (config.usecase != "unknown" && config.usecase != options.usecase.toString()) {
53
+ if (config.usecase != "default" && config.usecase != options.usecase.toString()) {
51
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.`);
52
55
  }
53
56
  else {
54
57
  config.usecase = options.usecase.toString();
55
58
  }
56
59
  }
57
- const file = args.input.toString();
58
- const outDir = statSync(file).isDirectory() ? file : dirname(file);
60
+ const debug = options.debug === true;
61
+ const isDirectory = statSync(file).isDirectory();
62
+ const outDir = isDirectory ? file : dirname(file);
59
63
  let outputIsDirectory = false;
60
64
  if (statSync(file).isDirectory() && options.output) {
61
65
  if (statSync(options.output.toString()).isDirectory()) {
@@ -73,17 +77,20 @@ Compress a single glTF file. The input file is compressed and written to the out
73
77
  const file_stats = [];
74
78
  const set = new Set();
75
79
  await foreachGLTF(file, async (file) => {
76
- let outputFile = options.output?.toString() || file;
80
+ let outputFile = args.output?.toString();
81
+ if (!outputFile?.length) {
82
+ outputFile = file;
83
+ }
77
84
  if (outputIsDirectory) {
78
85
  const filename = file.replaceAll("\\", "/").split("/").pop();
79
- outputFile = options.output?.toString() + "/" + filename;
86
+ outputFile = args.output?.toString() + "/" + filename;
80
87
  }
81
88
  const res = await packGLTF(file, outputFile, {
82
89
  config,
83
90
  packedFiles: set,
84
91
  stats,
85
- debug: false,
86
- useCache: true,
92
+ debug,
93
+ useCache: options.cache !== false,
87
94
  logger: logger,
88
95
  file_stats: file_stats,
89
96
  });
@@ -91,10 +98,11 @@ Compress a single glTF file. The input file is compressed and written to the out
91
98
  process.exit(ERROR_CODES.PACKING_FAILED);
92
99
  }
93
100
  });
94
- printStats(stats);
101
+ printStats(stats, logger, config);
95
102
  console.log("Stats: ", options);
96
- if (options.outputStats) {
97
- writeFileStatsToFile(file_stats, options.outputStats.toString());
103
+ if (options.stats) {
104
+ const statsFile = (isDirectory ? (path.join(file, getDirName(file))) : file) + ".needle.stats.json";
105
+ writeFileStatsToFile(config, file_stats, statsFile);
98
106
  }
99
107
  })
100
108
  .command("transform", "Transform (progressive loading) and compress glTF, GLB or VRM files")
@@ -103,10 +111,12 @@ This will produce multiple versions of the input file, each with a different lev
103
111
  Each version will be compressed and written to the output directory.
104
112
  `)
105
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: "" })
106
115
  .option("--usecase <usecase>", `The usecase for the compression. This will set the compression settings. Possible options: [${UsecaseOptions.join(", ")}]`, { validator: program.STRING })
107
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 })
108
- // .option("--output <output>", "The output directory. If not provided, the input files will be overwritten.", { validator: program.STRING })
109
- .option("--output-stats", "The path to a JSON file where the stats will be written to.", { 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 })
110
120
  .action(async ({ logger, args, options }) => {
111
121
  logger.info("Transforming glTF file: " + args.input);
112
122
  const stats = {
@@ -116,30 +126,33 @@ Each version will be compressed and written to the output directory.
116
126
  totalFileSizeInMB: 0,
117
127
  totalFileSizeInMBBefore: 0,
118
128
  };
119
- let config = getConfig(options.config?.toString());
129
+ const file = args.input.toString();
130
+ let config = getConfig(options.config?.toString(), file);
120
131
  if (options.usecase) {
121
132
  logger.info("Using usecase: " + options.usecase);
122
133
  config = { ...createConfig(options.usecase.toString()), ...config, };
123
- if (config.usecase != "unknown" && config.usecase != options.usecase.toString()) {
134
+ if (config.usecase != "default" && config.usecase != options.usecase.toString()) {
124
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.`);
125
136
  }
126
137
  else {
127
138
  config.usecase = options.usecase.toString();
128
139
  }
129
140
  }
130
- const file = args.input.toString();
131
- const outDir = statSync(file).isDirectory() ? file : dirname(file);
141
+ const isDirectory = statSync(file).isDirectory();
142
+ const outDir = isDirectory ? file : dirname(file);
132
143
  const file_stats = [];
133
144
  const set = new Set();
145
+ const useCache = options.cache !== false;
146
+ const debug = options.debug === true;
134
147
  await foreachGLTF(file, async (file) => {
135
- logger.info("> Transform " + file);
148
+ logger.info("→ Transform " + file);
136
149
  const progressive_results = new Array();
137
150
  await make_progressive({
138
151
  path: file,
139
152
  results: progressive_results,
140
153
  config,
141
- debug: false,
142
- useCache: true,
154
+ debug,
155
+ useCache,
143
156
  }).catch(err => {
144
157
  logger.error(err);
145
158
  process.exit(ERROR_CODES.PROGRESSIVE_FAILED);
@@ -148,25 +161,24 @@ Each version will be compressed and written to the output directory.
148
161
  config,
149
162
  stats,
150
163
  packedFiles: set,
151
- debug: false,
152
- useCache: true,
164
+ debug,
165
+ useCache,
153
166
  logger: logger,
154
167
  file_stats,
155
168
  };
156
169
  for (const res of progressive_results) {
157
- logger.info("> Compress " + res);
158
170
  if (await packGLTF(res, res, opts) === false) {
159
171
  process.exit(ERROR_CODES.PACKING_FAILED);
160
172
  }
161
173
  }
162
- logger.info("> Compress " + file);
163
174
  if (await packGLTF(file, file, opts) === false) {
164
175
  process.exit(ERROR_CODES.PACKING_FAILED);
165
176
  }
166
177
  });
167
- printStats(stats);
168
- if (options.outputStats) {
169
- writeFileStatsToFile(file_stats, options.outputStats.toString());
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);
170
182
  }
171
183
  });
172
184
  program.run();
@@ -1,4 +1,4 @@
1
- declare type Usecase = "unknown" | "product" | "world";
1
+ declare type Usecase = "default" | "product" | "world";
2
2
  export declare const UsecaseOptions: string[];
3
3
  /**
4
4
  * The config object contains information about how a file should be processed.
@@ -6,6 +6,9 @@ export declare const UsecaseOptions: string[];
6
6
  export type Config = {
7
7
  /** High-level configuration */
8
8
  usecase: Usecase;
9
+ textures?: {
10
+ lods: boolean;
11
+ };
9
12
  };
10
13
  /**
11
14
  * Create a config object with the given usecase.
@@ -39,5 +42,6 @@ export declare function createConfig(usecase: string): {
39
42
  *
40
43
  *
41
44
  */
42
- export declare function getConfig(path?: string): Config;
45
+ export declare function getConfig(configPath?: string, fallbackSearchDirectory?: string): Config;
46
+ export declare function searchConfig(directory: string): Config | null;
43
47
  export {};
@@ -1,7 +1,8 @@
1
- import { existsSync, readFileSync } from "fs";
1
+ import { existsSync, readFileSync, statSync } from "fs";
2
+ import path, { dirname } from "path";
2
3
  export const UsecaseOptions = ["product"];
3
4
  const defaultConfig = {
4
- usecase: "unknown",
5
+ usecase: "default",
5
6
  };
6
7
  /**
7
8
  * Create a config object with the given usecase.
@@ -40,49 +41,46 @@ export function createConfig(usecase) {
40
41
  *
41
42
  *
42
43
  */
43
- export function getConfig(path) {
44
- if (path != null) {
45
- if (!existsSync(path)) {
46
- throw new Error(`No config found at \"${path}\"`);
44
+ export function getConfig(configPath, fallbackSearchDirectory) {
45
+ if (configPath != null) {
46
+ if (!existsSync(configPath)) {
47
+ throw new Error(`No config found at \"${configPath}\"`);
48
+ }
49
+ const res = resolveConfig(configPath, readFileSync(configPath, "utf8"));
50
+ if (res) {
51
+ return res;
52
+ }
53
+ else {
54
+ throw new Error(`Failed parsing config at \"${configPath}\"`);
47
55
  }
48
- return resolveConfig(path, readFileSync(path, "utf8"));
49
56
  }
50
57
  if (_cachedConfig !== undefined) {
51
58
  return _cachedConfig;
52
59
  }
53
60
  // Resolve the config once and cache it
54
- const result = internalResolveConfig();
55
- if (result) {
56
- console.log("INFO: Using optimization config with usecase: " + result.usecase);
57
- _cachedConfig = result;
61
+ // Check if a needle.config.json exists in the directory from which the script is run (or any parent directory)
62
+ if (fallbackSearchDirectory) {
63
+ const result = searchConfig(fallbackSearchDirectory);
64
+ if (result) {
65
+ _cachedConfig = { ...defaultConfig, ...result };
66
+ console.log("INFO: Using config with usecase: " + _cachedConfig.usecase);
67
+ }
58
68
  }
59
- else {
60
- console.log("INFO: No optimization config found. Using default config.");
69
+ // If no config was found, use the default config
70
+ if (!_cachedConfig) {
71
+ console.log("INFO: No config found. Using default config.");
61
72
  _cachedConfig = defaultConfig;
62
73
  }
63
74
  return _cachedConfig;
64
75
  }
65
76
  let _cachedConfig = undefined;
66
- function internalResolveConfig() {
67
- // Check if a config path is passed as an argument
68
- const args = process.argv.slice(2);
69
- for (let i = 0; i < args.length; i++) {
70
- if (args[i] === "--config") {
71
- if (i + 1 < args.length) {
72
- const configPath = args[i + 1];
73
- if (existsSync(configPath)) {
74
- const content = readFileSync(configPath, "utf8");
75
- return resolveConfig(configPath, content);
76
- }
77
- throw new Error(`Config file not found at path configured by CLI --config argument: \"${configPath}\"`);
78
- }
79
- }
77
+ export function searchConfig(directory) {
78
+ if (statSync(directory).isFile()) {
79
+ directory = dirname(directory);
80
80
  }
81
- // Check if a needle.config.json exists in the directory from which the script is run (or any parent directory)
82
- const workingDir = process.cwd();
83
- let directory = workingDir.replaceAll("\\", "/");
81
+ directory = directory.replaceAll("\\", "/");
84
82
  while (directory.length > 0) {
85
- const configPath = `${directory}/needle.config.json`;
83
+ const configPath = path.join(directory, "needle.config.json");
86
84
  if (existsSync(configPath)) {
87
85
  const content = readFileSync(configPath, "utf8");
88
86
  const config = resolveConfig(configPath, content);
@@ -103,16 +101,16 @@ function resolveConfig(filename, content) {
103
101
  if (filename.endsWith("needle.config.json")) {
104
102
  try {
105
103
  const config = JSON.parse(content);
106
- if (config.optimization) {
107
- console.log("INFO: Found optimization config at \"" + filename + "\".");
104
+ if (config.gltf) {
105
+ console.log("INFO: Found \"gltf\" config at \"" + filename + "\".");
108
106
  // If the optimization config is a string, it's the usecase
109
- if (typeof config.optimization === "string") {
110
- return createConfig(config.optimization);
107
+ if (typeof config.gltf === "string") {
108
+ return createConfig(config.gltf);
111
109
  }
112
- return config.optimization;
110
+ return config.gltf;
113
111
  }
114
112
  else {
115
- console.log("INFO: No optimization config found in needle.config.json. Using default config.");
113
+ console.log("INFO: No \"gltf\" config found in needle.config.json. Using default config.");
116
114
  return null;
117
115
  }
118
116
  }
@@ -15,7 +15,7 @@ export interface IOpaqueProperty {
15
15
  /** register a reference so gltf-transform will not remove it in e.g. dedup
16
16
  * https://github.com/donmccurdy/glTF-Transform/issues/980#issuecomment-1585748202
17
17
  */
18
- setReference(name: string, value: any): any;
18
+ setReference(name: string, value: any, attributes?: Record<string, any>): void;
19
19
  }
20
20
  export interface IExtensionWriter {
21
21
  /** used by progressive texture extension to pass an extension between documents */