@xtctwins/tctwins-convert
Version:
JavaScript utilities to create .XTC files
292 lines (247 loc) • 9.91 kB
JavaScript
const commander = require("commander");
const npmPackage = require("./package.json");
const { convert2xtc, XTC_INFO } = require("./dist/tctwins-convert.cjs.js");
const fs = require("fs");
const defaultConfigs = require(`./convert2xtc.conf.js`);
const WebIFC = require("web-ifc");
const path = require("path");
const { createValidator } = require("@typeonly/validator");
// const validator = createValidator({
// bundle: require("./types.to.json")
// });
const program = new commander.Command();
program.version(npmPackage.version, "-v, --version");
program
.option("-c, --configs [file]", "optional path to JSON configs file; overrides convert2xtc.conf.js")
.option("-s, --source [file]", "path to source file")
.option("-a, --sourcemanifest [file]", "path to source manifest file (for converting split file output from ifcgltf -s)")
.option("-f, --format [string]", "source file format (optional); supported formats are gltf, ifc, laz, las, pcd, ply, stl and cityjson")
.option("-m, --metamodel [file]", "path to source metamodel JSON file (optional)")
.option("-i, --include [types]", "only convert these types (optional)")
.option("-x, --exclude [types]", "never convert these types (optional)")
.option("-r, --rotatex", "rotate model 90 degrees about X axis (for las and cityjson)")
.option("-g, --disablegeoreuse", "disable geometry reuse (optional)")
.option("-z, --minTileSize [number]", "minimum diagonal tile size (optional, default 500)")
.option("-t, --disabletextures", "ignore textures (optional)")
.option("-n, --disablenormals", "ignore normals (optional)")
.option(
"-o, --output [file]",
"path to target .xtc file when -s option given, or JSON manifest for multiple .xtc files when source manifest file given with -a; creates directories on path automatically if not existing"
)
.option("-l, --log", "enable logging (optional)");
program.on("--help", () => {
console.log(`\n\XTC version: ${XTC_INFO.xtcVersion}`);
});
program.parse(process.argv);
const options = program.opts();
let configs = defaultConfigs;
if (options.source === undefined && options.sourcemanifest === undefined) {
console.error("[convert2xtc] [ERROR]: Please specify path to source file or manifest.");
program.help();
process.exit(1);
}
if (options.source !== undefined && options.sourcemanifest !== undefined) {
console.error("[convert2xtc] [ERROR]: Can't specify path to source file AND manifest - only one of these params allowed.");
program.help();
process.exit(1);
}
if (options.output === undefined) {
console.error("[convert2xtc] [ERROR]: Please specify target xtc file path.");
program.help();
process.exit(1);
}
function log(msg) {
if (options.log) {
console.log(msg);
}
}
function getFileExtension(fileName) {
let ext = path.extname(fileName);
if (ext.charAt(0) === ".") {
ext = ext.substring(1);
}
return ext;
}
async function main() {
// const { memoryUsage } = process;
// let maxMemoryUsage = 0;
// let lastMemoryUsage = getMemoryUsageInMB();
// function getMemoryUsageInMB() {
// const memoryUsage = process.memoryUsage();
// return (memoryUsage.heapTotal / (1024 * 1024)).toFixed(2);
// }
// const intervalId = setInterval(() => {
// const currentMemoryUsage = getMemoryUsageInMB();
// if (currentMemoryUsage > lastMemoryUsage) {
// maxMemoryUsage = Math.max(maxMemoryUsage, currentMemoryUsage);
// }
// lastMemoryUsage = currentMemoryUsage;
// }, 1000);
let startTime = new Date().getTime();
log(`[convert2xtc] Running convert2xtc v${npmPackage.version}...`);
if (options.configs !== undefined) {
log(`[convert2xtc] Using JSON configs file: ${options.configs}`);
try {
let configsData = fs.readFileSync(options.configs);
configs = JSON.parse(configsData);
} catch (e) {
console.error(`[convert2xtc] [ERROR]: Failed to load custom configs file (specified with -c or --configs) - ${e}`);
process.exit(1);
}
} else {
log(`[convert2xtc] Using configs in ./convert2xtc.conf.js`);
}
configs.sourceConfigs ||= {};
if (options.sourcemanifest) {
log(`[convert2xtc] Converting glTF files in manifest ${options.sourcemanifest}...`);
let manifestData = fs.readFileSync(options.sourcemanifest);
let manifest = JSON.parse(manifestData);
if (!manifest.gltfOutFiles) {
console.error(`[convert2xtc] [ERROR]: Input manifest invalid - missing field: gltfOutFiles`);
process.exit(1);
}
const numInputFiles = manifest.gltfOutFiles.length;
if (numInputFiles === 0) {
console.error(`[convert2xtc] [ERROR]: Input manifest invalid - gltfOutFiles is zero length`);
process.exit(1);
}
const outputDir = path.dirname(options.output);
if (outputDir !== "" && !fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
function formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
const hours = String(date.getHours()).padStart(2, "0");
const minutes = String(date.getMinutes()).padStart(2, "0");
const seconds = String(date.getSeconds()).padStart(2, "0");
return `${day}-${month}-${year}- ${hours}-${minutes}-${seconds}`;
}
const xtcManifest = {
inputFile: options.sourcemanifest,
converterApplication: "convert2xtc",
converterApplicationVersion: `v${npmPackage.version}`,
conversionDate: formatDate(new Date()),
outputDir,
xtcFiles: []
};
const sourceConfigs = configs.sourceConfigs || {};
const formatConfig = sourceConfigs["gltf"] || configs["glb"] || {};
const externalMetadata = !!formatConfig.externalMetadata;
if (externalMetadata) {
xtcManifest.metaModelFiles = [];
for (let i = 0, len = manifest.metadataOutFiles.length; i < len; i++) {
const metadataSource = manifest.metadataOutFiles[i];
xtcManifest.metaModelFiles.push(path.basename(metadataSource));
}
}
let i = 0;
const convertNextFile = () => {
const source = manifest.gltfOutFiles[i];
const metaModelSource = i < manifest.metadataOutFiles.length ? manifest.metadataOutFiles[i] : null;
const outputFileName = getFileNameWithoutExtension(source);
const outputFileNameXTC = `${outputFileName}.xtc`;
const ext = getFileExtension(source);
let modelAABB;
// if (manifest.modelBoundsMin && manifest.modelBoundsMax) {
// modelAABB= [
// manifest.modelBoundsMin[0],
// manifest.modelBoundsMin[1],
// manifest.modelBoundsMin[2],
// manifest.modelBoundsMax[0],
// manifest.modelBoundsMax[1],
// manifest.modelBoundsMax[2]
// ];
// }
convert2xtc({
WebIFC,
configs,
source,
format: ext,
metaModelSource: !externalMetadata ? metaModelSource : null,
modelAABB,
output: path.join(outputDir, outputFileNameXTC),
includeTypes: options.include ? options.include.slice(",") : null,
excludeTypes: options.exclude ? options.exclude.slice(",") : null,
rotateX: options.rotatex,
reuseGeometries: options.disablegeoreuse !== true,
minTileSize: options.minTileSize,
includeTextures: !options.disabletextures,
includeNormals: !options.disablenormals,
log
})
.then(() => {
i++;
log(`[convert2xtc] Converted model${i}.xtc (${i} of ${numInputFiles})`);
xtcManifest.xtcFiles.push(outputFileNameXTC);
if (i === numInputFiles) {
fs.writeFileSync(options.output, JSON.stringify(xtcManifest));
log(`[convert2xtc] Done.`);
process.exit(0);
} else {
convertNextFile();
}
})
.catch((err) => {
console.error(`[convert2xtc] [ERROR]: ${err}`);
process.exit(1);
});
};
convertNextFile();
} else {
if (options.output) {
const outputDir = path.dirname(options.output);
if (outputDir !== "" && !fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
}
log(`[convert2xtc] Converting single input file ${options.source}...`);
convert2xtc({
WebIFC,
configs,
source: options.source,
format: options.format,
metaModelSource: options.metamodel,
output: options.output,
includeTypes: options.include ? options.include.slice(",") : null,
excludeTypes: options.exclude ? options.exclude.slice(",") : null,
rotateX: options.rotatex,
reuseGeometries: options.disablegeoreuse !== true,
minTileSize: options.minTileSize,
includeTextures: !options.disabletextures,
includeNormals: !options.disablenormals,
log
})
.then(() => {
log(`[convert2xtc] Done.`);
// clearInterval(intervalId);
// // // 打印最大内存消耗
// log(`[MemoryUsage] 最大内存消耗: ${maxMemoryUsage} MB`);
let endTime = new Date().getTime();
let executionTime = endTime - startTime;
if (executionTime > 600 * 1000) console.log("整体加载时长: " + (executionTime / 1000 / 60).toFixed(2) + " m'");
else console.log("整体加载时长: " + (executionTime / 1000).toFixed(2) + " s'");
setTimeout(() => {
process.exit(0);
}, 500);
})
.catch((err) => {
console.error(`[convert2xtc] [ERROR]: ${err}`);
let endTime = new Date().getTime();
let executionTime = endTime - startTime;
console.log("整体加载时长: " + executionTime + " ms");
process.exit(1);
});
}
}
function getFileNameWithoutExtension(filePath) {
const baseName = path.basename(filePath);
const fileNameWithoutExtension = path.parse(baseName).name;
return fileNameWithoutExtension;
}
main().catch((err) => {
console.error("Error:", err);
process.exit(1);
});