@genex-ai/cli-demo 1.19.0-dev.601 → 1.21.0-dev.603
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{blender-mcp-FQJFZETR.js → blender-mcp-PWOUSXK4.js} +1 -1
- package/dist/{chunk-YSMZVFJM.js → chunk-HEDRWUVP.js} +7 -0
- package/dist/index.js +542 -170
- package/package.json +1 -1
- package/templates/controllers/character/meshy/meshy-loader.ts +3 -2
- package/templates/controllers/quality/deadline.ts +117 -0
- package/templates/controllers/quality/pick-asset.ts +27 -15
- package/templates/skills/genex-ai-character/SKILL.md +67 -15
- package/templates/skills/genex-ai-model/SKILL.md +32 -0
- package/templates/skills/genex-game-director/SKILL.md +18 -10
- package/templates/skills/genex-lane-card/SKILL.md +60 -0
- package/templates/skills/genex-threejs-character-controller/SKILL.md +2 -1
- package/templates/skills/genex-threejs-creatures/SKILL.md +8 -1
- package/templates/skills/genex-threejs-visual-validation/SKILL.md +2 -1
- package/templates/skills/genex-tool-character/SKILL.md +36 -5
- package/templates/skills/genex-tool-model/SKILL.md +16 -3
package/dist/index.js
CHANGED
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
writeProject,
|
|
43
43
|
writeUserToken,
|
|
44
44
|
writeWorkspace
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-HEDRWUVP.js";
|
|
46
46
|
import {
|
|
47
47
|
CLI_CHANNEL,
|
|
48
48
|
DEFAULT_API_URL,
|
|
@@ -2397,6 +2397,16 @@ async function recordTerminal(id, status, urls, cwd = process.cwd()) {
|
|
|
2397
2397
|
async function countFailed(kind, cwd = process.cwd()) {
|
|
2398
2398
|
return (await readLedger(cwd)).filter((e) => e.kind === kind && e.status === "failed").length;
|
|
2399
2399
|
}
|
|
2400
|
+
async function trailingFailures(kind, cwd = process.cwd()) {
|
|
2401
|
+
const rows = (await readLedger(cwd)).filter((e) => e.kind === kind);
|
|
2402
|
+
let run2 = 0;
|
|
2403
|
+
for (let i = rows.length - 1; i >= 0; i -= 1) {
|
|
2404
|
+
const status = rows[i].status;
|
|
2405
|
+
if (status === "completed") break;
|
|
2406
|
+
if (status === "failed") run2 += 1;
|
|
2407
|
+
}
|
|
2408
|
+
return run2;
|
|
2409
|
+
}
|
|
2400
2410
|
async function countOutcomes(kind, cwd = process.cwd()) {
|
|
2401
2411
|
const rows = (await readLedger(cwd)).filter((e) => e.kind === kind);
|
|
2402
2412
|
return {
|
|
@@ -2516,26 +2526,32 @@ function auditGenerationPlan(input) {
|
|
|
2516
2526
|
const lane = m[1] ? LANE_OF_SKILL[m[1]] : void 0;
|
|
2517
2527
|
if (lane) declaredLanes.add(lane);
|
|
2518
2528
|
}
|
|
2519
|
-
const lanesRun = new Set(
|
|
2529
|
+
const lanesRun = new Set(
|
|
2530
|
+
ledger.filter((e) => e.status === "completed").map((e) => LANE_OF_KIND[e.kind] ?? e.kind)
|
|
2531
|
+
);
|
|
2532
|
+
const lanesPending = new Set(
|
|
2533
|
+
ledger.filter((e) => e.status === "queued").map((e) => LANE_OF_KIND[e.kind] ?? e.kind).filter((lane) => !lanesRun.has(lane))
|
|
2534
|
+
);
|
|
2520
2535
|
const warnings = [];
|
|
2521
2536
|
const owed = /* @__PURE__ */ new Map();
|
|
2522
2537
|
for (const row of rows) {
|
|
2523
2538
|
if (row.procedural || row.lane === null) continue;
|
|
2524
2539
|
if (row.status !== "planned" && row.status !== "generating") continue;
|
|
2525
2540
|
if (lanesRun.has(row.lane)) continue;
|
|
2541
|
+
if (lanesPending.has(row.lane)) continue;
|
|
2526
2542
|
if (row.lane === "skybox") continue;
|
|
2527
2543
|
const held = owed.get(row.lane) ?? [];
|
|
2528
2544
|
held.push(row.status === "generating" ? `${row.name} (says generating, not in this project's ledger)` : row.name);
|
|
2529
2545
|
owed.set(row.lane, held);
|
|
2530
2546
|
}
|
|
2531
2547
|
for (const lane of declaredLanes) {
|
|
2532
|
-
if (lanesRun.has(lane) || owed.has(lane) || lane === "skybox") continue;
|
|
2548
|
+
if (lanesRun.has(lane) || lanesPending.has(lane) || owed.has(lane) || lane === "skybox") continue;
|
|
2533
2549
|
if (!rows.some((r) => r.lane === lane)) owed.set(lane, ["declared as this subsystem's lane"]);
|
|
2534
2550
|
}
|
|
2535
2551
|
for (const [lane, names] of owed) {
|
|
2536
2552
|
const shown = names.slice(0, 3).join(", ") + (names.length > 3 ? `, +${names.length - 3} more` : "");
|
|
2537
2553
|
warnings.push(
|
|
2538
|
-
`DESIGN.md plans generated ${lane} work (${shown}) and this project has
|
|
2554
|
+
`DESIGN.md plans generated ${lane} work (${shown}) and this project has no ${lane} generation that landed \u2014 the plan says so and the generation ledger has nothing completed. Generate it (${LANE_COMMAND[lane] ?? `npx genex ${lane}`} "<prompt>" --no-wait), or change the row to say what happens instead and why.`
|
|
2539
2555
|
);
|
|
2540
2556
|
}
|
|
2541
2557
|
const paidRows = rows.filter((r) => !r.procedural);
|
|
@@ -6056,7 +6072,18 @@ async function detectSurfaceScan(cwd = process.cwd()) {
|
|
|
6056
6072
|
}
|
|
6057
6073
|
return found;
|
|
6058
6074
|
}
|
|
6059
|
-
var WIRED_BY_URL = /* @__PURE__ */ new Set([
|
|
6075
|
+
var WIRED_BY_URL = /* @__PURE__ */ new Set([
|
|
6076
|
+
"model",
|
|
6077
|
+
"model_segment",
|
|
6078
|
+
"model_rig",
|
|
6079
|
+
"model_animation",
|
|
6080
|
+
"skybox",
|
|
6081
|
+
"sfx",
|
|
6082
|
+
"music",
|
|
6083
|
+
"voice",
|
|
6084
|
+
"texture",
|
|
6085
|
+
"video"
|
|
6086
|
+
]);
|
|
6060
6087
|
var UNPICKED_AFTER_MS = 15 * 60 * 1e3;
|
|
6061
6088
|
async function detectGenerationAudit(cwd = process.cwd()) {
|
|
6062
6089
|
const ledger = await readLedger(cwd);
|
|
@@ -6067,7 +6094,10 @@ async function detectGenerationAudit(cwd = process.cwd()) {
|
|
|
6067
6094
|
let haystack = "";
|
|
6068
6095
|
const read = async (file) => {
|
|
6069
6096
|
try {
|
|
6070
|
-
|
|
6097
|
+
const raw = await fs15.readFile(file, "utf8");
|
|
6098
|
+
const ext = path14.extname(file).toLowerCase();
|
|
6099
|
+
if (ext === ".txt") return;
|
|
6100
|
+
haystack += ext === ".ts" || ext === ".tsx" || ext === ".js" || ext === ".jsx" || ext === ".css" ? blankComments(raw) : ext === ".html" ? raw.replace(/<!--[\s\S]*?-->/g, (m) => m.replace(/[^\n]/g, " ")) : raw;
|
|
6071
6101
|
} catch {
|
|
6072
6102
|
}
|
|
6073
6103
|
};
|
|
@@ -7164,9 +7194,20 @@ function applySkyboxGuard(prompt, raw) {
|
|
|
7164
7194
|
if (guarded.length > SKYBOX_PROMPT_MAX) return { prompt, guarded: false, nouns };
|
|
7165
7195
|
return { prompt: guarded, guarded: true, nouns };
|
|
7166
7196
|
}
|
|
7197
|
+
var MODEL_TEXTURE_TIERS = ["standard", "detailed", "none"];
|
|
7167
7198
|
function buildGenOptions(kind, opts) {
|
|
7168
7199
|
const options = { ...opts.generationOptions };
|
|
7169
7200
|
if (kind === "model" && opts.imageUrl) options.imageUrl = opts.imageUrl;
|
|
7201
|
+
if (kind === "model") {
|
|
7202
|
+
if (opts.texture === "none") options.texture = false;
|
|
7203
|
+
else if (opts.texture !== void 0) options.textureQuality = opts.texture;
|
|
7204
|
+
if (opts.geometry !== void 0) options.geometryQuality = opts.geometry;
|
|
7205
|
+
if (opts.quad) options.quad = true;
|
|
7206
|
+
if (opts.lowPoly) options.smartLowPoly = true;
|
|
7207
|
+
if (opts.parts) options.generateParts = true;
|
|
7208
|
+
if (opts.faceLimit !== void 0) options.faceLimit = opts.faceLimit;
|
|
7209
|
+
if (opts.autoSize) options.autoSize = true;
|
|
7210
|
+
}
|
|
7170
7211
|
if (kind === "texture" && opts.terrain) options.terrain = true;
|
|
7171
7212
|
if ((kind === "sfx" || kind === "music") && opts.duration) options.durationSeconds = opts.duration;
|
|
7172
7213
|
if (kind === "voice") {
|
|
@@ -7217,6 +7258,11 @@ async function runGenerate(kind, opts) {
|
|
|
7217
7258
|
const ref = opts.imageUrl.startsWith("data:") ? "local image" : opts.imageUrl;
|
|
7218
7259
|
typedPrompt = `from image: ${path16.basename(ref).slice(0, 120)}`;
|
|
7219
7260
|
}
|
|
7261
|
+
if (kind === "model" && opts.texture !== void 0 && !MODEL_TEXTURE_TIERS.includes(opts.texture)) {
|
|
7262
|
+
log.error(`--texture ${opts.texture} is a character texture size. \`genex model\` takes a texture TIER: ${MODEL_TEXTURE_TIERS.join("|")} (default detailed).`);
|
|
7263
|
+
process.exitCode = 1;
|
|
7264
|
+
return void 0;
|
|
7265
|
+
}
|
|
7220
7266
|
if (!typedPrompt) {
|
|
7221
7267
|
log.error(`Missing prompt. Usage: ${c.cyan(`genex ${kind} "<prompt>"`)}`);
|
|
7222
7268
|
process.exitCode = 1;
|
|
@@ -7550,7 +7596,10 @@ async function reportTerminal(kind, view, log, open = false, json = false, local
|
|
|
7550
7596
|
const failures = await countFailed("video");
|
|
7551
7597
|
(failures >= 2 ? log.plain : log.dim)(videoFailureAdvice(failures));
|
|
7552
7598
|
} else if (!failure?.billing) {
|
|
7553
|
-
const advice = laneFailureAdvice(kind,
|
|
7599
|
+
const advice = laneFailureAdvice(kind, {
|
|
7600
|
+
...await countOutcomes(kind),
|
|
7601
|
+
trailingFailed: await trailingFailures(kind)
|
|
7602
|
+
});
|
|
7554
7603
|
if (advice) log.plain(advice);
|
|
7555
7604
|
}
|
|
7556
7605
|
}
|
|
@@ -7644,8 +7693,11 @@ function reportLocalFiles(result, log, json) {
|
|
|
7644
7693
|
}
|
|
7645
7694
|
}
|
|
7646
7695
|
function laneFailureAdvice(kind, outcomes) {
|
|
7647
|
-
|
|
7648
|
-
|
|
7696
|
+
const run2 = outcomes.trailingFailed ?? (outcomes.completed > 0 ? 0 : outcomes.failed);
|
|
7697
|
+
if (run2 < 2) return null;
|
|
7698
|
+
const everSucceeded = outcomes.completed > 0;
|
|
7699
|
+
const opening = everSucceeded ? `\u270B the last ${run2} ${kind} generations in this project all failed.` : `\u270B ${run2} ${kind} generations attempted in this project, ${run2} failed, none succeeded.`;
|
|
7700
|
+
return ` ${c.bold(opening)} That pattern is the LANE, not your prompt \u2014 a fourth attempt bills the same and returns the same. Build this one in code, use a different lane, or tell the user in one plain line what is unavailable and carry on with the rest of the game.`;
|
|
7649
7701
|
}
|
|
7650
7702
|
function videoFailureAdvice(failures) {
|
|
7651
7703
|
if (failures >= 2) {
|
|
@@ -7780,7 +7832,14 @@ var HINT_URL_KINDS = /* @__PURE__ */ new Set([
|
|
|
7780
7832
|
]);
|
|
7781
7833
|
function assetHint(kind, view, url) {
|
|
7782
7834
|
const hint = {
|
|
7783
|
-
|
|
7835
|
+
// "straight from the URL" was the OPPOSITE of the kit's own rule, which
|
|
7836
|
+
// reads: "EVERY tier loads a game-ready rung — provider-raw originals are
|
|
7837
|
+
// archival/remix source, not game assets" (controllers/quality/pick-asset.ts).
|
|
7838
|
+
// A bare GLTFLoader on this URL fetches the original — a Tripo prop is
|
|
7839
|
+
// ~500k tris and 3x4096 textures, and a scene of them floored an M4 Max.
|
|
7840
|
+
// The url clause stays: the BARE url is the right thing to store, because
|
|
7841
|
+
// `pickModel` resolves the tier's rung at load time.
|
|
7842
|
+
model: `Standard GLB. Load it through the quality kit \u2014 createGltfLoader(renderer) then loadModelWithFallback(url, tier, (u) => gltf.loader.loadAsync(u), { ktx2: gltf.ktx2 }) \u2014 so the tier gets a game-ready rung; a bare GLTFLoader on this URL fetches the provider-raw original (~500k tris, 3x4096 textures). Install it with npx genex controller quality. See genex-ai-model.`,
|
|
7784
7843
|
// Unreachable while the lane is paused (see src/index.ts) — kept so a
|
|
7785
7844
|
// restore is one deletion. Games that already ship a panorama still load
|
|
7786
7845
|
// it this way.
|
|
@@ -7801,9 +7860,9 @@ function assetHint(kind, view, url) {
|
|
|
7801
7860
|
character: `Install the controller and current manifest with genex controller character --character ${view.id}.`,
|
|
7802
7861
|
character_animation: "The action is now part of the character's current manifest; refresh the local controller manifest before testing.",
|
|
7803
7862
|
character_motion: `The clips are part of the character's current manifest \u2014 install them with genex controller character --character ${view.id}, then play the game and watch the motion on the real character before calling it done.`,
|
|
7804
|
-
model_segment: `One GLB with NAMED parts \u2014 load
|
|
7805
|
-
model_rig: `Rigged GLB (Tripo skeleton) \u2014 load
|
|
7806
|
-
model_animation: `Animated GLB \u2014 load
|
|
7863
|
+
model_segment: `One GLB with NAMED parts \u2014 load it through the quality kit (loadModelWithFallback, as for a plain model), then getObjectByName / traverse scene.children to move, detach, or swap a part. Part names print above when the provider reports them.`,
|
|
7864
|
+
model_rig: `Rigged GLB (Tripo skeleton) \u2014 load it through the quality kit (loadModelWithFallback); give it motion with genex model animate ${view.id} --preset walk, or drive the bones in code.`,
|
|
7865
|
+
model_animation: `Animated GLB \u2014 load it through the quality kit (loadModelWithFallback) and play its clips via THREE.AnimationMixer (gltf.animations).`
|
|
7807
7866
|
};
|
|
7808
7867
|
const base = hint[kind];
|
|
7809
7868
|
return url && HINT_URL_KINDS.has(kind) ? `${base} url = "${url}"` : base;
|
|
@@ -7982,7 +8041,111 @@ function writeJson(value) {
|
|
|
7982
8041
|
}
|
|
7983
8042
|
|
|
7984
8043
|
// src/commands/model-sub.ts
|
|
7985
|
-
|
|
8044
|
+
import fs18 from "fs/promises";
|
|
8045
|
+
import path17 from "path";
|
|
8046
|
+
var MODEL_SUBCOMMANDS = ["segment", "rig", "animate", "import"];
|
|
8047
|
+
function apiErrorMessage(data, fallback) {
|
|
8048
|
+
if (typeof data !== "object" || data === null) return fallback;
|
|
8049
|
+
const body = data;
|
|
8050
|
+
if (typeof body.message === "string" && body.message) return body.message;
|
|
8051
|
+
if (typeof body.error === "string" && body.error) return body.error;
|
|
8052
|
+
return fallback;
|
|
8053
|
+
}
|
|
8054
|
+
var MODEL_IMPORT_MAX_BYTES = 64 * 1024 * 1024;
|
|
8055
|
+
var GLB_MAGIC = 1179937895;
|
|
8056
|
+
async function importModelFile(args) {
|
|
8057
|
+
const { log } = args;
|
|
8058
|
+
const filePath = args.filePath.trim();
|
|
8059
|
+
if (!/\.glb$/i.test(filePath)) {
|
|
8060
|
+
log.error(`\`import\` takes a .glb file (binary glTF 2.0). Export ${path17.basename(filePath) || "the model"} as GLB first \u2014 Blender: File \u2192 Export \u2192 glTF 2.0, format "glTF Binary".`);
|
|
8061
|
+
return null;
|
|
8062
|
+
}
|
|
8063
|
+
let bytes;
|
|
8064
|
+
try {
|
|
8065
|
+
bytes = await fs18.readFile(filePath);
|
|
8066
|
+
} catch {
|
|
8067
|
+
log.error(`Couldn't read ${filePath}.`);
|
|
8068
|
+
return null;
|
|
8069
|
+
}
|
|
8070
|
+
if (bytes.byteLength > MODEL_IMPORT_MAX_BYTES) {
|
|
8071
|
+
log.error(`${path17.basename(filePath)} is ${(bytes.byteLength / 1e6).toFixed(1)} MB; imports are capped at ${MODEL_IMPORT_MAX_BYTES / 1024 / 1024} MB. Shrink its textures (they are usually the bulk) and try again.`);
|
|
8072
|
+
return null;
|
|
8073
|
+
}
|
|
8074
|
+
if (bytes.byteLength < 12 || bytes.readUInt32LE(0) !== GLB_MAGIC) {
|
|
8075
|
+
log.error(`${path17.basename(filePath)} is not a GLB (no glTF magic). A .gltf + .bin pair must be exported as one binary .glb.`);
|
|
8076
|
+
return null;
|
|
8077
|
+
}
|
|
8078
|
+
const headers = { "Content-Type": "application/json", Authorization: `Bearer ${args.token}` };
|
|
8079
|
+
const minted = await apiFetch(`${args.apiUrl}/api/generations/import`, {
|
|
8080
|
+
method: "POST",
|
|
8081
|
+
headers,
|
|
8082
|
+
body: JSON.stringify({ filename: path17.basename(filePath), bytes: bytes.byteLength, contentType: "model/gltf-binary" })
|
|
8083
|
+
});
|
|
8084
|
+
if (printedStructuredError(minted)) return null;
|
|
8085
|
+
if (!minted.ok) {
|
|
8086
|
+
const data = await minted.json().catch(() => ({}));
|
|
8087
|
+
log.error(apiErrorMessage(data, `Couldn't start the import (HTTP ${minted.status}).`));
|
|
8088
|
+
return null;
|
|
8089
|
+
}
|
|
8090
|
+
const { id, uploadUrl, url } = await minted.json();
|
|
8091
|
+
log.dim(` uploading ${path17.basename(filePath)} (${(bytes.byteLength / 1e6).toFixed(1)} MB)\u2026`);
|
|
8092
|
+
const put = await fetch(uploadUrl, {
|
|
8093
|
+
method: "PUT",
|
|
8094
|
+
headers: { "Content-Type": "model/gltf-binary", "Content-Length": String(bytes.byteLength) },
|
|
8095
|
+
body: new Uint8Array(bytes)
|
|
8096
|
+
});
|
|
8097
|
+
if (!put.ok) {
|
|
8098
|
+
log.error(`Upload failed (HTTP ${put.status}). Nothing was charged \u2014 run the import again.`);
|
|
8099
|
+
return null;
|
|
8100
|
+
}
|
|
8101
|
+
const done = await apiFetch(`${args.apiUrl}/api/generations/import/${encodeURIComponent(id)}/complete`, {
|
|
8102
|
+
method: "POST",
|
|
8103
|
+
headers
|
|
8104
|
+
});
|
|
8105
|
+
if (printedStructuredError(done)) return null;
|
|
8106
|
+
if (!done.ok) {
|
|
8107
|
+
const data = await done.json().catch(() => ({}));
|
|
8108
|
+
log.error(apiErrorMessage(data, `The import was refused (HTTP ${done.status}).`));
|
|
8109
|
+
return null;
|
|
8110
|
+
}
|
|
8111
|
+
const view = await done.json();
|
|
8112
|
+
return { id, url, inspection: view.inspection ?? null };
|
|
8113
|
+
}
|
|
8114
|
+
async function runModelImport(opts) {
|
|
8115
|
+
const log = createLogger({ quiet: opts.quiet || opts.json });
|
|
8116
|
+
const filePath = opts.sourceId?.trim();
|
|
8117
|
+
if (!filePath) {
|
|
8118
|
+
log.error(`Missing file. Usage: ${c.cyan("genex model import <file.glb>")}`);
|
|
8119
|
+
process.exitCode = 1;
|
|
8120
|
+
return;
|
|
8121
|
+
}
|
|
8122
|
+
const token = opts.token ?? await readUserToken(opts.envPath);
|
|
8123
|
+
if (!token) {
|
|
8124
|
+
log.error("Not authorized. Run `genex init` first to sign in.");
|
|
8125
|
+
process.exitCode = 1;
|
|
8126
|
+
return;
|
|
8127
|
+
}
|
|
8128
|
+
const project = await readProject();
|
|
8129
|
+
const apiUrl = getApiUrl(opts.apiUrl ?? project?.apiUrl);
|
|
8130
|
+
const imported = await importModelFile({ apiUrl, token, filePath, log });
|
|
8131
|
+
if (!imported) {
|
|
8132
|
+
process.exitCode = 1;
|
|
8133
|
+
return;
|
|
8134
|
+
}
|
|
8135
|
+
const next = {
|
|
8136
|
+
rig: `genex model rig ${imported.id}`,
|
|
8137
|
+
character: `genex character import ${filePath}`
|
|
8138
|
+
};
|
|
8139
|
+
if (opts.json) {
|
|
8140
|
+
writeJson({ kind: "model", id: imported.id, status: "completed", url: imported.url, inspection: imported.inspection, nextCommands: next });
|
|
8141
|
+
return;
|
|
8142
|
+
}
|
|
8143
|
+
const facts = imported.inspection ? ` \xB7 ${imported.inspection.faceCount.toLocaleString("en-US")} faces${imported.inspection.skinned ? " \xB7 already skinned" : ""}` : "";
|
|
8144
|
+
log.success(`Imported (${imported.id})${facts}`);
|
|
8145
|
+
log.plain(` ${imported.url}`);
|
|
8146
|
+
log.plain(` Rig it (7 body plans): ${c.cyan(next.rig)}`);
|
|
8147
|
+
log.plain(` Or make it a playable humanoid: ${c.cyan(next.character)}`);
|
|
8148
|
+
}
|
|
7986
8149
|
var SEGMENT_GRANULARITIES = ["simple", "balanced", "detailed"];
|
|
7987
8150
|
var MODEL_RIG_TYPES = [
|
|
7988
8151
|
"biped",
|
|
@@ -8271,8 +8434,8 @@ async function toRow(e, v, cwd) {
|
|
|
8271
8434
|
}
|
|
8272
8435
|
|
|
8273
8436
|
// src/commands/controller.ts
|
|
8274
|
-
import
|
|
8275
|
-
import
|
|
8437
|
+
import fs20 from "fs/promises";
|
|
8438
|
+
import path19 from "path";
|
|
8276
8439
|
|
|
8277
8440
|
// ../../packages/meshy-animation-catalog/src/index.ts
|
|
8278
8441
|
import { createHash } from "crypto";
|
|
@@ -17402,9 +17565,9 @@ function searchMeshyAnimations(query, options = {}) {
|
|
|
17402
17565
|
}
|
|
17403
17566
|
|
|
17404
17567
|
// src/lib/anims.ts
|
|
17405
|
-
import
|
|
17406
|
-
import
|
|
17407
|
-
var ANIMS_DEST =
|
|
17568
|
+
import fs19 from "fs/promises";
|
|
17569
|
+
import path18 from "path";
|
|
17570
|
+
var ANIMS_DEST = path18.join("public", "assets", "anims");
|
|
17408
17571
|
var HIDDEN_TAG = "reference";
|
|
17409
17572
|
async function runAnims(opts) {
|
|
17410
17573
|
const log = createLogger({ quiet: opts.quiet });
|
|
@@ -17420,7 +17583,7 @@ async function runAnims(opts) {
|
|
|
17420
17583
|
printCatalog(log, manifest, selectors);
|
|
17421
17584
|
return;
|
|
17422
17585
|
}
|
|
17423
|
-
const controllerMarker =
|
|
17586
|
+
const controllerMarker = path18.join(root, "src", "controllers", "character");
|
|
17424
17587
|
if (!await exists2(controllerMarker)) {
|
|
17425
17588
|
log.error(
|
|
17426
17589
|
`No character controller in this game (missing ${c.cyan("src/controllers/character/")}).`
|
|
@@ -17429,11 +17592,11 @@ async function runAnims(opts) {
|
|
|
17429
17592
|
process.exitCode = 1;
|
|
17430
17593
|
return;
|
|
17431
17594
|
}
|
|
17432
|
-
const destDir =
|
|
17433
|
-
const gameManifestPath =
|
|
17595
|
+
const destDir = path18.join(root, ANIMS_DEST);
|
|
17596
|
+
const gameManifestPath = path18.join(destDir, "manifest.json");
|
|
17434
17597
|
if (opts.reset) {
|
|
17435
|
-
await
|
|
17436
|
-
log.step(`Cleared ${c.cyan(ANIMS_DEST +
|
|
17598
|
+
await fs19.rm(destDir, { recursive: true, force: true });
|
|
17599
|
+
log.step(`Cleared ${c.cyan(ANIMS_DEST + path18.sep)} (--reset)`);
|
|
17437
17600
|
}
|
|
17438
17601
|
if (selectors.length === 0) {
|
|
17439
17602
|
const installed = await readGameManifest(gameManifestPath);
|
|
@@ -17471,35 +17634,35 @@ async function runAnims(opts) {
|
|
|
17471
17634
|
}
|
|
17472
17635
|
}
|
|
17473
17636
|
const wanted = [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
17474
|
-
const cacheDir =
|
|
17637
|
+
const cacheDir = path18.join(
|
|
17475
17638
|
opts.cacheDir ?? getAnimsCacheDir(),
|
|
17476
17639
|
`${manifest.library}-v${manifest.version}`
|
|
17477
17640
|
);
|
|
17478
|
-
await
|
|
17479
|
-
await
|
|
17641
|
+
await fs19.mkdir(cacheDir, { recursive: true });
|
|
17642
|
+
await fs19.mkdir(destDir, { recursive: true });
|
|
17480
17643
|
const base = getAnimsBase(opts.animsBase);
|
|
17481
17644
|
let installedCount = 0;
|
|
17482
17645
|
let presentCount = 0;
|
|
17483
17646
|
let addedBytes = 0;
|
|
17484
17647
|
const failures = [];
|
|
17485
17648
|
for (const entry of wanted) {
|
|
17486
|
-
const dest =
|
|
17649
|
+
const dest = path18.join(destDir, entry.file);
|
|
17487
17650
|
if (await hasSize(dest, entry.bytes)) {
|
|
17488
17651
|
presentCount++;
|
|
17489
17652
|
continue;
|
|
17490
17653
|
}
|
|
17491
17654
|
try {
|
|
17492
|
-
const cached =
|
|
17655
|
+
const cached = path18.join(cacheDir, entry.file);
|
|
17493
17656
|
if (!await hasSize(cached, entry.bytes)) {
|
|
17494
17657
|
const res = await fetch(base + entry.file);
|
|
17495
17658
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
17496
17659
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
17497
|
-
await
|
|
17660
|
+
await fs19.writeFile(cached, buf);
|
|
17498
17661
|
}
|
|
17499
|
-
await
|
|
17662
|
+
await fs19.copyFile(cached, dest);
|
|
17500
17663
|
installedCount++;
|
|
17501
17664
|
addedBytes += entry.bytes;
|
|
17502
|
-
log.dim(` ${
|
|
17665
|
+
log.dim(` ${path18.join(ANIMS_DEST, entry.file)} (${formatMb(entry.bytes)})`);
|
|
17503
17666
|
} catch (err) {
|
|
17504
17667
|
failures.push(`${entry.name} (${err instanceof Error ? err.message : String(err)})`);
|
|
17505
17668
|
}
|
|
@@ -17515,13 +17678,13 @@ async function runAnims(opts) {
|
|
|
17515
17678
|
version: manifest.version,
|
|
17516
17679
|
clips: [...union].sort((a, b) => a.localeCompare(b))
|
|
17517
17680
|
};
|
|
17518
|
-
await
|
|
17681
|
+
await fs19.writeFile(gameManifestPath, JSON.stringify(gameManifest, null, 2) + "\n");
|
|
17519
17682
|
log.plain("");
|
|
17520
17683
|
const parts = [`${installedCount} clip${installedCount === 1 ? "" : "s"} installed`];
|
|
17521
17684
|
if (presentCount > 0) parts.push(`${presentCount} already present`);
|
|
17522
17685
|
if (bundledSkips > 0) parts.push(`${bundledSkips} already bundled in animation-library.glb`);
|
|
17523
17686
|
log.success(
|
|
17524
|
-
`${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST +
|
|
17687
|
+
`${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST + path18.sep)}${addedBytes > 0 ? ` (+${formatMb(addedBytes)})` : ""}`
|
|
17525
17688
|
);
|
|
17526
17689
|
for (const [selector, entries] of resolved) {
|
|
17527
17690
|
const names = entries.filter((e) => !coreNames.has(e.name)).map((e) => e.name);
|
|
@@ -17553,8 +17716,8 @@ async function loadManifest(baseOverride) {
|
|
|
17553
17716
|
}
|
|
17554
17717
|
} catch {
|
|
17555
17718
|
}
|
|
17556
|
-
const snapshotPath =
|
|
17557
|
-
const manifest = JSON.parse(await
|
|
17719
|
+
const snapshotPath = path18.join(getTemplatesDir(), "controllers", "assets", "anims-manifest.json");
|
|
17720
|
+
const manifest = JSON.parse(await fs19.readFile(snapshotPath, "utf8"));
|
|
17558
17721
|
return { manifest, source: "snapshot" };
|
|
17559
17722
|
}
|
|
17560
17723
|
function resolveSelectors(manifest, selectors) {
|
|
@@ -17672,21 +17835,21 @@ function printCatalog(log, manifest, selectors) {
|
|
|
17672
17835
|
}
|
|
17673
17836
|
async function readGameManifest(file) {
|
|
17674
17837
|
try {
|
|
17675
|
-
return JSON.parse(await
|
|
17838
|
+
return JSON.parse(await fs19.readFile(file, "utf8"));
|
|
17676
17839
|
} catch {
|
|
17677
17840
|
return null;
|
|
17678
17841
|
}
|
|
17679
17842
|
}
|
|
17680
17843
|
async function hasSize(file, bytes) {
|
|
17681
17844
|
try {
|
|
17682
|
-
return (await
|
|
17845
|
+
return (await fs19.stat(file)).size === bytes;
|
|
17683
17846
|
} catch {
|
|
17684
17847
|
return false;
|
|
17685
17848
|
}
|
|
17686
17849
|
}
|
|
17687
17850
|
async function exists2(p) {
|
|
17688
17851
|
try {
|
|
17689
|
-
await
|
|
17852
|
+
await fs19.access(p);
|
|
17690
17853
|
return true;
|
|
17691
17854
|
} catch {
|
|
17692
17855
|
return false;
|
|
@@ -17819,6 +17982,7 @@ var CONTROLLER_FILE_SETS = {
|
|
|
17819
17982
|
code: [
|
|
17820
17983
|
"quality/tier.ts",
|
|
17821
17984
|
"quality/governor.ts",
|
|
17985
|
+
"quality/deadline.ts",
|
|
17822
17986
|
"quality/pick-asset.ts",
|
|
17823
17987
|
"quality/gltf-loader.ts",
|
|
17824
17988
|
"quality/depth.ts",
|
|
@@ -17886,8 +18050,8 @@ var CONTROLLER_FILE_SETS = {
|
|
|
17886
18050
|
]
|
|
17887
18051
|
}
|
|
17888
18052
|
};
|
|
17889
|
-
var CODE_DEST =
|
|
17890
|
-
var ASSETS_DEST =
|
|
18053
|
+
var CODE_DEST = path19.join("src", "controllers");
|
|
18054
|
+
var ASSETS_DEST = path19.join("public", "assets");
|
|
17891
18055
|
async function runController(opts) {
|
|
17892
18056
|
const log = createLogger({ quiet: opts.quiet });
|
|
17893
18057
|
if (opts.kind?.trim() === "anims") {
|
|
@@ -17904,31 +18068,31 @@ async function runController(opts) {
|
|
|
17904
18068
|
process.exitCode = 1;
|
|
17905
18069
|
return;
|
|
17906
18070
|
}
|
|
17907
|
-
const srcDir =
|
|
18071
|
+
const srcDir = path19.join(getTemplatesDir(), "controllers");
|
|
17908
18072
|
const root = opts.cwd ?? process.cwd();
|
|
17909
18073
|
const set = CONTROLLER_FILE_SETS[kind];
|
|
17910
18074
|
log.plain(c.bold(`genex controller ${kind}`));
|
|
17911
18075
|
log.plain("");
|
|
17912
|
-
log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST +
|
|
18076
|
+
log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST + path19.sep)}`);
|
|
17913
18077
|
const plan = [
|
|
17914
|
-
...set.code.map((rel) => ({ from: rel, rel:
|
|
18078
|
+
...set.code.map((rel) => ({ from: rel, rel: path19.join(CODE_DEST, rel) })),
|
|
17915
18079
|
...set.assets.map((rel) => ({
|
|
17916
18080
|
from: rel,
|
|
17917
|
-
rel:
|
|
18081
|
+
rel: path19.join(ASSETS_DEST, path19.basename(rel))
|
|
17918
18082
|
}))
|
|
17919
18083
|
];
|
|
17920
18084
|
let copied = 0;
|
|
17921
18085
|
let skipped = 0;
|
|
17922
18086
|
try {
|
|
17923
18087
|
for (const file of plan) {
|
|
17924
|
-
const dest =
|
|
18088
|
+
const dest = path19.join(root, file.rel);
|
|
17925
18089
|
if (!opts.force && await exists3(dest)) {
|
|
17926
18090
|
skipped++;
|
|
17927
18091
|
log.dim(` skipped ${file.rel} (exists \u2014 use --force to overwrite)`);
|
|
17928
18092
|
continue;
|
|
17929
18093
|
}
|
|
17930
|
-
await
|
|
17931
|
-
await
|
|
18094
|
+
await fs20.mkdir(path19.dirname(dest), { recursive: true });
|
|
18095
|
+
await fs20.copyFile(path19.join(srcDir, file.from), dest);
|
|
17932
18096
|
copied++;
|
|
17933
18097
|
log.dim(` ${file.rel}`);
|
|
17934
18098
|
}
|
|
@@ -17981,7 +18145,7 @@ async function runController(opts) {
|
|
|
17981
18145
|
for (const line of set.sketch) {
|
|
17982
18146
|
log.dim(` ${line}`);
|
|
17983
18147
|
}
|
|
17984
|
-
if (kind === "character" && !await exists3(
|
|
18148
|
+
if (kind === "character" && !await exists3(path19.join(root, ASSETS_DEST, "meshy-character.json"))) {
|
|
17985
18149
|
log.plain("");
|
|
17986
18150
|
log.plain(
|
|
17987
18151
|
` ${stepOffset + 3}. This game has no generated character yet \u2014 ${c.cyan(
|
|
@@ -18009,13 +18173,13 @@ async function installMeshyCharacterManifest(args) {
|
|
|
18009
18173
|
}
|
|
18010
18174
|
const body = await response.json();
|
|
18011
18175
|
const manifest = body.manifest;
|
|
18012
|
-
if (!manifest || manifest.schema !== 1 || manifest.rig !== "meshy-biped" || manifest.characterId !== args.characterId || typeof manifest.model?.url !== "string" || typeof manifest.model.skeletonSignature !== "string" || !Array.isArray(manifest.clips) || !manifest.locomotion?.slots) {
|
|
18176
|
+
if (!manifest || manifest.schema !== 1 || manifest.rig !== "meshy-biped" && manifest.rig !== "uthana-biped" || manifest.characterId !== args.characterId || typeof manifest.model?.url !== "string" || typeof manifest.model.skeletonSignature !== "string" || !Array.isArray(manifest.clips) || !manifest.locomotion?.slots) {
|
|
18013
18177
|
throw new Error("The API returned an invalid Meshy character manifest.");
|
|
18014
18178
|
}
|
|
18015
18179
|
assertCompleteMeshyControllerPack(manifest);
|
|
18016
|
-
const destination =
|
|
18017
|
-
await
|
|
18018
|
-
await
|
|
18180
|
+
const destination = path19.join(args.root, ASSETS_DEST, "meshy-character.json");
|
|
18181
|
+
await fs20.mkdir(path19.dirname(destination), { recursive: true });
|
|
18182
|
+
await fs20.writeFile(
|
|
18019
18183
|
destination,
|
|
18020
18184
|
`${JSON.stringify(manifest, null, 2)}
|
|
18021
18185
|
`
|
|
@@ -18025,6 +18189,13 @@ async function installMeshyCharacterManifest(args) {
|
|
|
18025
18189
|
const pack2 = manifest.controllerPack;
|
|
18026
18190
|
if (typeof pack2?.key === "string" && typeof pack2.version === "number") {
|
|
18027
18191
|
args.log.success(`Meshy controller pack ${pack2.key} v${pack2.version}`);
|
|
18192
|
+
} else if (manifest.rig === "uthana-biped") {
|
|
18193
|
+
const slotCount = Object.keys(manifest.locomotion?.slots ?? {}).length;
|
|
18194
|
+
if (slotCount === 0) {
|
|
18195
|
+
args.log.warn(`Uthana-rigged import with no locomotion yet \u2014 run \`genex character animate ${args.characterId} --locomotion\` or the body will stand still.`);
|
|
18196
|
+
} else {
|
|
18197
|
+
args.log.success(`Uthana-rigged import \xB7 ${slotCount} locomotion slot${slotCount === 1 ? "" : "s"} from generated motion`);
|
|
18198
|
+
}
|
|
18028
18199
|
} else {
|
|
18029
18200
|
args.log.warn("Legacy Meshy manifest \u2014 regenerate the character for the immutable preview-reviewed neutral-v3 locomotion pack.");
|
|
18030
18201
|
}
|
|
@@ -18153,14 +18324,14 @@ function assertCompleteMeshyControllerPack(manifest) {
|
|
|
18153
18324
|
}
|
|
18154
18325
|
async function installFallbackAvatar(args) {
|
|
18155
18326
|
const { root, srcDir, log } = args;
|
|
18156
|
-
const dest =
|
|
18157
|
-
await
|
|
18158
|
-
await
|
|
18327
|
+
const dest = path19.join(root, ASSETS_DEST, "avatar.vrm");
|
|
18328
|
+
await fs20.mkdir(path19.dirname(dest), { recursive: true });
|
|
18329
|
+
await fs20.copyFile(path19.join(srcDir, "assets", "default-avatar.vrm"), dest);
|
|
18159
18330
|
log.dim(" public/assets/avatar.vrm (fallback avatar \u2014 bundled CC0 default)");
|
|
18160
18331
|
}
|
|
18161
18332
|
async function exists3(p) {
|
|
18162
18333
|
try {
|
|
18163
|
-
await
|
|
18334
|
+
await fs20.access(p);
|
|
18164
18335
|
return true;
|
|
18165
18336
|
} catch {
|
|
18166
18337
|
return false;
|
|
@@ -18168,8 +18339,8 @@ async function exists3(p) {
|
|
|
18168
18339
|
}
|
|
18169
18340
|
|
|
18170
18341
|
// src/commands/character.ts
|
|
18171
|
-
import
|
|
18172
|
-
import
|
|
18342
|
+
import fs21 from "fs/promises";
|
|
18343
|
+
import path20 from "path";
|
|
18173
18344
|
function exactAnimation(selector) {
|
|
18174
18345
|
const trimmed = selector.trim();
|
|
18175
18346
|
if (/^-?\d+$/.test(trimmed)) return animationById(Number(trimmed));
|
|
@@ -18201,7 +18372,7 @@ async function context(opts) {
|
|
|
18201
18372
|
return { token, apiUrl: getApiUrl(opts.apiUrl ?? project?.apiUrl) };
|
|
18202
18373
|
}
|
|
18203
18374
|
var CHARACTER_BRIEF_MAX_CHARS = 600;
|
|
18204
|
-
function
|
|
18375
|
+
function apiErrorMessage2(data, fallback) {
|
|
18205
18376
|
if (typeof data !== "object" || data === null) return fallback;
|
|
18206
18377
|
const body = data;
|
|
18207
18378
|
if (typeof body.message === "string" && body.message) return body.message;
|
|
@@ -18225,7 +18396,7 @@ async function quote(url, token, body) {
|
|
|
18225
18396
|
if (printedStructuredError(response)) return null;
|
|
18226
18397
|
if (!response.ok) {
|
|
18227
18398
|
const data = await response.json().catch(() => ({}));
|
|
18228
|
-
throw new Error(
|
|
18399
|
+
throw new Error(apiErrorMessage2(data, `Quote failed (HTTP ${response.status}).`));
|
|
18229
18400
|
}
|
|
18230
18401
|
return (await response.json()).quote;
|
|
18231
18402
|
}
|
|
@@ -18249,7 +18420,7 @@ function showAmbiguity(selector, candidates, log, json = false) {
|
|
|
18249
18420
|
}
|
|
18250
18421
|
process.exitCode = 1;
|
|
18251
18422
|
}
|
|
18252
|
-
var INSTALLED_MANIFEST =
|
|
18423
|
+
var INSTALLED_MANIFEST = path20.join("public", "assets", "meshy-character.json");
|
|
18253
18424
|
async function resolveAdoptTarget(selector) {
|
|
18254
18425
|
const trimmed = selector?.trim();
|
|
18255
18426
|
if (trimmed && !trimmed.endsWith(".json")) {
|
|
@@ -18258,7 +18429,7 @@ async function resolveAdoptTarget(selector) {
|
|
|
18258
18429
|
const file = trimmed ?? INSTALLED_MANIFEST;
|
|
18259
18430
|
let raw;
|
|
18260
18431
|
try {
|
|
18261
|
-
raw = await
|
|
18432
|
+
raw = await fs21.readFile(file, "utf8");
|
|
18262
18433
|
} catch {
|
|
18263
18434
|
return {
|
|
18264
18435
|
ok: false,
|
|
@@ -18334,6 +18505,81 @@ async function runCharacterAdopt(opts) {
|
|
|
18334
18505
|
}
|
|
18335
18506
|
});
|
|
18336
18507
|
}
|
|
18508
|
+
async function runCharacterImport(opts) {
|
|
18509
|
+
const log = createLogger({ quiet: opts.quiet || opts.json });
|
|
18510
|
+
const filePath = opts.importPath?.trim();
|
|
18511
|
+
if (!filePath) {
|
|
18512
|
+
fail2(opts, "Missing file. Usage: genex character import <file.glb> [--height 1.8] [--no-fingers]");
|
|
18513
|
+
return;
|
|
18514
|
+
}
|
|
18515
|
+
if (opts.texture !== void 0 || opts.ultra === false || opts.pose !== void 0 || opts.polycount !== void 0) {
|
|
18516
|
+
fail2(opts, "--texture / --no-ultra / --pose / --polycount are Meshy generation knobs; an import keeps your mesh exactly as it is. Uthana only adds the skeleton (--no-fingers skips finger joints).");
|
|
18517
|
+
return;
|
|
18518
|
+
}
|
|
18519
|
+
if (opts.controllerPack === false || (opts.actions?.length ?? 0) > 0) {
|
|
18520
|
+
fail2(opts, "An imported character has no Meshy controller pack and takes no catalog --animation: its motion comes from `genex character animate <id> --locomotion` and plain-word verbs.");
|
|
18521
|
+
return;
|
|
18522
|
+
}
|
|
18523
|
+
const ctx = await context(opts);
|
|
18524
|
+
if (!ctx) {
|
|
18525
|
+
fail2(opts, "Not authorized. Run `genex init` first to sign in.");
|
|
18526
|
+
return;
|
|
18527
|
+
}
|
|
18528
|
+
const imported = await importModelFile({ apiUrl: ctx.apiUrl, token: ctx.token, filePath, log });
|
|
18529
|
+
if (!imported) {
|
|
18530
|
+
process.exitCode = 1;
|
|
18531
|
+
return;
|
|
18532
|
+
}
|
|
18533
|
+
const body = {
|
|
18534
|
+
sourceGenerationId: imported.id,
|
|
18535
|
+
includeFingers: opts.fingers !== false,
|
|
18536
|
+
...opts.height === void 0 ? {} : { heightMeters: opts.height }
|
|
18537
|
+
};
|
|
18538
|
+
const price = await quote(`${ctx.apiUrl}/api/characters/import/quote`, ctx.token, body);
|
|
18539
|
+
if (!price) {
|
|
18540
|
+
process.exitCode = 1;
|
|
18541
|
+
return;
|
|
18542
|
+
}
|
|
18543
|
+
if (!opts.json) {
|
|
18544
|
+
log.plain(c.bold("Character import quote"));
|
|
18545
|
+
log.plain(` ${price.credits} Genex credits \xB7 Uthana auto-rig${opts.fingers === false ? " (no finger joints)" : " with finger joints"} \xB7 the model upload was free`);
|
|
18546
|
+
log.plain("");
|
|
18547
|
+
}
|
|
18548
|
+
await runWorkflow({
|
|
18549
|
+
opts,
|
|
18550
|
+
ctx,
|
|
18551
|
+
kind: "character",
|
|
18552
|
+
prompt: `Import ${path20.basename(filePath)} as a rigged character`,
|
|
18553
|
+
createPath: "/api/characters/import",
|
|
18554
|
+
body,
|
|
18555
|
+
quote: price,
|
|
18556
|
+
completed: (view) => {
|
|
18557
|
+
const confidence = typeof view.metadata?.autoRigConfidence === "number" ? view.metadata.autoRigConfidence : null;
|
|
18558
|
+
const next = {
|
|
18559
|
+
locomotion: `genex character animate ${view.id} --locomotion`,
|
|
18560
|
+
install: `genex controller character --character ${view.id}`
|
|
18561
|
+
};
|
|
18562
|
+
if (opts.json) {
|
|
18563
|
+
writeJson({
|
|
18564
|
+
kind: "character",
|
|
18565
|
+
status: view.status,
|
|
18566
|
+
characterId: view.id,
|
|
18567
|
+
modelGenerationId: imported.id,
|
|
18568
|
+
rigProvider: "uthana",
|
|
18569
|
+
autoRigConfidence: confidence,
|
|
18570
|
+
nextCommand: next.locomotion,
|
|
18571
|
+
nextCommands: next
|
|
18572
|
+
});
|
|
18573
|
+
return;
|
|
18574
|
+
}
|
|
18575
|
+
log.success(`Rigged by Uthana${confidence === null ? "" : ` (confidence ${confidence.toFixed(2)})`}: ${c.cyan(view.id)}`);
|
|
18576
|
+
log.plain(" It has a skeleton and no clips yet. Give it the walk/run set, then point the game at it:");
|
|
18577
|
+
log.plain(` ${c.cyan(next.locomotion)}`);
|
|
18578
|
+
log.plain(` ${c.cyan(next.install)}`);
|
|
18579
|
+
log.dim(' Any move in plain words: genex character animate <id> "<what it should do>" \xB7 the Meshy catalog does not apply to this rig.');
|
|
18580
|
+
}
|
|
18581
|
+
});
|
|
18582
|
+
}
|
|
18337
18583
|
async function runCharacter(opts) {
|
|
18338
18584
|
if (opts.directText) {
|
|
18339
18585
|
await runDirectTextCharacter(opts);
|
|
@@ -18362,7 +18608,7 @@ async function postWorkflow(url, token, body) {
|
|
|
18362
18608
|
if (printedStructuredError(response)) return null;
|
|
18363
18609
|
if (!response.ok) {
|
|
18364
18610
|
const data2 = await response.json().catch(() => ({}));
|
|
18365
|
-
throw new Error(
|
|
18611
|
+
throw new Error(apiErrorMessage2(data2, `Character workflow request failed (HTTP ${response.status}).`));
|
|
18366
18612
|
}
|
|
18367
18613
|
const data = await response.json();
|
|
18368
18614
|
if (typeof data.id !== "string" || data.id.length === 0) {
|
|
@@ -18432,7 +18678,11 @@ async function runCharacterConcept(opts) {
|
|
|
18432
18678
|
return;
|
|
18433
18679
|
}
|
|
18434
18680
|
if (opts.polycount !== void 0) {
|
|
18435
|
-
fail2(opts, "--polycount is only available with --direct-text. The approval flow
|
|
18681
|
+
fail2(opts, "--polycount is only available with --direct-text. The approval flow takes its face budget on `character finalize --approve-remesh <faces>`.", "character_concept");
|
|
18682
|
+
return;
|
|
18683
|
+
}
|
|
18684
|
+
if (opts.texture !== void 0 || opts.ultra === false || opts.pose !== void 0) {
|
|
18685
|
+
fail2(opts, "--texture / --no-ultra / --pose apply to the paid mesh call: pass them to `character preview` (guided) or `character --direct-text` / `creature` (one shot). Concepts are images.", "character_concept");
|
|
18436
18686
|
return;
|
|
18437
18687
|
}
|
|
18438
18688
|
if (opts.controllerPack === false) {
|
|
@@ -18528,10 +18778,26 @@ async function runDirectTextCharacter(opts) {
|
|
|
18528
18778
|
actionIds: resolved.actionIds,
|
|
18529
18779
|
controllerPack,
|
|
18530
18780
|
...opts.height === void 0 ? {} : { heightMeters: opts.height },
|
|
18531
|
-
targetPolycount: opts.polycount ?? 1e4
|
|
18781
|
+
targetPolycount: opts.polycount ?? 1e4,
|
|
18782
|
+
...meshyQualityOptions(opts),
|
|
18783
|
+
...opts.pose === void 0 ? {} : { poseMode: opts.pose }
|
|
18532
18784
|
}
|
|
18533
18785
|
});
|
|
18534
18786
|
}
|
|
18787
|
+
var CHARACTER_TEXTURE_SIZES = ["2k", "4k", "8k"];
|
|
18788
|
+
var REMESH_TARGET_MIN = 1e4;
|
|
18789
|
+
var REMESH_TARGET_MAX = 1e5;
|
|
18790
|
+
function meshyQualityOptions(opts) {
|
|
18791
|
+
return {
|
|
18792
|
+
...opts.texture === void 0 ? {} : { textureResolution: opts.texture },
|
|
18793
|
+
...opts.ultra === false ? { ultraMode: false } : {}
|
|
18794
|
+
};
|
|
18795
|
+
}
|
|
18796
|
+
function badCharacterTexture(opts) {
|
|
18797
|
+
if (opts.texture === void 0) return null;
|
|
18798
|
+
if (CHARACTER_TEXTURE_SIZES.includes(opts.texture)) return null;
|
|
18799
|
+
return `--texture ${opts.texture} is a \`genex model\` tier. A character takes a texture SIZE: ${CHARACTER_TEXTURE_SIZES.join("|")} (default 4k).`;
|
|
18800
|
+
}
|
|
18535
18801
|
async function runCharacterPreview(opts) {
|
|
18536
18802
|
const log = createLogger({ quiet: opts.quiet || opts.json });
|
|
18537
18803
|
const conceptId = opts.conceptId?.trim();
|
|
@@ -18552,7 +18818,12 @@ async function runCharacterPreview(opts) {
|
|
|
18552
18818
|
fail2(opts, "Not authorized. Run `genex init` first to sign in.", "character_preview");
|
|
18553
18819
|
return;
|
|
18554
18820
|
}
|
|
18555
|
-
const
|
|
18821
|
+
const badTexture = badCharacterTexture(opts);
|
|
18822
|
+
if (badTexture) {
|
|
18823
|
+
fail2(opts, badTexture, "character_preview");
|
|
18824
|
+
return;
|
|
18825
|
+
}
|
|
18826
|
+
const body = { candidateIndex: opts.candidate, userApproved: true, ...meshyQualityOptions(opts) };
|
|
18556
18827
|
const basePath = `/api/characters/concepts/${encodeURIComponent(conceptId)}/previews`;
|
|
18557
18828
|
const price = await quote(`${ctx.apiUrl}${basePath}/quote`, ctx.token, body);
|
|
18558
18829
|
if (!price) {
|
|
@@ -18590,12 +18861,20 @@ async function runCharacterFinalize(opts) {
|
|
|
18590
18861
|
fail2(opts, "STOP: finalization requires explicit user approval of all four preview views. Re-run with --user-approved only after approval.");
|
|
18591
18862
|
return;
|
|
18592
18863
|
}
|
|
18593
|
-
|
|
18594
|
-
|
|
18864
|
+
const remeshTarget = opts.approveRemesh;
|
|
18865
|
+
if (remeshTarget === void 0 || !Number.isInteger(remeshTarget) || remeshTarget < REMESH_TARGET_MIN || remeshTarget > REMESH_TARGET_MAX) {
|
|
18866
|
+
fail2(
|
|
18867
|
+
opts,
|
|
18868
|
+
`--approve-remesh <faces> is required: the face budget of the rigging copy, ${REMESH_TARGET_MIN}-${REMESH_TARGET_MAX} (10000 for a crowd body, 30000 or more for a hero). The number is the approval.`
|
|
18869
|
+
);
|
|
18595
18870
|
return;
|
|
18596
18871
|
}
|
|
18597
18872
|
if (opts.polycount !== void 0) {
|
|
18598
|
-
fail2(opts, "Do not combine --polycount with finalize; --approve-remesh
|
|
18873
|
+
fail2(opts, "Do not combine --polycount with finalize; --approve-remesh <faces> is the rigging-copy target.");
|
|
18874
|
+
return;
|
|
18875
|
+
}
|
|
18876
|
+
if (opts.texture !== void 0 || opts.ultra === false) {
|
|
18877
|
+
fail2(opts, "--texture / --no-ultra belong to `genex character preview` \u2014 the preview is the paid Meshy call; finalize remeshes and rigs what it made.");
|
|
18599
18878
|
return;
|
|
18600
18879
|
}
|
|
18601
18880
|
if (opts.controllerPack === false) {
|
|
@@ -18615,7 +18894,7 @@ async function runCharacterFinalize(opts) {
|
|
|
18615
18894
|
const body = {
|
|
18616
18895
|
...requestedBuild(opts, resolved.actionIds),
|
|
18617
18896
|
userApproved: true,
|
|
18618
|
-
approvedRemeshTarget:
|
|
18897
|
+
approvedRemeshTarget: remeshTarget
|
|
18619
18898
|
};
|
|
18620
18899
|
const basePath = `/api/characters/previews/${encodeURIComponent(previewId)}/finalize`;
|
|
18621
18900
|
const price = await quote(`${ctx.apiUrl}${basePath}/quote`, ctx.token, body);
|
|
@@ -18626,7 +18905,7 @@ async function runCharacterFinalize(opts) {
|
|
|
18626
18905
|
if (!opts.json) {
|
|
18627
18906
|
log.plain(c.bold("Character finalize quote"));
|
|
18628
18907
|
log.plain(` ${price.credits} Genex credits = base ${price.baseCredits} + actions ${price.animationCredits}`);
|
|
18629
|
-
log.plain(
|
|
18908
|
+
log.plain(` Approved rigging copy: ${remeshTarget.toLocaleString("en-US")} faces; the high-detail source remains preserved in R2.`);
|
|
18630
18909
|
log.plain("");
|
|
18631
18910
|
}
|
|
18632
18911
|
await runWorkflow({
|
|
@@ -18741,22 +19020,22 @@ async function context2(opts) {
|
|
|
18741
19020
|
const project = await readProject();
|
|
18742
19021
|
return { token, apiUrl: getApiUrl(opts.apiUrl ?? project?.apiUrl) };
|
|
18743
19022
|
}
|
|
18744
|
-
async function readVideo(
|
|
19023
|
+
async function readVideo(path27, log) {
|
|
18745
19024
|
let bytes;
|
|
18746
19025
|
try {
|
|
18747
|
-
bytes = await readFile(
|
|
19026
|
+
bytes = await readFile(path27);
|
|
18748
19027
|
} catch {
|
|
18749
|
-
log.error(`Can't read ${
|
|
19028
|
+
log.error(`Can't read ${path27}.`);
|
|
18750
19029
|
return null;
|
|
18751
19030
|
}
|
|
18752
19031
|
if (bytes.byteLength > MAX_VIDEO_BYTES) {
|
|
18753
|
-
log.error(`${basename(
|
|
19032
|
+
log.error(`${basename(path27)} is ${(bytes.byteLength / 1e6).toFixed(0)} MB \u2014 the limit is ${MAX_VIDEO_BYTES / 1e6} MB.`);
|
|
18754
19033
|
return null;
|
|
18755
19034
|
}
|
|
18756
19035
|
return bytes;
|
|
18757
19036
|
}
|
|
18758
|
-
async function uploadVideo(apiUrl, token, characterId,
|
|
18759
|
-
const contentType = /\.mov$/i.test(
|
|
19037
|
+
async function uploadVideo(apiUrl, token, characterId, path27, bytes, log) {
|
|
19038
|
+
const contentType = /\.mov$/i.test(path27) ? "video/quicktime" : "video/mp4";
|
|
18760
19039
|
const minted = await apiFetch(
|
|
18761
19040
|
`${apiUrl}/api/characters/${encodeURIComponent(characterId)}/motions/video-url`,
|
|
18762
19041
|
{
|
|
@@ -18771,7 +19050,7 @@ async function uploadVideo(apiUrl, token, characterId, path26, bytes, log) {
|
|
|
18771
19050
|
return null;
|
|
18772
19051
|
}
|
|
18773
19052
|
const { uploadUrl, videoUrl } = await minted.json();
|
|
18774
|
-
log.dim(` uploading ${basename(
|
|
19053
|
+
log.dim(` uploading ${basename(path27)} (${(bytes.byteLength / 1e6).toFixed(1)} MB)\u2026`);
|
|
18775
19054
|
const put = await fetch(uploadUrl, {
|
|
18776
19055
|
method: "PUT",
|
|
18777
19056
|
headers: { "Content-Type": contentType, "Content-Length": String(bytes.byteLength) },
|
|
@@ -19087,8 +19366,8 @@ function rank(items, query) {
|
|
|
19087
19366
|
}
|
|
19088
19367
|
|
|
19089
19368
|
// src/commands/motion.ts
|
|
19090
|
-
import
|
|
19091
|
-
import
|
|
19369
|
+
import fs22 from "fs/promises";
|
|
19370
|
+
import path21 from "path";
|
|
19092
19371
|
|
|
19093
19372
|
// src/lib/motion/npz.ts
|
|
19094
19373
|
import zlib from "zlib";
|
|
@@ -20339,7 +20618,7 @@ async function motionGen(opts, log) {
|
|
|
20339
20618
|
}
|
|
20340
20619
|
if (opts.constraintsPath !== void 0) {
|
|
20341
20620
|
try {
|
|
20342
|
-
const raw = await
|
|
20621
|
+
const raw = await fs22.readFile(opts.constraintsPath, "utf8");
|
|
20343
20622
|
generationOptions.constraints = JSON.parse(raw);
|
|
20344
20623
|
} catch {
|
|
20345
20624
|
log.error(`Couldn't read the --constraints JSON at ${opts.constraintsPath}.`);
|
|
@@ -20363,10 +20642,10 @@ async function motionGen(opts, log) {
|
|
|
20363
20642
|
async function expandTakes(selectors) {
|
|
20364
20643
|
const out = [];
|
|
20365
20644
|
for (const sel of selectors) {
|
|
20366
|
-
const st = await
|
|
20645
|
+
const st = await fs22.stat(sel).catch(() => null);
|
|
20367
20646
|
if (st?.isDirectory()) {
|
|
20368
|
-
const names = await
|
|
20369
|
-
for (const n of names.sort()) if (n.endsWith(".npz")) out.push(
|
|
20647
|
+
const names = await fs22.readdir(sel);
|
|
20648
|
+
for (const n of names.sort()) if (n.endsWith(".npz")) out.push(path21.join(sel, n));
|
|
20370
20649
|
} else if (st?.isFile()) {
|
|
20371
20650
|
out.push(sel);
|
|
20372
20651
|
} else {
|
|
@@ -20401,7 +20680,7 @@ async function motionVerify(opts, log) {
|
|
|
20401
20680
|
let gates = DEFAULT_GATES;
|
|
20402
20681
|
if (opts.gatesPath) {
|
|
20403
20682
|
try {
|
|
20404
|
-
gates = mergeGates(DEFAULT_GATES, JSON.parse(await
|
|
20683
|
+
gates = mergeGates(DEFAULT_GATES, JSON.parse(await fs22.readFile(opts.gatesPath, "utf8")));
|
|
20405
20684
|
} catch {
|
|
20406
20685
|
log.error(`Couldn't read the --gates JSON at ${opts.gatesPath}.`);
|
|
20407
20686
|
process.exitCode = 1;
|
|
@@ -20423,9 +20702,9 @@ async function motionVerify(opts, log) {
|
|
|
20423
20702
|
}
|
|
20424
20703
|
const reports = [];
|
|
20425
20704
|
for (const file of files) {
|
|
20426
|
-
const stem =
|
|
20705
|
+
const stem = path21.basename(file).replace(/\.npz$/, "");
|
|
20427
20706
|
try {
|
|
20428
|
-
reports.push(analyzeTake(stem, await
|
|
20707
|
+
reports.push(analyzeTake(stem, await fs22.readFile(file), gates));
|
|
20429
20708
|
} catch (err) {
|
|
20430
20709
|
reports.push({
|
|
20431
20710
|
take: stem,
|
|
@@ -20463,7 +20742,7 @@ async function motionCompile(opts, log) {
|
|
|
20463
20742
|
let cfg = DEFAULT_MOTION_CONFIG;
|
|
20464
20743
|
if (opts.configPath) {
|
|
20465
20744
|
try {
|
|
20466
|
-
const patch = JSON.parse(await
|
|
20745
|
+
const patch = JSON.parse(await fs22.readFile(opts.configPath, "utf8"));
|
|
20467
20746
|
cfg = { ...DEFAULT_MOTION_CONFIG, ...patch, idleLoop: { ...DEFAULT_MOTION_CONFIG.idleLoop, ...patch.idleLoop } };
|
|
20468
20747
|
} catch {
|
|
20469
20748
|
log.error(`Couldn't read the --config JSON at ${opts.configPath}.`);
|
|
@@ -20481,16 +20760,16 @@ async function motionCompile(opts, log) {
|
|
|
20481
20760
|
}
|
|
20482
20761
|
const inputs = [];
|
|
20483
20762
|
for (const file of files) {
|
|
20484
|
-
const stem =
|
|
20763
|
+
const stem = path21.basename(file).replace(/\.npz$/, "");
|
|
20485
20764
|
try {
|
|
20486
|
-
inputs.push({ stem, take: loadTake(await
|
|
20765
|
+
inputs.push({ stem, take: loadTake(await fs22.readFile(file)) });
|
|
20487
20766
|
} catch (err) {
|
|
20488
20767
|
log.error(`${stem}: ${err instanceof Error ? err.message : String(err)}`);
|
|
20489
20768
|
process.exitCode = 1;
|
|
20490
20769
|
return;
|
|
20491
20770
|
}
|
|
20492
20771
|
}
|
|
20493
|
-
const setName = opts.set ??
|
|
20772
|
+
const setName = opts.set ?? path21.basename(opts.out).replace(/\.json$/, "");
|
|
20494
20773
|
let result;
|
|
20495
20774
|
try {
|
|
20496
20775
|
result = compileSet(inputs, setName, cfg);
|
|
@@ -20505,9 +20784,9 @@ async function motionCompile(opts, log) {
|
|
|
20505
20784
|
process.exitCode = 1;
|
|
20506
20785
|
return;
|
|
20507
20786
|
}
|
|
20508
|
-
await
|
|
20787
|
+
await fs22.mkdir(path21.dirname(path21.resolve(opts.out)), { recursive: true });
|
|
20509
20788
|
const json = JSON.stringify(result.data);
|
|
20510
|
-
await
|
|
20789
|
+
await fs22.writeFile(opts.out, json);
|
|
20511
20790
|
if (opts.json) {
|
|
20512
20791
|
writeJson({ out: opts.out, set: setName, tracks: Object.keys(result.data.gaits), bytes: json.length });
|
|
20513
20792
|
return;
|
|
@@ -20525,9 +20804,9 @@ var MOTION_RUNTIME_FILES = [
|
|
|
20525
20804
|
var MOTION_PRESETS = {
|
|
20526
20805
|
rifle: ["sets/rifle.json", "sets/jumps.json"]
|
|
20527
20806
|
};
|
|
20528
|
-
var MOTION_DEST =
|
|
20807
|
+
var MOTION_DEST = path21.join("src", "motion");
|
|
20529
20808
|
async function motionInstall(opts, log) {
|
|
20530
|
-
const srcDir =
|
|
20809
|
+
const srcDir = path21.join(getTemplatesDir(), "motion");
|
|
20531
20810
|
const root = opts.cwd ?? process.cwd();
|
|
20532
20811
|
const preset = opts.set;
|
|
20533
20812
|
if (preset !== void 0 && !MOTION_PRESETS[preset]) {
|
|
@@ -20538,21 +20817,21 @@ async function motionInstall(opts, log) {
|
|
|
20538
20817
|
const files = [...MOTION_RUNTIME_FILES, ...preset ? MOTION_PRESETS[preset] : []];
|
|
20539
20818
|
log.plain(c.bold(`genex motion install${preset ? ` --set ${preset}` : ""}`));
|
|
20540
20819
|
log.plain("");
|
|
20541
|
-
log.step(`Vendoring the motion runtime into ${c.cyan(MOTION_DEST +
|
|
20820
|
+
log.step(`Vendoring the motion runtime into ${c.cyan(MOTION_DEST + path21.sep)}`);
|
|
20542
20821
|
let copied = 0, skipped = 0;
|
|
20543
20822
|
try {
|
|
20544
20823
|
for (const rel of files) {
|
|
20545
|
-
const dest =
|
|
20546
|
-
const exists5 = await
|
|
20824
|
+
const dest = path21.join(root, MOTION_DEST, rel);
|
|
20825
|
+
const exists5 = await fs22.access(dest).then(() => true, () => false);
|
|
20547
20826
|
if (!opts.force && exists5) {
|
|
20548
20827
|
skipped++;
|
|
20549
|
-
log.dim(` skipped ${
|
|
20828
|
+
log.dim(` skipped ${path21.join(MOTION_DEST, rel)} (exists \u2014 use --force to overwrite)`);
|
|
20550
20829
|
continue;
|
|
20551
20830
|
}
|
|
20552
|
-
await
|
|
20553
|
-
await
|
|
20831
|
+
await fs22.mkdir(path21.dirname(dest), { recursive: true });
|
|
20832
|
+
await fs22.copyFile(path21.join(srcDir, rel), dest);
|
|
20554
20833
|
copied++;
|
|
20555
|
-
log.dim(` ${
|
|
20834
|
+
log.dim(` ${path21.join(MOTION_DEST, rel)}`);
|
|
20556
20835
|
}
|
|
20557
20836
|
} catch (err) {
|
|
20558
20837
|
log.error(`Copy failed: ${String(err)}`);
|
|
@@ -20593,7 +20872,7 @@ async function motionConstraints(opts, log) {
|
|
|
20593
20872
|
}
|
|
20594
20873
|
const doc = directionConstraint(dir, speed, duration);
|
|
20595
20874
|
const out = opts.out ?? "constraints.json";
|
|
20596
|
-
await
|
|
20875
|
+
await fs22.writeFile(out, JSON.stringify(doc));
|
|
20597
20876
|
if (opts.json) {
|
|
20598
20877
|
writeJson({ out, dir: dirName, speed, duration, waypoints: doc[0].frame_indices.length });
|
|
20599
20878
|
return;
|
|
@@ -20630,14 +20909,14 @@ async function runMotion(opts) {
|
|
|
20630
20909
|
}
|
|
20631
20910
|
|
|
20632
20911
|
// src/commands/blender.ts
|
|
20633
|
-
import
|
|
20634
|
-
import
|
|
20912
|
+
import fs23 from "fs/promises";
|
|
20913
|
+
import path22 from "path";
|
|
20635
20914
|
var SUBS2 = ["demo", "exec", "snap", "scene", "import", "export", "reset", "mcp", "serve", "seat", "release"];
|
|
20636
20915
|
var DEFAULT_OUT_DIR = "assets/blender";
|
|
20637
20916
|
async function writeB64(dir, name, b64) {
|
|
20638
|
-
await
|
|
20639
|
-
const p =
|
|
20640
|
-
await
|
|
20917
|
+
await fs23.mkdir(dir, { recursive: true });
|
|
20918
|
+
const p = path22.join(dir, name);
|
|
20919
|
+
await fs23.writeFile(p, Buffer.from(b64, "base64"));
|
|
20641
20920
|
return p;
|
|
20642
20921
|
}
|
|
20643
20922
|
function reportScene(log, s) {
|
|
@@ -20663,7 +20942,7 @@ async function runBlender(opts) {
|
|
|
20663
20942
|
return serveLocalBlender({ port, log });
|
|
20664
20943
|
}
|
|
20665
20944
|
if (sub === "mcp") {
|
|
20666
|
-
const { runBlenderMcp } = await import("./blender-mcp-
|
|
20945
|
+
const { runBlenderMcp } = await import("./blender-mcp-PWOUSXK4.js");
|
|
20667
20946
|
return runBlenderMcp();
|
|
20668
20947
|
}
|
|
20669
20948
|
if (sub === "seat") {
|
|
@@ -20721,7 +21000,7 @@ async function runBlender(opts) {
|
|
|
20721
21000
|
log.plain(rest.join("\n"));
|
|
20722
21001
|
return 1;
|
|
20723
21002
|
}
|
|
20724
|
-
const outDir =
|
|
21003
|
+
const outDir = path22.resolve(opts.outDir ?? DEFAULT_OUT_DIR);
|
|
20725
21004
|
const mode = opts.mode;
|
|
20726
21005
|
if (mode !== void 0 && !isRenderMode(mode)) {
|
|
20727
21006
|
log.error(`Unknown --mode ${mode}. Use one of: ${RENDER_MODES.join(", ")}.`);
|
|
@@ -20761,14 +21040,14 @@ async function runBlender(opts) {
|
|
|
20761
21040
|
return 0;
|
|
20762
21041
|
}
|
|
20763
21042
|
case "export": {
|
|
20764
|
-
const target = opts.out ??
|
|
21043
|
+
const target = opts.out ?? path22.join(outDir, "scene.glb");
|
|
20765
21044
|
const r = await blenderCall(base, "/export", { path: "/tmp/genex-export.glb" });
|
|
20766
21045
|
if (!r.glbBase64) {
|
|
20767
21046
|
log.error(`/export answered with no GLB bytes${r.uploaded ? " (it was uploaded, not inlined)" : ""}`);
|
|
20768
21047
|
return 1;
|
|
20769
21048
|
}
|
|
20770
|
-
await
|
|
20771
|
-
await
|
|
21049
|
+
await fs23.mkdir(path22.dirname(target), { recursive: true });
|
|
21050
|
+
await fs23.writeFile(target, Buffer.from(r.glbBase64, "base64"));
|
|
20772
21051
|
log.success(`Exported ${r.bytes ?? 0} bytes`);
|
|
20773
21052
|
log.plain(` ${c.cyan(target)}`);
|
|
20774
21053
|
return 0;
|
|
@@ -20801,12 +21080,12 @@ async function runBlender(opts) {
|
|
|
20801
21080
|
return 1;
|
|
20802
21081
|
}
|
|
20803
21082
|
try {
|
|
20804
|
-
script = await
|
|
21083
|
+
script = await fs23.readFile(opts.input, "utf8");
|
|
20805
21084
|
} catch {
|
|
20806
21085
|
log.error(`Can't read ${opts.input}`);
|
|
20807
21086
|
return 1;
|
|
20808
21087
|
}
|
|
20809
|
-
label =
|
|
21088
|
+
label = path22.basename(opts.input);
|
|
20810
21089
|
}
|
|
20811
21090
|
const r = await blenderCall(base, "/exec", { script, ...mode ? { mode } : {}, sheet: { formats: SHEET_FORMATS } });
|
|
20812
21091
|
if (r.stdout?.trim()) log.plain(r.stdout.trimEnd());
|
|
@@ -20925,9 +21204,9 @@ print(f"castle: {n} objects")
|
|
|
20925
21204
|
`;
|
|
20926
21205
|
|
|
20927
21206
|
// src/commands/asset-new.ts
|
|
20928
|
-
import
|
|
21207
|
+
import fs24 from "fs";
|
|
20929
21208
|
import fsp from "fs/promises";
|
|
20930
|
-
import
|
|
21209
|
+
import path23 from "path";
|
|
20931
21210
|
import { pathToFileURL } from "url";
|
|
20932
21211
|
var EXTRA_FILES = [
|
|
20933
21212
|
"genex-asset.example.json",
|
|
@@ -21021,7 +21300,7 @@ Every milestone ends with \`npm run verify\`, then \`npx genex preview --no-buil
|
|
|
21021
21300
|
}
|
|
21022
21301
|
async function runAssetNew(options) {
|
|
21023
21302
|
const log = createLogger();
|
|
21024
|
-
const cwd = options.dir ?
|
|
21303
|
+
const cwd = options.dir ? path23.resolve(options.dir) : process.cwd();
|
|
21025
21304
|
const slug = options.assetSlug;
|
|
21026
21305
|
if (!slug) {
|
|
21027
21306
|
log.error('Usage: genex asset new <slug> [--title "<Name>"] [--dims WxDxH] [--tri-band LOW-HIGH] [--holder "<your name>"] [--out <dir>]');
|
|
@@ -21031,14 +21310,14 @@ async function runAssetNew(options) {
|
|
|
21031
21310
|
log.error(`The slug must be kebab-case (letters, digits, dashes), got: ${slug}`);
|
|
21032
21311
|
return 1;
|
|
21033
21312
|
}
|
|
21034
|
-
const templateDir =
|
|
21035
|
-
if (!
|
|
21313
|
+
const templateDir = path23.join(getTemplatesDir(), "asset-viewer");
|
|
21314
|
+
if (!fs24.existsSync(templateDir)) {
|
|
21036
21315
|
log.error(`Vendored asset-viewer template not found at ${templateDir} \u2014 reinstall the CLI.`);
|
|
21037
21316
|
return 1;
|
|
21038
21317
|
}
|
|
21039
|
-
const manifestTools = await import(pathToFileURL(
|
|
21318
|
+
const manifestTools = await import(pathToFileURL(path23.join(templateDir, "tools", "emit-manifest.mjs")).href);
|
|
21040
21319
|
const { SHARED_FILES, PARITY_FILENAME, hashSharedFiles, pascalCase } = manifestTools;
|
|
21041
|
-
const lockPath =
|
|
21320
|
+
const lockPath = path23.join(templateDir, "shared-files.sha256.json");
|
|
21042
21321
|
const lock = JSON.parse(await fsp.readFile(lockPath, "utf8"));
|
|
21043
21322
|
const actual = hashSharedFiles(templateDir);
|
|
21044
21323
|
const drifted = SHARED_FILES.filter((rel) => lock.files[rel] !== actual[rel]);
|
|
@@ -21051,8 +21330,8 @@ async function runAssetNew(options) {
|
|
|
21051
21330
|
const triBand = parseBand(options.triBand ?? "500-8000");
|
|
21052
21331
|
const holder = options.holder?.trim() || HOLDER_PLACEHOLDER;
|
|
21053
21332
|
const year = (/* @__PURE__ */ new Date()).getUTCFullYear();
|
|
21054
|
-
const outDir =
|
|
21055
|
-
if (
|
|
21333
|
+
const outDir = path23.resolve(cwd, options.out ?? slug);
|
|
21334
|
+
if (fs24.existsSync(outDir) && fs24.readdirSync(outDir).length > 0 && !options.force) {
|
|
21056
21335
|
log.error(`${outDir} already holds files. Nothing was touched \u2014 pass --force to write into it anyway.`);
|
|
21057
21336
|
return 1;
|
|
21058
21337
|
}
|
|
@@ -21080,24 +21359,24 @@ async function runAssetNew(options) {
|
|
|
21080
21359
|
};
|
|
21081
21360
|
await fsp.mkdir(outDir, { recursive: true });
|
|
21082
21361
|
for (const rel of [...SHARED_FILES, ...EXTRA_FILES]) {
|
|
21083
|
-
const to =
|
|
21084
|
-
await fsp.mkdir(
|
|
21085
|
-
await fsp.copyFile(
|
|
21362
|
+
const to = path23.join(outDir, rel);
|
|
21363
|
+
await fsp.mkdir(path23.dirname(to), { recursive: true });
|
|
21364
|
+
await fsp.copyFile(path23.join(templateDir, rel), to);
|
|
21086
21365
|
}
|
|
21087
|
-
const pkg = fillTemplate(await fsp.readFile(
|
|
21366
|
+
const pkg = fillTemplate(await fsp.readFile(path23.join(templateDir, "package.json"), "utf8"), {
|
|
21088
21367
|
slug,
|
|
21089
21368
|
name,
|
|
21090
21369
|
version
|
|
21091
21370
|
});
|
|
21092
|
-
await fsp.writeFile(
|
|
21093
|
-
await fsp.writeFile(
|
|
21094
|
-
await fsp.writeFile(
|
|
21371
|
+
await fsp.writeFile(path23.join(outDir, "package.json"), pkg, "utf8");
|
|
21372
|
+
await fsp.writeFile(path23.join(outDir, "asset.config.json"), JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
21373
|
+
await fsp.writeFile(path23.join(outDir, ".gitignore"), GITIGNORE, "utf8");
|
|
21095
21374
|
await fsp.writeFile(
|
|
21096
|
-
|
|
21375
|
+
path23.join(outDir, "DESIGN.md"),
|
|
21097
21376
|
designDoc({ name, slug, sizeMeters, triBand, holder }),
|
|
21098
21377
|
"utf8"
|
|
21099
21378
|
);
|
|
21100
|
-
const placeholder = await fsp.readFile(
|
|
21379
|
+
const placeholder = await fsp.readFile(path23.join(templateDir, "src", "asset", "PLACEHOLDER.ts"), "utf8");
|
|
21101
21380
|
const seeded = seedAssetSource(placeholder, {
|
|
21102
21381
|
slug,
|
|
21103
21382
|
name,
|
|
@@ -21108,8 +21387,8 @@ async function runAssetNew(options) {
|
|
|
21108
21387
|
pascalCase
|
|
21109
21388
|
});
|
|
21110
21389
|
const assetFilename = `${slug.replace(/-([a-z0-9])/g, (_, ch) => ch.toUpperCase())}.ts`;
|
|
21111
|
-
await fsp.mkdir(
|
|
21112
|
-
await fsp.writeFile(
|
|
21390
|
+
await fsp.mkdir(path23.join(outDir, "src", "asset"), { recursive: true });
|
|
21391
|
+
await fsp.writeFile(path23.join(outDir, "src", "asset", assetFilename), seeded, "utf8");
|
|
21113
21392
|
const copied = hashSharedFiles(outDir);
|
|
21114
21393
|
const mismatched = SHARED_FILES.filter((rel) => copied[rel] !== lock.files[rel]);
|
|
21115
21394
|
if (mismatched.length) {
|
|
@@ -21117,7 +21396,7 @@ async function runAssetNew(options) {
|
|
|
21117
21396
|
return 1;
|
|
21118
21397
|
}
|
|
21119
21398
|
await fsp.writeFile(
|
|
21120
|
-
|
|
21399
|
+
path23.join(outDir, PARITY_FILENAME),
|
|
21121
21400
|
JSON.stringify(
|
|
21122
21401
|
{
|
|
21123
21402
|
note: "Recorded by `genex asset new`. Gate G13 recomputes these; do not hand-edit the viewer.",
|
|
@@ -21161,11 +21440,11 @@ async function runAssetNew(options) {
|
|
|
21161
21440
|
}
|
|
21162
21441
|
|
|
21163
21442
|
// src/commands/tools.ts
|
|
21164
|
-
import
|
|
21443
|
+
import path26 from "path";
|
|
21165
21444
|
|
|
21166
21445
|
// src/lib/local-install.ts
|
|
21167
|
-
import
|
|
21168
|
-
import
|
|
21446
|
+
import fs25 from "fs/promises";
|
|
21447
|
+
import path24 from "path";
|
|
21169
21448
|
import { spawn as spawn4 } from "child_process";
|
|
21170
21449
|
var CLI_PACKAGE = "@genex-ai/cli-demo";
|
|
21171
21450
|
var FULL_NAME_FALLBACK = `npx ${CLI_PACKAGE}@${CLI_CHANNEL}`;
|
|
@@ -21178,17 +21457,17 @@ var LOCKFILES = [
|
|
|
21178
21457
|
];
|
|
21179
21458
|
async function exists4(p) {
|
|
21180
21459
|
try {
|
|
21181
|
-
await
|
|
21460
|
+
await fs25.access(p);
|
|
21182
21461
|
return true;
|
|
21183
21462
|
} catch {
|
|
21184
21463
|
return false;
|
|
21185
21464
|
}
|
|
21186
21465
|
}
|
|
21187
21466
|
async function detectPackageManager(cwd) {
|
|
21188
|
-
let dir =
|
|
21467
|
+
let dir = path24.resolve(cwd);
|
|
21189
21468
|
for (; ; ) {
|
|
21190
21469
|
try {
|
|
21191
|
-
const raw = await
|
|
21470
|
+
const raw = await fs25.readFile(path24.join(dir, "package.json"), "utf8");
|
|
21192
21471
|
const pm = JSON.parse(raw).packageManager;
|
|
21193
21472
|
if (typeof pm === "string") {
|
|
21194
21473
|
const name = pm.split("@")[0];
|
|
@@ -21197,18 +21476,18 @@ async function detectPackageManager(cwd) {
|
|
|
21197
21476
|
} catch {
|
|
21198
21477
|
}
|
|
21199
21478
|
for (const [file, pm] of LOCKFILES) {
|
|
21200
|
-
if (await exists4(
|
|
21479
|
+
if (await exists4(path24.join(dir, file))) return pm;
|
|
21201
21480
|
}
|
|
21202
|
-
const parent =
|
|
21481
|
+
const parent = path24.dirname(dir);
|
|
21203
21482
|
if (parent === dir) return "npm";
|
|
21204
21483
|
dir = parent;
|
|
21205
21484
|
}
|
|
21206
21485
|
}
|
|
21207
21486
|
async function findLocalCli(cwd) {
|
|
21208
|
-
let dir =
|
|
21487
|
+
let dir = path24.resolve(cwd);
|
|
21209
21488
|
for (; ; ) {
|
|
21210
|
-
if (await exists4(
|
|
21211
|
-
const parent =
|
|
21489
|
+
if (await exists4(path24.join(dir, "node_modules", CLI_PACKAGE, "package.json"))) return dir;
|
|
21490
|
+
const parent = path24.dirname(dir);
|
|
21212
21491
|
if (parent === dir) return null;
|
|
21213
21492
|
dir = parent;
|
|
21214
21493
|
}
|
|
@@ -21226,7 +21505,7 @@ function installArgs(pm, spec) {
|
|
|
21226
21505
|
}
|
|
21227
21506
|
}
|
|
21228
21507
|
function manifestName(cwd) {
|
|
21229
|
-
const slug =
|
|
21508
|
+
const slug = path24.basename(path24.resolve(cwd)).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 100);
|
|
21230
21509
|
return slug || "genex-tools-workspace";
|
|
21231
21510
|
}
|
|
21232
21511
|
function isSourceRun(moduleUrl = import.meta.url) {
|
|
@@ -21284,10 +21563,10 @@ async function ensureLocalCli(log, cwd = process.cwd(), opts = {}) {
|
|
|
21284
21563
|
return;
|
|
21285
21564
|
}
|
|
21286
21565
|
const pm = await detectPackageManager(cwd);
|
|
21287
|
-
const hadManifest = await exists4(
|
|
21566
|
+
const hadManifest = await exists4(path24.join(cwd, "package.json"));
|
|
21288
21567
|
if (!hadManifest) {
|
|
21289
|
-
await
|
|
21290
|
-
|
|
21568
|
+
await fs25.writeFile(
|
|
21569
|
+
path24.join(cwd, "package.json"),
|
|
21291
21570
|
JSON.stringify({ name: manifestName(cwd), private: true }, null, 2) + "\n"
|
|
21292
21571
|
);
|
|
21293
21572
|
await ensureIgnored(cwd, "node_modules/");
|
|
@@ -21307,20 +21586,20 @@ async function ensureLocalCli(log, cwd = process.cwd(), opts = {}) {
|
|
|
21307
21586
|
}
|
|
21308
21587
|
}
|
|
21309
21588
|
async function ensureIgnored(dir, entry) {
|
|
21310
|
-
const file =
|
|
21589
|
+
const file = path24.join(dir, ".gitignore");
|
|
21311
21590
|
let content = "";
|
|
21312
21591
|
try {
|
|
21313
|
-
content = await
|
|
21592
|
+
content = await fs25.readFile(file, "utf8");
|
|
21314
21593
|
} catch {
|
|
21315
21594
|
}
|
|
21316
21595
|
if (content.split("\n").some((l) => l.trim() === entry)) return;
|
|
21317
21596
|
let next = content;
|
|
21318
21597
|
if (next.length > 0 && !next.endsWith("\n")) next += "\n";
|
|
21319
|
-
await
|
|
21598
|
+
await fs25.writeFile(file, next + entry + "\n");
|
|
21320
21599
|
}
|
|
21321
21600
|
|
|
21322
21601
|
// src/commands/doctor.ts
|
|
21323
|
-
import
|
|
21602
|
+
import path25 from "path";
|
|
21324
21603
|
var LANE_ORDER = [
|
|
21325
21604
|
"model",
|
|
21326
21605
|
"image",
|
|
@@ -21601,7 +21880,7 @@ async function fetchLegalStatus(apiUrl, token) {
|
|
|
21601
21880
|
}
|
|
21602
21881
|
async function firstSkillsMarker() {
|
|
21603
21882
|
for (const target of resolveAgentTargets()) {
|
|
21604
|
-
const marker = await readSkillsMarker(
|
|
21883
|
+
const marker = await readSkillsMarker(path25.join(target.baseDir, "skills"));
|
|
21605
21884
|
if (marker) return marker;
|
|
21606
21885
|
}
|
|
21607
21886
|
return null;
|
|
@@ -21640,8 +21919,8 @@ async function runTools(opts) {
|
|
|
21640
21919
|
let totalNew = 0;
|
|
21641
21920
|
let totalUpdated = 0;
|
|
21642
21921
|
for (const t of targets) {
|
|
21643
|
-
const dest =
|
|
21644
|
-
const { copied, updated } = await copyTemplates(
|
|
21922
|
+
const dest = path26.join(t.baseDir, "skills");
|
|
21923
|
+
const { copied, updated } = await copyTemplates(path26.join(templatesDir, "skills"), dest, {
|
|
21645
21924
|
filter: (rel) => skillFamilyFilter("tools")(`skills/${rel}`)
|
|
21646
21925
|
});
|
|
21647
21926
|
await pruneRemovedSkills(dest, log);
|
|
@@ -21702,7 +21981,7 @@ async function runTools(opts) {
|
|
|
21702
21981
|
// src/lib/costs.ts
|
|
21703
21982
|
var TYPICAL_CREDITS = {
|
|
21704
21983
|
model: 35,
|
|
21705
|
-
// 46 from a reference image
|
|
21984
|
+
// 46 from a reference image; --texture standard 23, --no-texture 12, --geometry detailed 58, --quad 41, --low-poly 46, --parts 58 quote off the estimate
|
|
21706
21985
|
image: 4,
|
|
21707
21986
|
// gpt-image-2 tiers (--transparent, --quality high, --edit, 4K) quote higher
|
|
21708
21987
|
texture: 5,
|
|
@@ -21717,17 +21996,22 @@ var TYPICAL_CREDITS = {
|
|
|
21717
21996
|
model_rig: 29,
|
|
21718
21997
|
model_animation: 12,
|
|
21719
21998
|
// PER CLIP — `--preset walk,run` quotes ceil(2 × 10¢ × 1.15)
|
|
21720
|
-
character:
|
|
21999
|
+
character: 64,
|
|
22000
|
+
// one-shot on Meshy 7 Ultra at 4k with the controller pack; `genex creature` (no pack) 46, --no-ultra 58, --texture 8k 69
|
|
21721
22001
|
character_concept: 32,
|
|
21722
22002
|
character_preview: 41,
|
|
22003
|
+
// Meshy 7 Ultra at 4k; --no-ultra 35, --texture 8k 46
|
|
21723
22004
|
character_finalize: 29,
|
|
21724
22005
|
character_animation: 6,
|
|
21725
22006
|
character_motion: 46,
|
|
21726
22007
|
// one text-route clip; other routes are priced off their own cost
|
|
21727
|
-
character_rerig: 12
|
|
22008
|
+
character_rerig: 12,
|
|
22009
|
+
character_import: 18
|
|
22010
|
+
// Uthana auto-rig of your own GLB; the upload itself is free
|
|
21728
22011
|
};
|
|
21729
22012
|
|
|
21730
22013
|
// src/index.ts
|
|
22014
|
+
var TEXTURE_FLAG_VALUES = ["standard", "detailed", "none", "2k", "4k", "8k"];
|
|
21731
22015
|
var MODEL_SUB_SET = new Set(MODEL_SUBCOMMANDS);
|
|
21732
22016
|
var GEN_KINDS = /* @__PURE__ */ new Set(["model", "skybox", "sfx", "music", "voice", "texture", "image", "video"]);
|
|
21733
22017
|
var HELP = `${c.bold("genex")} \u2014 set up your project's agent workspace, authorize, and publish 3D games.
|
|
@@ -21804,7 +22088,11 @@ ${c.bold("Usage")}
|
|
|
21804
22088
|
default, and --no-resellable turns it back off.
|
|
21805
22089
|
genex model "<prompt>" [options] Generate a 3D model (GLB); prints a public asset URL.
|
|
21806
22090
|
--image <path|url> builds it FROM a reference
|
|
21807
|
-
image instead (prompt optional).
|
|
22091
|
+
image instead (prompt optional). Quality knobs:
|
|
22092
|
+
--texture standard|detailed|none, --geometry
|
|
22093
|
+
detailed, --quad, --low-poly, --parts,
|
|
22094
|
+
--face-limit <n>, --auto-size (each is priced
|
|
22095
|
+
in the quote).
|
|
21808
22096
|
genex model segment <id> Split a generated model into NAMED parts \u2014
|
|
21809
22097
|
one GLB, parts addressable by name (doors,
|
|
21810
22098
|
magazines, turrets, destructibles).
|
|
@@ -21814,6 +22102,10 @@ ${c.bold("Usage")}
|
|
|
21814
22102
|
avian|serpentine|aquatic). Auto-detects the
|
|
21815
22103
|
plan; --type picks it explicitly. Unriggable
|
|
21816
22104
|
meshes are refused and auto-refunded.
|
|
22105
|
+
genex model import <file.glb> Bring in a mesh you already have (a Blender
|
|
22106
|
+
export, a bought asset) as a model of yours \u2014
|
|
22107
|
+
free, \u226464 MB \u2014 so rig/animate/segment, blender
|
|
22108
|
+
import and "Use in game" all work on it.
|
|
21817
22109
|
genex model animate <rig-id> --preset walk[,run,\u2026]
|
|
21818
22110
|
Retarget ready-made motion clips onto a
|
|
21819
22111
|
'model rig' result (billed per clip).
|
|
@@ -21853,6 +22145,12 @@ ${c.bold("Usage")}
|
|
|
21853
22145
|
can take new animations. With no id it adopts the
|
|
21854
22146
|
body this game already wears. Free \u2014 it copies,
|
|
21855
22147
|
it does not generate.
|
|
22148
|
+
genex character import <file.glb> Your own humanoid mesh, auto-rigged by Uthana
|
|
22149
|
+
(finger joints included; --no-fingers skips
|
|
22150
|
+
them; --height <m>). \u226430 MB, biped, T- or
|
|
22151
|
+
A-pose. Then 'character animate <id>
|
|
22152
|
+
--locomotion' \u2014 the Meshy catalog and pack do
|
|
22153
|
+
not apply to a Uthana rig.
|
|
21856
22154
|
genex creature "<desc>" One-shot rigged enemy/creature (biped-shaped
|
|
21857
22155
|
bodies only \u2014 Meshy rig limit): model \u2192 rig \u2192
|
|
21858
22156
|
bind library clips via --animation. No approval
|
|
@@ -21914,6 +22212,14 @@ ${c.bold("Options for the generators (`model` `sfx` `music` `voice` `texture` `i
|
|
|
21914
22212
|
--image <path|url> (model) build the model FROM this reference image \u2014 a
|
|
21915
22213
|
local file (\u22644 MB, inlined) or a previous generation's
|
|
21916
22214
|
asset URL. The prompt becomes optional.
|
|
22215
|
+
--texture <tier> (model) standard | detailed (default) | none \u2014 detailed is
|
|
22216
|
+
+10 credits over standard, none is geometry only.
|
|
22217
|
+
--geometry <tier> (model) standard (default) | detailed (+20 credits, hero pieces).
|
|
22218
|
+
--quad (model) quad-dominant mesh (+5; face limit \u2264150000).
|
|
22219
|
+
--low-poly (model) smart low-poly topology (+10) \u2014 game-ready meshes.
|
|
22220
|
+
--parts (model) separated, named parts at generation (+20).
|
|
22221
|
+
--face-limit <n> (model) cap on the raw mesh, 1000-2000000 (default 150000).
|
|
22222
|
+
--auto-size (model) scale to real-world metres by AI estimate.
|
|
21917
22223
|
--granularity <g> (model segment) part granularity: simple | balanced |
|
|
21918
22224
|
detailed (default balanced).
|
|
21919
22225
|
--type <plan> (model rig) body plan: biped | quadruped | hexapod |
|
|
@@ -22060,13 +22366,23 @@ ${c.bold("Options for `character` / `animations search`")}
|
|
|
22060
22366
|
ranked action IDs instead of choosing silently.
|
|
22061
22367
|
--candidate <1|2|3> (character preview) selected concept candidate.
|
|
22062
22368
|
--user-approved Confirm the user explicitly approved the candidate/preview.
|
|
22063
|
-
--approve-remesh
|
|
22064
|
-
(character finalize) approve the separate
|
|
22065
|
-
|
|
22369
|
+
--approve-remesh <faces>
|
|
22370
|
+
(character finalize) approve the separate rigging copy at this
|
|
22371
|
+
face budget, 10000-100000 (10000 for a crowd body, 30000+ for a
|
|
22372
|
+
hero). The number IS the approval.
|
|
22373
|
+
--direct-text One-shot path: skip concept/preview approval and generate
|
|
22066
22374
|
directly from text (defaults to a 10,000-face target).
|
|
22067
22375
|
--no-controller-pack (character --direct-text only) skip the validated locomotion pack.
|
|
22068
22376
|
--height <meters> (character) target character height, 0.5-3 meters.
|
|
22069
|
-
--polycount <count> (character --direct-text
|
|
22377
|
+
--polycount <count> (character --direct-text / creature) target 10000-100000 polygons.
|
|
22378
|
+
--texture <res> (character/creature/preview) texture size 2k | 4k (default) | 8k
|
|
22379
|
+
(+5 credits). Meshy 7 on every lane.
|
|
22380
|
+
--no-ultra (character/creature/preview) skip Meshy Ultra (\u22125 credits, less
|
|
22381
|
+
surface detail).
|
|
22382
|
+
--pose <a-pose|t-pose>
|
|
22383
|
+
(character --direct-text / creature) preferred rest pose; the
|
|
22384
|
+
other stays the QA fallback.
|
|
22385
|
+
--no-fingers (character import) skip finger joints in the Uthana rig.
|
|
22070
22386
|
--no-wait (character/preview/finalize/animate) enqueue and return a generation id.
|
|
22071
22387
|
--json Emit one machine-readable workflow result object.
|
|
22072
22388
|
--action <id|query> (character animate) action to add; repeatable.
|
|
@@ -22324,6 +22640,13 @@ function parseArgs(argv) {
|
|
|
22324
22640
|
"--image",
|
|
22325
22641
|
"--granularity",
|
|
22326
22642
|
"--preset",
|
|
22643
|
+
// Quality knobs (2026-09-06): `--texture` is shared by `model`
|
|
22644
|
+
// (standard|detailed|none) and the character lanes (2k|4k|8k); each command
|
|
22645
|
+
// re-validates its own vocabulary client-side.
|
|
22646
|
+
"--texture",
|
|
22647
|
+
"--geometry",
|
|
22648
|
+
"--face-limit",
|
|
22649
|
+
"--pose",
|
|
22327
22650
|
// `genex blender` (M1 spike): which shading the contact sheet uses.
|
|
22328
22651
|
"--mode",
|
|
22329
22652
|
// `genex ui` string flags (the numeric ones come from UI_NUMBER_FLAGS).
|
|
@@ -22407,6 +22730,24 @@ function parseArgs(argv) {
|
|
|
22407
22730
|
case "--no-controller-pack":
|
|
22408
22731
|
parsed.options.controllerPack = false;
|
|
22409
22732
|
break;
|
|
22733
|
+
case "--no-fingers":
|
|
22734
|
+
parsed.options.fingers = false;
|
|
22735
|
+
break;
|
|
22736
|
+
case "--no-ultra":
|
|
22737
|
+
parsed.options.ultra = false;
|
|
22738
|
+
break;
|
|
22739
|
+
case "--quad":
|
|
22740
|
+
parsed.options.quad = true;
|
|
22741
|
+
break;
|
|
22742
|
+
case "--low-poly":
|
|
22743
|
+
parsed.options.lowPoly = true;
|
|
22744
|
+
break;
|
|
22745
|
+
case "--parts":
|
|
22746
|
+
parsed.options.parts = true;
|
|
22747
|
+
break;
|
|
22748
|
+
case "--auto-size":
|
|
22749
|
+
parsed.options.autoSize = true;
|
|
22750
|
+
break;
|
|
22410
22751
|
case "--user-approved":
|
|
22411
22752
|
parsed.options.userApproved = true;
|
|
22412
22753
|
break;
|
|
@@ -22527,7 +22868,9 @@ function parseArgs(argv) {
|
|
|
22527
22868
|
parsed.options.conceptId = arg;
|
|
22528
22869
|
} else if (parsed.options.name === "finalize" && !parsed.options.previewId) {
|
|
22529
22870
|
parsed.options.previewId = arg;
|
|
22530
|
-
} else if (
|
|
22871
|
+
} else if (parsed.options.name === "import" && !parsed.options.importPath) {
|
|
22872
|
+
parsed.options.importPath = arg;
|
|
22873
|
+
} else if (!["animate", "motions", "preview", "finalize", "adopt", "import"].includes(parsed.options.name ?? "")) {
|
|
22531
22874
|
parsed.options.name = `${parsed.options.name} ${arg}`;
|
|
22532
22875
|
} else {
|
|
22533
22876
|
parsed.error = `Unexpected argument: ${arg}`;
|
|
@@ -22678,8 +23021,8 @@ function applyValueFlag(options, flag, value) {
|
|
|
22678
23021
|
}
|
|
22679
23022
|
case "--approve-remesh": {
|
|
22680
23023
|
const n = Number(value);
|
|
22681
|
-
if (!Number.isInteger(n) || n
|
|
22682
|
-
throw new Error(`Invalid --approve-remesh value: ${value} (expected
|
|
23024
|
+
if (!Number.isInteger(n) || n < REMESH_TARGET_MIN || n > REMESH_TARGET_MAX) {
|
|
23025
|
+
throw new Error(`Invalid --approve-remesh value: ${value} (expected ${REMESH_TARGET_MIN}-${REMESH_TARGET_MAX} faces)`);
|
|
22683
23026
|
}
|
|
22684
23027
|
options.approveRemesh = n;
|
|
22685
23028
|
break;
|
|
@@ -22847,6 +23190,32 @@ function applyValueFlag(options, flag, value) {
|
|
|
22847
23190
|
case "--image":
|
|
22848
23191
|
options.imageUrl = value;
|
|
22849
23192
|
break;
|
|
23193
|
+
case "--texture":
|
|
23194
|
+
if (!TEXTURE_FLAG_VALUES.includes(value)) {
|
|
23195
|
+
throw new Error(`Invalid --texture value: ${value} (model: standard|detailed|none; character: 2k|4k|8k)`);
|
|
23196
|
+
}
|
|
23197
|
+
options.texture = value;
|
|
23198
|
+
break;
|
|
23199
|
+
case "--geometry":
|
|
23200
|
+
if (value !== "standard" && value !== "detailed") {
|
|
23201
|
+
throw new Error(`Invalid --geometry value: ${value} (expected standard|detailed)`);
|
|
23202
|
+
}
|
|
23203
|
+
options.geometry = value;
|
|
23204
|
+
break;
|
|
23205
|
+
case "--face-limit": {
|
|
23206
|
+
const n = Number(value);
|
|
23207
|
+
if (!Number.isInteger(n) || n < 1e3 || n > 2e6) {
|
|
23208
|
+
throw new Error(`Invalid --face-limit value: ${value} (expected 1000-2000000)`);
|
|
23209
|
+
}
|
|
23210
|
+
options.faceLimit = n;
|
|
23211
|
+
break;
|
|
23212
|
+
}
|
|
23213
|
+
case "--pose":
|
|
23214
|
+
if (value !== "a-pose" && value !== "t-pose") {
|
|
23215
|
+
throw new Error(`Invalid --pose value: ${value} (expected a-pose|t-pose)`);
|
|
23216
|
+
}
|
|
23217
|
+
options.pose = value;
|
|
23218
|
+
break;
|
|
22850
23219
|
case "--granularity":
|
|
22851
23220
|
options.granularity = value;
|
|
22852
23221
|
break;
|
|
@@ -22966,6 +23335,7 @@ async function main() {
|
|
|
22966
23335
|
if (parsed.command === "model" && MODEL_SUB_SET.has(parsed.options.name ?? "")) {
|
|
22967
23336
|
if (parsed.options.name === "segment") await runModelSegment(parsed.options);
|
|
22968
23337
|
else if (parsed.options.name === "rig") await runModelRig(parsed.options);
|
|
23338
|
+
else if (parsed.options.name === "import") await runModelImport(parsed.options);
|
|
22969
23339
|
else await runModelAnimate(parsed.options);
|
|
22970
23340
|
return;
|
|
22971
23341
|
}
|
|
@@ -23031,6 +23401,8 @@ async function main() {
|
|
|
23031
23401
|
await runCharacterFinalize(parsed.options);
|
|
23032
23402
|
} else if (parsed.options.name === "adopt") {
|
|
23033
23403
|
await runCharacterAdopt(parsed.options);
|
|
23404
|
+
} else if (parsed.options.name === "import") {
|
|
23405
|
+
await runCharacterImport(parsed.options);
|
|
23034
23406
|
} else {
|
|
23035
23407
|
await runCharacter({ ...parsed.options, prompt: parsed.options.name });
|
|
23036
23408
|
}
|