@gltf-transform/cli
Advanced tools
+175
| const Table = require('cli-table'); | ||
| const { ImageUtils } = require('@gltf-transform/core'); | ||
| function list (type, doc) { | ||
| const logger = doc.getLogger(); | ||
| let result; | ||
| switch (type) { | ||
| case 'meshes': result = listMeshes(doc); break; | ||
| case 'textures': result = listTextures(doc); break; | ||
| case 'extensions': result = listExtensions(doc); break; | ||
| case 'animations': result = listAnimations(doc); break; | ||
| default: | ||
| throw new Error('Not implemented.'); | ||
| } | ||
| const {head, rows, warnings} = result; | ||
| if (rows.length === 0) { | ||
| logger.warn(`No ${type} found.`); | ||
| return; | ||
| } | ||
| const table = new Table({head}); | ||
| table.push(...rows); | ||
| logger.info(table.toString()); | ||
| warnings.forEach((warning) => logger.warn(formatParagraph(warning))); | ||
| } | ||
| /** List meshes. */ | ||
| function listMeshes (doc) { | ||
| const rows = doc.getRoot().listMeshes().map((mesh, index) => { | ||
| const references = mesh.listParents() | ||
| .filter((parent) => parent.propertyType !== 'Root') | ||
| .length; | ||
| let size = 0; | ||
| mesh.listPrimitives().forEach((prim) => { | ||
| for (const attr of prim.listAttributes()) { | ||
| size += attr.getArray().byteLength; | ||
| } | ||
| if (prim.getIndices()) size += prim.getIndices().getArray().byteLength; | ||
| }); | ||
| return [ | ||
| index, | ||
| mesh.getName(), | ||
| references, | ||
| formatBytes(size) | ||
| ]; | ||
| }); | ||
| return { | ||
| rows, | ||
| head: ['index', 'name', 'references', 'size'], | ||
| warnings: [ | ||
| 'NOTICE: Estimated mesh sizes do not include morph targets, and may overestimate' | ||
| + ' total sizes if multiple meshes are sharing the same accessors.' | ||
| ], | ||
| }; | ||
| } | ||
| /** List textures. */ | ||
| function listTextures (doc) { | ||
| const rows = doc.getRoot().listTextures().map((texture, index) => { | ||
| const references = texture.listParents() | ||
| .filter((parent) => parent.propertyType !== 'Root') | ||
| .length; | ||
| const slots = doc.getGraph().getLinks() | ||
| .filter((link) => link.getChild() === texture) | ||
| .map((link) => link.getName()) | ||
| .filter((name) => name !== 'texture'); | ||
| // TODO: This requires a Buffer, currently? | ||
| // const dims = ''; | ||
| // if (texture.getMimeType() === 'image/png') { | ||
| // dims = ImageUtils.getSizePNG(texture.getImage()).join('x'); | ||
| // } else if (texture.getMimeType() === 'image/jpeg') { | ||
| // dims = ImageUtils.getSizeJPEG(texture.getImage()).join('x'); | ||
| // } | ||
| return [ | ||
| index, | ||
| texture.getName(), | ||
| texture.getURI(), | ||
| Array.from(new Set(slots)).join(', '), | ||
| references, | ||
| texture.getMimeType(), | ||
| formatBytes(texture.getImage().byteLength) | ||
| ] | ||
| }); | ||
| return { | ||
| rows, | ||
| head: ['index', 'name', 'uri', 'slots', 'references', 'mimeType', 'size'], | ||
| warnings: [], | ||
| }; | ||
| } | ||
| /** List animations. */ | ||
| function listAnimations (doc) { | ||
| const rows = doc.getRoot().listAnimations().map((anim, index) => { | ||
| let minTime = Infinity; | ||
| let maxTime = -Infinity; | ||
| anim.listSamplers().forEach((sampler) => { | ||
| minTime = Math.min(minTime, sampler.getInput().getMin([])[0]); | ||
| maxTime = Math.max(maxTime, sampler.getInput().getMax([])[0]); | ||
| }); | ||
| let size = 0; | ||
| const accessors = new Set(); | ||
| anim.listSamplers().forEach((sampler) => { | ||
| accessors.add(sampler.getInput()); | ||
| accessors.add(sampler.getOutput()); | ||
| }); | ||
| Array.from(accessors) | ||
| .map((accessor) => accessor.getArray().byteLength) | ||
| .forEach((byteLength) => (size += byteLength)); | ||
| return [ | ||
| index, | ||
| anim.getName(), | ||
| anim.listChannels().length, | ||
| anim.listSamplers().length, | ||
| (maxTime - minTime).toFixed(3) + 's', | ||
| formatBytes(size), | ||
| ] | ||
| }); | ||
| return { | ||
| rows, | ||
| head: ['index', 'name', 'channels', 'samplers', 'duration', 'size'], | ||
| warnings: [ | ||
| 'NOTICE: Estimated animation sizes may overestimate total sizes if multiple animations' | ||
| + ' are sharing the same accessors.' | ||
| ], | ||
| } | ||
| } | ||
| /** List extensions. */ | ||
| function listExtensions (doc) { | ||
| const required = new Set(doc.getRoot().listExtensionsRequired().map((e) => e.extensionName)); | ||
| const rows = doc.getRoot().listExtensionsUsed().map((extension) => { | ||
| return [ | ||
| extension.extensionName, | ||
| required.has(extension) ? 'x' : '' | ||
| ]; | ||
| }); | ||
| return { | ||
| rows, | ||
| head: ['name', 'required'], | ||
| warnings: [], | ||
| }; | ||
| } | ||
| // Utilities. | ||
| function formatBytes(bytes, decimals = 2) { | ||
| if (bytes === 0) return '0 Bytes'; | ||
| const k = 1024; | ||
| const dm = decimals < 0 ? 0 : decimals; | ||
| const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; | ||
| const i = Math.floor(Math.log(bytes) / Math.log(k)); | ||
| return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; | ||
| } | ||
| function formatParagraph(str) { | ||
| return str.match(/.{1,80}(\s|$)/g).join('\n'); | ||
| } | ||
| module.exports = {list}; |
+9
-8
| { | ||
| "name": "@gltf-transform/cli", | ||
| "version": "0.4.0", | ||
| "version": "0.4.1", | ||
| "repository": "github:donmccurdy/glTF-Transform", | ||
@@ -17,9 +17,10 @@ "description": "CLI interface to glTF Transform", | ||
| "dependencies": { | ||
| "@gltf-transform/ao": "^0.4.0", | ||
| "@gltf-transform/colorspace": "^0.4.0", | ||
| "@gltf-transform/core": "^0.4.0", | ||
| "@gltf-transform/extensions": "^0.4.0", | ||
| "@gltf-transform/prune": "^0.4.0", | ||
| "@gltf-transform/split": "^0.4.0", | ||
| "@gltf-transform/ao": "^0.4.1", | ||
| "@gltf-transform/colorspace": "^0.4.1", | ||
| "@gltf-transform/core": "^0.4.1", | ||
| "@gltf-transform/extensions": "^0.4.1", | ||
| "@gltf-transform/prune": "^0.4.1", | ||
| "@gltf-transform/split": "^0.4.1", | ||
| "caporal": "^1.3.0", | ||
| "cli-table": "^0.3.1", | ||
| "gl": "^4.5.0" | ||
@@ -33,3 +34,3 @@ }, | ||
| ], | ||
| "gitHead": "9a2c30c6dd0f0222e86e5f65a8c13f07b9765b1c" | ||
| "gitHead": "fa72e862b943db559d398918e3091216f54a6592" | ||
| } |
+16
-20
@@ -5,3 +5,2 @@ #!/usr/bin/env node | ||
| const path = require('path'); | ||
| const util = require('util'); | ||
| const gl = require('gl'); | ||
@@ -16,2 +15,3 @@ const program = require('caporal'); | ||
| const { prune } = require('@gltf-transform/prune'); | ||
| const { list } = require('./list'); | ||
@@ -21,21 +21,5 @@ const io = new NodeIO(fs, path).registerExtensions(KHRONOS_EXTENSIONS); | ||
| program | ||
| .version(version); | ||
| .version(version) | ||
| .description('Commandline interface for the glTF-Transform SDK.'); | ||
| // ANALYZE | ||
| program | ||
| .command('analyze', 'Analyzes a model\'s contents') | ||
| .argument('<input>', 'Path to glTF 2.0 (.glb, .gltf) model') | ||
| .action(({input}, options, logger) => { | ||
| const root = io.read(input).getRoot(); | ||
| const analysis = { | ||
| accessors: root.listAccessors().length, | ||
| buffers: root.listBuffers().length, | ||
| materials: root.listMaterials().length, | ||
| meshes: root.listMeshes().length, | ||
| nodes: root.listNodes().length, | ||
| textures: root.listTextures().length, | ||
| }; | ||
| console.log(util.inspect(analysis, {colors: true, sorted: true})); | ||
| }); | ||
| // REPACK | ||
@@ -51,2 +35,14 @@ program | ||
| // LIST | ||
| program | ||
| .command('list', 'List a model\'s resources of a given type') | ||
| .argument( | ||
| '<type>', 'Property type ("animations", "extensions", "meshes", "textures")', | ||
| ['animations', 'extensions', 'meshes', 'textures'] | ||
| ) | ||
| .argument('<input>', 'Path to glTF 2.0 (.glb, .gltf) model') | ||
| .action(({type, input}, _, logger) => { | ||
| list(type, io.read(input).setLogger(logger)); | ||
| }); | ||
| // AMBIENT OCCLUSION | ||
@@ -95,3 +91,3 @@ program | ||
| program | ||
| .command('split', 'Splits buffers so that separate meshes can be stored in separate .bin files') | ||
| .command('split', 'Splits mesh data into separate .bin files') | ||
| .argument('<input>', 'Path to read glTF 2.0 (.glb, .gltf) input') | ||
@@ -98,0 +94,0 @@ .argument('<output>', 'Path to write output') |
11652
61.03%5
25%264
121.85%9
12.5%+ Added
+ Added
Updated
Updated
Updated
Updated