@gltf-transform/cli
Advanced tools
+10
-9
| { | ||
| "name": "@gltf-transform/cli", | ||
| "version": "0.4.0-alpha.2", | ||
| "version": "0.4.0-alpha.3", | ||
| "repository": "github:donmccurdy/glTF-Transform", | ||
@@ -17,10 +17,11 @@ "description": "CLI interface to glTF Transform", | ||
| "dependencies": { | ||
| "@gltf-transform/ao": "^0.4.0-alpha.2", | ||
| "@gltf-transform/colorspace": "^0.4.0-alpha.2", | ||
| "@gltf-transform/core": "^0.4.0-alpha.2", | ||
| "@gltf-transform/extensions": "^0.4.0-alpha.2", | ||
| "@gltf-transform/prune": "^0.4.0-alpha.2", | ||
| "@gltf-transform/split": "^0.4.0-alpha.2", | ||
| "@gltf-transform/ao": "^0.4.0-alpha.3", | ||
| "@gltf-transform/colorspace": "^0.4.0-alpha.3", | ||
| "@gltf-transform/core": "^0.4.0-alpha.3", | ||
| "@gltf-transform/extensions": "^0.4.0-alpha.3", | ||
| "@gltf-transform/prune": "^0.4.0-alpha.3", | ||
| "@gltf-transform/split": "^0.4.0-alpha.3", | ||
| "caporal": "^1.3.0", | ||
| "gl": "^4.5.0" | ||
| "gl": "^4.5.0", | ||
| "minimatch": "^3.0.4" | ||
| }, | ||
@@ -33,3 +34,3 @@ "files": [ | ||
| ], | ||
| "gitHead": "f744d10a839f51261f72ddc81c8eb559b2fe70ae" | ||
| "gitHead": "6ab967a22de4bfb41845d8d0cfb9ca743f324adb" | ||
| } |
+6
-6
@@ -15,3 +15,3 @@ #!/usr/bin/env node | ||
| const { prune } = require('@gltf-transform/prune'); | ||
| const { toktx, Mode, TOKTX_OPTIONS } = require('./toktx'); | ||
| const { toktx, Mode, TOKTX_DEFAULTS } = require('./toktx'); | ||
@@ -112,3 +112,3 @@ const io = new NodeIO(fs, path).registerExtensions(KHRONOS_EXTENSIONS); | ||
| 'Basis Universal compression mode ["etc1s" = low quality, "uastc" = high quality]', | ||
| ['etc1s', 'uastc'], 'etc1s' | ||
| [Mode.ETC1S, Mode.UASTC], Mode.ETC1S | ||
| ) | ||
@@ -118,3 +118,3 @@ .option( | ||
| 'Quality level, where higher levels mean larger files [0 – 1]', | ||
| program.FLOAT, TOKTX_OPTIONS.quality | ||
| program.FLOAT, TOKTX_DEFAULTS.quality | ||
| ) | ||
@@ -127,5 +127,5 @@ .option( | ||
| .option( | ||
| '--maps <maps>', | ||
| 'Texture slots to compress. ["*", "normal"]', | ||
| ["*", "normal"], "*" | ||
| '--slots <slots>', | ||
| 'Texture slots to compress (glob expression)', | ||
| program.STRING, "*" | ||
| ) | ||
@@ -132,0 +132,0 @@ .action(({input, output}, options, logger) => { |
+32
-33
| const fs = require('fs'); | ||
| const tmp = require('tmp'); | ||
| const minimatch = require('minimatch'); | ||
| const { spawnSync } = require('child_process'); | ||
@@ -12,7 +13,7 @@ const { BufferUtils, ImageUtils, FileUtils, PropertyType } = require('@gltf-transform/core'); | ||
| const TOKTX_OPTIONS = { | ||
| const TOKTX_DEFAULTS = { | ||
| mode: Mode.ETC1S, | ||
| quality: 0.5, | ||
| zstd: 0, | ||
| maps: '*', | ||
| slots: '*', | ||
| }; | ||
@@ -23,3 +24,3 @@ | ||
| const toktx = function (options) { | ||
| options = {...TOKTX_OPTIONS, ...options}; | ||
| options = {...TOKTX_DEFAULTS, ...options}; | ||
@@ -29,10 +30,22 @@ return (doc) => { | ||
| logger.debug(`KTX Settings: ${JSON.stringify(options)}`); | ||
| doc.createExtension(TextureBasisu); | ||
| doc.createExtension(TextureBasisu); | ||
| doc.getRoot() | ||
| .listTextures() | ||
| .filter((texture) => texture.getMimeType() !== 'image/ktx2') | ||
| .filter((texture) => filterMaps(texture, options.maps)) | ||
| .forEach((texture, textureIndex) => { | ||
| const slots = getTextureSlots(doc, texture); | ||
| const textureLabel = texture.getURI() | ||
| || texture.getName() | ||
| || `${textureIndex + 1}/${doc.getRoot().listTextures().length}`; | ||
| logger.debug(`Texture ${textureLabel} (${slots.join(', ')})`); | ||
| // Exclude textures that don't match the 'slots' glob, or are already KTX. | ||
| if (texture.getMimeType() === 'image/ktx2') { | ||
| logger.debug('• Skipping, already KTX.'); | ||
| return; | ||
| } else if (options.slots !== '*' && !slots.find((slot) => minimatch(slot, options.slots, {nocase: true}))) { | ||
| logger.debug(`• Skipping, excluded by pattern "${options.slots}".`) | ||
| return; | ||
| } | ||
| const inExtension = texture.getURI() | ||
@@ -44,11 +57,6 @@ ? FileUtils.extension(texture.getURI()) | ||
| const textureLabel = texture.getURI() | ||
| || texture.getName() | ||
| || `${textureIndex + 1}/${doc.getRoot().listTextures().length}`; | ||
| logger.debug(`Compressing ${textureLabel}`); | ||
| const inBytes = texture.getImage().byteLength; | ||
| fs.writeFileSync(inPath, Buffer.from(texture.getImage())); | ||
| const params = [...createParams(texture, options), outPath, inPath]; | ||
| const params = [...createParams(slots, options), outPath, inPath]; | ||
| logger.debug(`• toktx ${params.join(' ')}`); | ||
@@ -73,22 +81,10 @@ | ||
| function filterMaps (texture, glob) { | ||
| if (glob === '*') { | ||
| return true; | ||
| } else if (glob === 'normal') { | ||
| return isNormalMap(texture); | ||
| } | ||
| throw new Error(`Unsupported pattern, "${glob}".`); | ||
| function getTextureSlots (doc, texture) { | ||
| return doc.getGraph().getLinks() | ||
| .filter((link) => link.getChild() === texture) | ||
| .map((link) => link.getName()) | ||
| .filter((slot) => slot !== 'texture') | ||
| } | ||
| function isNormalMap (texture) { | ||
| return !!texture.listParents() | ||
| .find((property) => { | ||
| if (property.propertyType !== PropertyType.MATERIAL) { | ||
| return false; | ||
| } | ||
| return property.getNormalTexture() === texture; | ||
| }); | ||
| } | ||
| function createParams (texture, options) { | ||
| function createParams (slots, options) { | ||
| const params = ['--genmipmap']; | ||
@@ -103,3 +99,6 @@ | ||
| params.push('--qlevel', Math.round(lerp(1, 255, options.quality))); | ||
| if (isNormalMap(texture)) params.push('--linear', '--normal_map'); | ||
| if (slots.find((slot) => minimatch(slot, '*normal*', {nocase: true}))) { | ||
| params.push('--linear', '--normal_map'); | ||
| } | ||
| } | ||
@@ -122,2 +121,2 @@ | ||
| module.exports = {toktx, Mode, TOKTX_OPTIONS}; | ||
| module.exports = {toktx, Mode, TOKTX_DEFAULTS}; |
11649
1.68%9
12.5%243
-0.82%