Files
2026-07-13 13:18:05 +08:00

200 lines
7.9 KiB
JavaScript

#!/usr/bin/env node
import fs from 'node:fs';
import yargs from 'yargs/yargs';
import { hideBin } from 'yargs/helpers';
import packageJson from '../package.json' with { type: 'json' };
import { run } from '../dist/index.js';
import { enumerateAdapters } from '../dist/utils/index.js';
const ExtraText = `
Transform Gaussian splats file
===================================
SUPPORTED INPUTS
.ply .compressed.ply .sog meta.json .ksplat .splat .spz .lcc .esz
SUPPORTED OUTPUTS
.ply .spz .uspz .splat .sog .esz
`;
function readJsonConfig(config) {
const content = fs.readFileSync(config, { encoding: 'utf-8' });
return JSON.parse(content);
}
const cli = yargs(hideBin(process.argv))
.scriptName('splat-transform')
.version(packageJson.version)
.showHelpOnFail(false)
.parserConfiguration({
'boolean-negation': false,
'duplicate-arguments-array': false,
})
.alias('h', 'help')
.alias('v', 'version')
.usage(`${ExtraText}\n$0 <config>`)
.strict()
.command({
command: '$0 <config>',
describe: 'Execute a task pipeline from configuration file',
builder(argv) {
argv.positional('config', { describe: 'pipeline config file', type: 'string' });
},
async handler(argv) {
await run(readJsonConfig(argv.config));
},
})
.command('create <input> <output>', false, {
describe: 'Merge & Transform gaussian splat file',
builder(argv) {
argv.positional('input', { describe: 'input file', type: 'string' }).positional('output', {
describe: 'output file',
type: 'string',
});
},
async handler(argv) {
await run({
version: 1,
tasks: [
{ id: '0', type: 'Read', config: { inputs: [argv.input], output: 'cache0' } },
{ id: '1', type: 'Modify', config: { input: 'cache0', output: 'cache0' } },
{ id: '2', type: 'Write', config: { input: 'cache0', output: argv.output } },
],
});
},
})
.command('lod:loading <input> <output>', false, {
describe: 'Generate loading-lod for gaussian splat file',
builder(argv) {
argv.option('ratio', { describe: 'ratio', type: 'number' })
.positional('input', { describe: 'input filepath', type: 'string' })
.positional('output', { describe: 'output filepath', type: 'string' });
},
async handler(argv) {
await run({
version: 1,
tasks: [
{ id: '0', type: 'Read', config: { inputs: [argv.input], output: 'cache0' } },
{ id: '1', type: 'SkeletonLod', config: { input: 'cache0', output: 'cache0', ratio: argv.ratio } },
{ id: '2', type: 'Write', config: { input: 'cache0', output: argv.output } },
],
});
},
})
.command('lod:flex <input> <output>', false, {
describe: 'Generate flex-lod for gaussian splat file',
builder(argv) {
argv.option('score', { demandOption: true, type: 'string' })
.option('ratio', { describe: 'ratio', type: 'number' })
.positional('input', { describe: 'input filepath', type: 'string' })
.positional('output', { describe: 'output filepath', type: 'string' });
},
async handler(argv) {
await run({
version: 1,
tasks: [
{ id: '0', type: 'Read', config: { inputs: [argv.input], output: 'cache0' } },
{
id: '1',
type: 'FlexLod',
config: { input: 'cache0', output: 'cache0', ratio: argv.ratio, scorePath: argv.score },
},
{ id: '2', type: 'Write', config: { input: 'cache0', output: argv.output } },
],
});
},
})
.command('lod:auto <input> <output>', false, {
describe: 'Generate auto-lod for gaussian splat file',
builder(argv) {
argv.option('ratio', { describe: 'ratio', type: 'number' })
.positional('input', { describe: 'input filepath', type: 'string' })
.positional('output', { describe: 'output filepath', type: 'string' });
},
async handler(argv) {
await run({
version: 1,
tasks: [
{ id: '0', type: 'Read', config: { inputs: [argv.input], output: 'cache0' } },
{ id: '1', type: 'AutoLod', config: { input: 'cache0', output: 'cache0', ratio: argv.ratio } },
{ id: '2', type: 'Write', config: { input: 'cache0', output: argv.output } },
],
});
},
})
.command('lod:auto-chunk <input> <output>', false, {
describe: 'Generate auto-chunk-lod for gaussian splat file',
builder(argv) {
argv.option('type', {
alias: 't',
choices: ['ply', 'spz', 'splat', 'sog', 'esz'],
demandOption: true,
describe: 'output file type',
type: 'string',
})
.option('max-chunk-counts', { describe: 'max chunk counts', type: 'number' })
.positional('input', { describe: 'input filepath', type: 'string' })
.positional('output', { describe: 'output directory', type: 'string' });
},
async handler(argv) {
await run({
version: 1,
tasks: [
{ id: '0', type: 'Read', config: { inputs: [argv.input], output: 'cache0' } },
{
id: '1',
type: 'AutoChunkLod',
config: {
input: 'cache0',
output: 'cache0',
type: argv.type,
maxChunkCounts: argv.maxChunkCounts,
},
},
{ id: '2', type: 'Write', config: { input: 'cache0', output: argv.output } },
],
});
},
})
.command('split <input> <output>', false, {
describe: 'Split splat file into blocks',
builder(argv) {
argv.option('type', {
alias: 't',
choices: ['ply', 'spz', 'splat', 'sog'],
demandOption: true,
describe: 'output file type',
type: 'string',
})
.option('precision', { describe: 'precision', type: 'number' })
.positional('input', { describe: 'input filepath', type: 'string' })
.positional('output', { describe: 'output directory', type: 'string' });
},
async handler(argv) {
await run({
version: 1,
tasks: [
{ id: '0', type: 'Read', config: { inputs: [argv.input], output: 'cache0' } },
{
id: '1',
type: 'SplitSplatTask',
config: { input: 'cache0', output: 'cache0', type: argv.type, blockPrecision: argv.precision },
},
{ id: '2', type: 'Write', config: { input: 'cache0', output: argv.output } },
],
});
},
})
.command('list:gpu', false, {
describe: 'List all available gpu adapters',
builder() {},
async handler() {
const adapters = await enumerateAdapters();
const alignment = Math.ceil(Math.log10(adapters.length));
for (const adapter of adapters) {
console.log(`Adapter ${adapter.index.toString().padStart(alignment, ' ')}: ${adapter.name}`);
}
},
});
await cli.parseAsync();