@needle-tools/gltf-build-pipeline 2.15.9 → 2.15.10-next.0a00d78
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/dist/cli/index.js +3 -2
- package/dist/config/index.js +12 -8
- package/dist/extensions/NEEDLE_lightmaps/index.js +11 -9
- package/dist/extensions/NEEDLE_opaque/NEEDLE_opaque.js +1 -1
- package/dist/scripts/stats.d.ts +4 -1
- package/dist/scripts/stats.js +3 -2
- package/dist/transforms/needle_progressive.js +8 -3
- package/dist/transforms/needle_toktx.js +12 -8
- package/dist/utils/version.gen.d.ts +1 -1
- package/dist/utils/version.gen.js +1 -1
- package/package.json +7 -7
package/dist/cli/index.js
CHANGED
|
@@ -31,13 +31,14 @@ Caches are limited to ${cacheSizeLimit} MB disc space by default.
|
|
|
31
31
|
`)
|
|
32
32
|
.argument('<input>', "Path to a glTF, GLB or VRM file OR a directory")
|
|
33
33
|
.argument('<output>', "Output path for the stats", { default: null, validator: program.STRING })
|
|
34
|
+
.option("--verbose", "Enable verbose output", { validator: program.BOOLEAN, default: false })
|
|
34
35
|
.action(async ({ logger, args, options }) => {
|
|
35
|
-
logger.level = "info";
|
|
36
|
+
logger.level = options.verbose ? "debug" : "info";
|
|
36
37
|
logger.info("Calculating stats for \"" + args.input + "\"");
|
|
37
38
|
const input = args.input.toString();
|
|
38
39
|
const filestats = new Array();
|
|
39
40
|
await foreachGLTF(input, async (file) => {
|
|
40
|
-
const stats = await calculateStats(file);
|
|
41
|
+
const stats = await calculateStats(file, { verbose: !!options.verbose });
|
|
41
42
|
filestats.push(stats);
|
|
42
43
|
});
|
|
43
44
|
let output = args.output?.toString();
|
package/dist/config/index.js
CHANGED
|
@@ -79,12 +79,12 @@ export function getConfig(configPath, fallbackSearchDirectory, opts) {
|
|
|
79
79
|
const result = searchConfig(fallbackSearchDirectory, logger);
|
|
80
80
|
if (result) {
|
|
81
81
|
_cachedConfig = { ...defaultConfig, ...result };
|
|
82
|
-
logger.info(
|
|
82
|
+
logger.info(`Using config with usecase: ${_cachedConfig.usecase}`);
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
// If no config was found, use the default config
|
|
86
86
|
if (!_cachedConfig) {
|
|
87
|
-
logger.
|
|
87
|
+
logger.debug("No config found. Using default config.");
|
|
88
88
|
_cachedConfig = defaultConfig;
|
|
89
89
|
}
|
|
90
90
|
return _cachedConfig;
|
|
@@ -112,13 +112,14 @@ export function searchConfig(directory, logger) {
|
|
|
112
112
|
}
|
|
113
113
|
return null;
|
|
114
114
|
}
|
|
115
|
+
const loggedConfigPaths = new Set();
|
|
115
116
|
function resolveConfig(filename, content, logger) {
|
|
116
117
|
// Handle config being part of `needle.config.json`
|
|
117
118
|
if (filename.endsWith("needle.config.json")) {
|
|
118
119
|
try {
|
|
119
120
|
const config = JSON.parse(content);
|
|
120
121
|
if (config.gltf) {
|
|
121
|
-
logger.info(
|
|
122
|
+
logger.info(`Found \"gltf\" config at \"${filename.replaceAll("\\", "/")}\".`);
|
|
122
123
|
// If the optimization config is a string, it's the usecase
|
|
123
124
|
if (typeof config.gltf === "string") {
|
|
124
125
|
return createConfig(config.gltf);
|
|
@@ -126,12 +127,15 @@ function resolveConfig(filename, content, logger) {
|
|
|
126
127
|
return config.gltf;
|
|
127
128
|
}
|
|
128
129
|
else {
|
|
129
|
-
|
|
130
|
+
if (!loggedConfigPaths.has(filename)) {
|
|
131
|
+
logger.debug(`No \"gltf\" config found in needle.config.json at \"${filename.replaceAll("\\", "/")}\". You may add a \"gltf\" field to the config with the optimization config. Example: { \"gltf\": { \"usecase\": \"product\" } }. Using default config for glTF optimization now.`);
|
|
132
|
+
loggedConfigPaths.add(filename);
|
|
133
|
+
}
|
|
130
134
|
return null;
|
|
131
135
|
}
|
|
132
136
|
}
|
|
133
137
|
catch (err) {
|
|
134
|
-
logger.error(`Failed parsing config \"${err.message}\" at ${filename}`);
|
|
138
|
+
logger.error(`Failed parsing config \"${err.message}\" at ${filename.replaceAll("\\", "/")}`);
|
|
135
139
|
}
|
|
136
140
|
}
|
|
137
141
|
// Handle config being the content of the file
|
|
@@ -141,12 +145,12 @@ function resolveConfig(filename, content, logger) {
|
|
|
141
145
|
}
|
|
142
146
|
catch (err) {
|
|
143
147
|
if (err instanceof Error)
|
|
144
|
-
logger.error(`Failed parsing config \"${err.message}\" at ${filename}`);
|
|
148
|
+
logger.error(`Failed parsing config \"${err.message}\" at ${filename.replaceAll("\\", "/")}`);
|
|
145
149
|
else
|
|
146
|
-
logger.error(`Failed parsing config \"${err}\" at ${filename}`);
|
|
150
|
+
logger.error(`Failed parsing config \"${err}\" at ${filename.replaceAll("\\", "/")}`);
|
|
147
151
|
}
|
|
148
152
|
}
|
|
149
153
|
else {
|
|
150
|
-
logger.error("Invalid config file format. Expected JSON object at " + filename);
|
|
154
|
+
logger.error("Invalid config file format. Expected JSON object at " + filename.replaceAll("\\", "/"));
|
|
151
155
|
}
|
|
152
156
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Extension, ExtensionProperty, PropertyType } from '@gltf-transform/core';
|
|
1
|
+
import { Extension, ExtensionProperty, PropertyType, TextureChannel } from '@gltf-transform/core';
|
|
2
2
|
import { existsSync, readFileSync } from 'fs';
|
|
3
3
|
import { join, dirname } from 'path';
|
|
4
4
|
import { isLightmap } from '../../utils/texture.js';
|
|
@@ -21,8 +21,8 @@ class LightmapProperty extends ExtensionProperty {
|
|
|
21
21
|
return Object.assign(super.getDefaults(), {});
|
|
22
22
|
}
|
|
23
23
|
/** Public wrapper around protected setRef to register references */
|
|
24
|
-
setReference(name, value) {
|
|
25
|
-
this.setRef(name, value);
|
|
24
|
+
setReference(name, value, attributes) {
|
|
25
|
+
this.setRef(name, value, attributes);
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
@@ -110,7 +110,7 @@ export class NEEDLE_lightmaps_ext extends Extension {
|
|
|
110
110
|
let texture = null;
|
|
111
111
|
let inputTextureDef;
|
|
112
112
|
if (entry.pointer.startsWith('/textures/') || entry.pointer.startsWith('textures/')) {
|
|
113
|
-
// Embedded pointer — resolve
|
|
113
|
+
// Embedded pointer — resolve Found lightmapto Texture via the JSON texture definition
|
|
114
114
|
const prefix = entry.pointer.startsWith('/') ? '/textures/' : 'textures/';
|
|
115
115
|
const texIndex = parseInt(entry.pointer.substring(prefix.length));
|
|
116
116
|
const texDef = textureDefs[texIndex];
|
|
@@ -142,8 +142,9 @@ export class NEEDLE_lightmaps_ext extends Extension {
|
|
|
142
142
|
if (texture) {
|
|
143
143
|
this.resolved.push({ entry, texture, inputTextureDef });
|
|
144
144
|
// Set a reference so gltf-transform won't prune/dedup this texture
|
|
145
|
-
const refName = `
|
|
146
|
-
|
|
145
|
+
const refName = `lightmapTexture_${entry.pointer}`;
|
|
146
|
+
const channels = TextureChannel.R | TextureChannel.G | TextureChannel.B | TextureChannel.A;
|
|
147
|
+
this.lightmapProp.setReference(refName, texture, { channels });
|
|
147
148
|
// Also keep a reference to the buffer
|
|
148
149
|
const buffer = this.document.getRoot().listBuffers()[0];
|
|
149
150
|
if (buffer) {
|
|
@@ -151,7 +152,7 @@ export class NEEDLE_lightmaps_ext extends Extension {
|
|
|
151
152
|
}
|
|
152
153
|
}
|
|
153
154
|
else {
|
|
154
|
-
|
|
155
|
+
this.document.getLogger().error(`NEEDLE_lightmaps: Could not resolve texture for pointer "${entry.pointer}"`);
|
|
155
156
|
testAssert(false, `NEEDLE_lightmaps: Could not resolve texture for pointer "${entry.pointer}"`);
|
|
156
157
|
}
|
|
157
158
|
}
|
|
@@ -160,8 +161,9 @@ export class NEEDLE_lightmaps_ext extends Extension {
|
|
|
160
161
|
for (const tex of textures) {
|
|
161
162
|
if (isLightmap(tex) && tex.getMimeType() !== 'image/exr') {
|
|
162
163
|
const refName = `lightmapTexture_${tex.getName()}`;
|
|
163
|
-
|
|
164
|
-
|
|
164
|
+
const channels = TextureChannel.R | TextureChannel.G | TextureChannel.B | TextureChannel.A;
|
|
165
|
+
this.lightmapProp.setReference(refName, tex, { channels });
|
|
166
|
+
this.document.getLogger().debug(`[NEEDLE_lightmaps] Found lightmap texture "${tex.getName()}" (${tex.getMimeType()})`);
|
|
165
167
|
}
|
|
166
168
|
}
|
|
167
169
|
return this;
|
|
@@ -200,7 +200,7 @@ export function createOpaqueExtension(name, types, opts) {
|
|
|
200
200
|
prop.setReference("lightmapTexture", tex, { channels: TextureChannel.R | TextureChannel.G | TextureChannel.B | TextureChannel.A });
|
|
201
201
|
const texDef = textureDefinitions[i]; // @TODO: textureDefinitions and textures might not be in the same order - we need to find the texture by source index instead of assuming the order is the same
|
|
202
202
|
const name = tex.getName() || texDef?.name;
|
|
203
|
-
logger.
|
|
203
|
+
logger.debug(`Found lightmap texture \"${name}\" (${tex.getMimeType()}) in ${EXTENSION_NAME}`);
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
206
|
}
|
package/dist/scripts/stats.d.ts
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
import { FileStats } from "../utils/stats.js";
|
|
2
|
-
export
|
|
2
|
+
export interface StatsOptions {
|
|
3
|
+
verbose?: boolean;
|
|
4
|
+
}
|
|
5
|
+
export declare function calculateStats(inputfile: string, options?: StatsOptions): Promise<FileStats>;
|
package/dist/scripts/stats.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { NodeIO } from "@gltf-transform/core";
|
|
1
|
+
import { Logger, NodeIO, Verbosity } from "@gltf-transform/core";
|
|
2
2
|
import { ALL_EXTENSIONS } from "@gltf-transform/extensions";
|
|
3
3
|
import draco3d from 'draco3dgltf';
|
|
4
4
|
import { MeshoptDecoder, MeshoptEncoder } from "meshoptimizer";
|
|
5
5
|
import { createNewFilestats } from "../utils/stats.js";
|
|
6
|
-
export async function calculateStats(inputfile) {
|
|
6
|
+
export async function calculateStats(inputfile, options) {
|
|
7
7
|
const io = new NodeIO()
|
|
8
|
+
.setLogger(new Logger(options?.verbose ? Verbosity.DEBUG : Verbosity.ERROR))
|
|
8
9
|
.registerExtensions(ALL_EXTENSIONS)
|
|
9
10
|
.registerDependencies({
|
|
10
11
|
'draco3d.decoder': await draco3d.createDecoderModule(),
|
|
@@ -376,6 +376,11 @@ async function createTextureLod(args, opts) {
|
|
|
376
376
|
setLightmapName(newTexture);
|
|
377
377
|
break;
|
|
378
378
|
default:
|
|
379
|
+
if (slot.startsWith("lightmapTexture_") || slot.startsWith("lightmap_")) {
|
|
380
|
+
newMaterial.setBaseColorTexture(newTexture);
|
|
381
|
+
setLightmapName(newTexture);
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
379
384
|
if (slot.startsWith("/textures/")) {
|
|
380
385
|
// Ensure the texture is using the alpha channel since we don't know what slots the original texture was used for
|
|
381
386
|
// see https://linear.app/needle/issue/NE-4985
|
|
@@ -408,11 +413,11 @@ async function createTextureLod(args, opts) {
|
|
|
408
413
|
// Pass compression settings to the new texture
|
|
409
414
|
const compressionSettingsExt = texture.getExtension(NEEDLE_compression_texture.EXTENSION_NAME);
|
|
410
415
|
if (compressionSettingsExt !== undefined && compressionSettingsExt !== null && compressionSettingsExt.getExtensionDefinition) {
|
|
411
|
-
logger.
|
|
416
|
+
logger.debug("> Pass compression extension to new texture");
|
|
412
417
|
const newExt = newDoc.createExtension(NEEDLE_compression_texture);
|
|
413
418
|
const compressionSettings = compressionSettingsExt.getExtensionDefinition();
|
|
414
|
-
if (compressionSettings?.mode === "none") {
|
|
415
|
-
//
|
|
419
|
+
if (compressionSettings?.mode === "none" || compressionSettings?.mode === "webp" || compressionSettings?.mode === "UASTC" || compressionSettings?.mode === "ETC1S") {
|
|
420
|
+
// explicit compression mode set by the user — keep it for LOD textures
|
|
416
421
|
}
|
|
417
422
|
else {
|
|
418
423
|
switch (opts.config.usecase) {
|
|
@@ -53,6 +53,11 @@ export class NeedleToktxContext {
|
|
|
53
53
|
if (existsSync(defaultPath))
|
|
54
54
|
this.env.PATH = `${this.env.PATH};${defaultPath}`;
|
|
55
55
|
}
|
|
56
|
+
else {
|
|
57
|
+
const defaultPath = "/usr/local/bin";
|
|
58
|
+
if (existsSync(defaultPath))
|
|
59
|
+
this.env.PATH = `${this.env.PATH}:${defaultPath}`;
|
|
60
|
+
}
|
|
56
61
|
// append default system paths
|
|
57
62
|
if (isUsingWindows && process.env.Path?.length)
|
|
58
63
|
this.env.PATH = `${this.env.PATH};${process.env.Path}`;
|
|
@@ -60,11 +65,11 @@ export class NeedleToktxContext {
|
|
|
60
65
|
if (options.debug) {
|
|
61
66
|
console.log("INFO: Found these paths including ktx:\n\t" + ktxPaths.join("\n\t") + "");
|
|
62
67
|
}
|
|
63
|
-
else if (ktxPaths?.length <= 0) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
+
// else if (ktxPaths?.length <= 0) {
|
|
69
|
+
// if (didLogEnvironmentInfo === false)
|
|
70
|
+
// console.log("INFO: You may set the 'NEEDLE_TOKTX' environment variable to specify the directory of the toktx installation (path/to/bin)")
|
|
71
|
+
// didLogEnvironmentInfo = true;
|
|
72
|
+
// }
|
|
68
73
|
}
|
|
69
74
|
async prepare(document) {
|
|
70
75
|
try {
|
|
@@ -72,7 +77,7 @@ export class NeedleToktxContext {
|
|
|
72
77
|
const logger = document.getLogger();
|
|
73
78
|
this.version = await checkKTXSoftware(logger, this.env);
|
|
74
79
|
if (this.version === null) {
|
|
75
|
-
logger.warn(`WARN: toktx not found, skipping texture compression. Please install KTX-Software >= ${KTX_SOFTWARE_VERSION_MIN}`);
|
|
80
|
+
logger.warn(`WARN: toktx not found, skipping KTX2 texture compression. Please install KTX-Software >= ${KTX_SOFTWARE_VERSION_MIN}. You may set the 'NEEDLE_TOKTX' environment variable to specify the directory of the toktx installation (path/to/bin)`);
|
|
76
81
|
this._prepareFailed = true;
|
|
77
82
|
return false;
|
|
78
83
|
}
|
|
@@ -84,8 +89,7 @@ export class NeedleToktxContext {
|
|
|
84
89
|
this.numTextures = document.getRoot().listTextures().length;
|
|
85
90
|
}
|
|
86
91
|
catch (e) {
|
|
87
|
-
console.error(e);
|
|
88
|
-
console.log("Error preparing toktx transform");
|
|
92
|
+
console.error("ERR: preparing toktx transform failed: " + e);
|
|
89
93
|
this._prepareFailed = true;
|
|
90
94
|
return false;
|
|
91
95
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "2.15.
|
|
1
|
+
export declare const version = "2.15.10-next.0a00d78";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = "2.15.
|
|
1
|
+
export const version = "2.15.10-next.0a00d78";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needle-tools/gltf-build-pipeline",
|
|
3
|
-
"version": "2.15.
|
|
3
|
+
"version": "2.15.10-next.0a00d78",
|
|
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",
|
|
@@ -55,10 +55,10 @@
|
|
|
55
55
|
"vitest": "^3.1.1"
|
|
56
56
|
},
|
|
57
57
|
"optionalDependencies": {
|
|
58
|
-
"@img/sharp-darwin-arm64": "^0.
|
|
59
|
-
"@img/sharp-darwin-x64": "^0.
|
|
60
|
-
"@img/sharp-linux-arm": "^0.
|
|
61
|
-
"@img/sharp-win32-x64": "^0.
|
|
58
|
+
"@img/sharp-darwin-arm64": "^0.34.5",
|
|
59
|
+
"@img/sharp-darwin-x64": "^0.34.5",
|
|
60
|
+
"@img/sharp-linux-arm": "^0.34.5",
|
|
61
|
+
"@img/sharp-win32-x64": "^0.34.5"
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"@donmccurdy/caporal": "~0.0.10",
|
|
@@ -73,10 +73,10 @@
|
|
|
73
73
|
"micromatch": "^4.0.5",
|
|
74
74
|
"ndarray": "^1.0.19",
|
|
75
75
|
"ndarray-lanczos": "^0.3.0",
|
|
76
|
-
"ndarray-pixels": "^
|
|
76
|
+
"ndarray-pixels": "^5.0.1",
|
|
77
77
|
"p-limit": "3.1.0",
|
|
78
78
|
"semver": "^7.3.7",
|
|
79
|
-
"sharp": "^0.
|
|
79
|
+
"sharp": "^0.34.5",
|
|
80
80
|
"tmp": "^0.2.1",
|
|
81
81
|
"uuid": "^9.0.1",
|
|
82
82
|
"xxhash-wasm": "^1.1.0"
|