增加转格式
This commit is contained in:
82
lib/utils/gltf.js
Normal file
82
lib/utils/gltf.js
Normal file
@ -0,0 +1,82 @@
|
||||
import fs from "fs";
|
||||
|
||||
export const BACKUP_SUFFIX = "_备份";
|
||||
|
||||
// 检查当前目录是否有 gltf 文件
|
||||
export function hasGltfFile() {
|
||||
return listGltfFiles().length > 0;
|
||||
}
|
||||
|
||||
// 获取当前目录下可用的 gltf 文件列表
|
||||
export function listGltfFiles() {
|
||||
const cwd = process.cwd();
|
||||
return fs.readdirSync(cwd).filter(f => f.toLowerCase().endsWith(".gltf") && !f.includes(BACKUP_SUFFIX));
|
||||
}
|
||||
|
||||
// 获取 glTF / GLB 模型文件
|
||||
export function listModelFiles() {
|
||||
const cwd = process.cwd();
|
||||
return fs
|
||||
.readdirSync(cwd)
|
||||
.filter(file => /\.(gltf|glb)$/i.test(file) && !file.includes(BACKUP_SUFFIX));
|
||||
}
|
||||
|
||||
// 获取所有支持的模型文件(包括需要转换的格式)
|
||||
export function listAllModelFiles() {
|
||||
const cwd = process.cwd();
|
||||
return fs
|
||||
.readdirSync(cwd)
|
||||
.filter(file => /\.(gltf|glb|obj|fbx)$/i.test(file) && !file.includes(BACKUP_SUFFIX));
|
||||
}
|
||||
|
||||
// 判断是否需要先转换
|
||||
export function needsConversion(file) {
|
||||
return /\.(obj|fbx)$/i.test(file);
|
||||
}
|
||||
|
||||
// 检查是否满足执行条件(有 ktx2、gltf、bin 文件)
|
||||
export function checkRequiredFiles() {
|
||||
const cwd = process.cwd();
|
||||
const files = fs.readdirSync(cwd);
|
||||
const hasKtx2 = files.some(f => f.toLowerCase().endsWith(".ktx2"));
|
||||
const hasGltf = files.some(f => f.toLowerCase().endsWith(".gltf"));
|
||||
const hasBin = files.some(f => f.toLowerCase().endsWith(".bin"));
|
||||
|
||||
const missing = [];
|
||||
if (!hasKtx2) missing.push("ktx2");
|
||||
if (!hasGltf) missing.push("gltf");
|
||||
if (!hasBin) missing.push("bin");
|
||||
|
||||
return { ok: missing.length === 0, missing };
|
||||
}
|
||||
|
||||
// 修改 gltf 文件,根据选项添加扩展
|
||||
export function modifyGltfContent(gltfPath, options = ["textureBasisu"]) {
|
||||
const content = fs.readFileSync(gltfPath, "utf-8");
|
||||
const gltf = JSON.parse(content);
|
||||
|
||||
if (options.includes("textureBasisu")) {
|
||||
if (!gltf.extensionsUsed) gltf.extensionsUsed = [];
|
||||
if (!gltf.extensionsUsed.includes("KHR_texture_basisu")) {
|
||||
gltf.extensionsUsed.push("KHR_texture_basisu");
|
||||
}
|
||||
|
||||
if (gltf.textures) {
|
||||
gltf.textures = gltf.textures.map(tex => ({
|
||||
extensions: { KHR_texture_basisu: { source: tex.source } }
|
||||
}));
|
||||
}
|
||||
|
||||
if (gltf.images) {
|
||||
gltf.images = gltf.images.map(img => {
|
||||
const uri = img.uri || "";
|
||||
const newUri = uri.replace(/\.(png|jpg|jpeg|webp|tga)$/i, ".ktx2");
|
||||
return { uri: newUri, mimeType: "image/ktx2" };
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// placeholder1, placeholder2, placeholder3 预留扩展点
|
||||
|
||||
return gltf;
|
||||
}
|
||||
124
lib/utils/stepui.js
Normal file
124
lib/utils/stepui.js
Normal file
@ -0,0 +1,124 @@
|
||||
import color from "picocolors";
|
||||
import { initKeypress, onKey } from "../keyboard.js";
|
||||
|
||||
export function createStepUI(options) {
|
||||
const { title, getSteps } = options;
|
||||
|
||||
let steps = [];
|
||||
let results = [];
|
||||
let completed = new Set();
|
||||
let currentStep = 0;
|
||||
let currentOption = 0;
|
||||
|
||||
function initResults() {
|
||||
steps = typeof getSteps === "function" ? getSteps() : getSteps;
|
||||
results = steps.map(s => s.type === "multiselect" ? [...(s.default || [])] : s.default);
|
||||
completed = new Set();
|
||||
currentStep = 0;
|
||||
currentOption = 0;
|
||||
}
|
||||
|
||||
function renderNav() {
|
||||
const nav = steps.map((step, i) => {
|
||||
if (completed.has(i)) return color.green("☑ " + step.name);
|
||||
if (i === currentStep) return color.bgCyan(color.black(" " + step.name + " "));
|
||||
return color.dim("□ " + step.name);
|
||||
});
|
||||
return "← " + nav.join(" ") + " " + color.green("✓Submit") + " →";
|
||||
}
|
||||
|
||||
function renderOptions() {
|
||||
const step = steps[currentStep];
|
||||
const lines = [color.cyan(step.message), ""];
|
||||
if (!step.options || !step.options.length) {
|
||||
lines.push(color.dim(step.emptyMessage || "无可用选项"));
|
||||
return lines.join("\n");
|
||||
}
|
||||
step.options.forEach((opt, i) => {
|
||||
const isCurrent = i === currentOption;
|
||||
const isSelected = step.type === "multiselect"
|
||||
? results[currentStep]?.includes(opt.value)
|
||||
: results[currentStep] === opt.value;
|
||||
const prefix = step.type === "multiselect"
|
||||
? (isSelected ? color.green("◉ ") : "○ ")
|
||||
: (isSelected ? color.green("● ") : "○ ");
|
||||
const cursor = isCurrent ? color.cyan("❯ ") : " ";
|
||||
const label = isCurrent ? color.cyan(opt.label) : opt.label;
|
||||
const check = isSelected ? color.green(" ✓") : "";
|
||||
lines.push(cursor + prefix + label + check);
|
||||
if (opt.hint) lines.push(" " + color.dim(opt.hint));
|
||||
});
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function render() {
|
||||
console.clear();
|
||||
console.log(color.bgCyan(color.black(` ${title} `)));
|
||||
console.log("\n" + renderNav());
|
||||
console.log(color.dim("\n← → 切换步骤 | ↑ ↓ 选择 | Space 选中 | Tab 提交 | Esc 返回\n"));
|
||||
console.log(renderOptions());
|
||||
}
|
||||
|
||||
function runInteractive() {
|
||||
initResults();
|
||||
initKeypress();
|
||||
return new Promise(resolve => {
|
||||
render();
|
||||
onKey((str, key) => {
|
||||
if (!key) return;
|
||||
const step = steps[currentStep];
|
||||
const optCount = step.options?.length || 0;
|
||||
if (key.name === "left") {
|
||||
if (currentStep > 0) { currentStep--; currentOption = 0; }
|
||||
render();
|
||||
} else if (key.name === "right") {
|
||||
if (currentStep < steps.length - 1) { currentStep++; currentOption = 0; }
|
||||
render();
|
||||
} else if (key.name === "up") {
|
||||
if (!optCount) return;
|
||||
currentOption = (currentOption - 1 + optCount) % optCount;
|
||||
render();
|
||||
} else if (key.name === "down") {
|
||||
if (!optCount) return;
|
||||
currentOption = (currentOption + 1) % optCount;
|
||||
render();
|
||||
} else if (key.name === "space") {
|
||||
if (!optCount) return;
|
||||
const opt = step.options[currentOption];
|
||||
if (step.type === "multiselect") {
|
||||
const list = results[currentStep];
|
||||
const idx = list.indexOf(opt.value);
|
||||
if (idx >= 0) list.splice(idx, 1);
|
||||
else list.push(opt.value);
|
||||
} else {
|
||||
results[currentStep] = opt.value;
|
||||
}
|
||||
completed.add(currentStep);
|
||||
render();
|
||||
} else if (key.name === "return") {
|
||||
if (!optCount) return;
|
||||
if (step.type === "select") {
|
||||
results[currentStep] = step.options[currentOption].value;
|
||||
}
|
||||
completed.add(currentStep);
|
||||
if (currentStep < steps.length - 1) { currentStep++; currentOption = 0; }
|
||||
render();
|
||||
} else if (key.name === "tab") {
|
||||
resolve({ steps, results });
|
||||
} else if (key.name === "escape" || (key.ctrl && key.name === "c")) {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function showSummary(lines) {
|
||||
console.clear();
|
||||
console.log(color.bgCyan(color.black(` ${title} `)));
|
||||
console.log("\n" + color.green("配置完成!当前设置:"));
|
||||
lines.forEach(line => console.log(" " + line));
|
||||
console.log();
|
||||
}
|
||||
|
||||
return { runInteractive, showSummary, initResults };
|
||||
}
|
||||
Reference in New Issue
Block a user