creative-pipeline-mcp 0.2.14-alpha.0
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/LICENSE +18 -0
- package/NOTICE +9 -0
- package/README.md +205 -0
- package/SECURITY.md +19 -0
- package/docs/API_TOOLS.md +74 -0
- package/docs/ARCHITECTURE.md +28 -0
- package/docs/ARTIFACT_SCHEMA.md +43 -0
- package/docs/BLENDER_BRIDGE_STATUS_SCHEMA.md +42 -0
- package/docs/CEP_STATUS_SCHEMA.md +35 -0
- package/docs/COMPATIBILITY_MATRIX.md +36 -0
- package/docs/GPL_ADAPTERS.md +19 -0
- package/docs/INSTALL_BLENDER.md +39 -0
- package/docs/INSTALL_DASHBOARD.md +47 -0
- package/docs/INSTALL_PREMIERE.md +62 -0
- package/docs/LICENSING.md +19 -0
- package/docs/PREMIERE_E2E_TEST.md +55 -0
- package/docs/RELEASE_PROCESS.md +74 -0
- package/docs/ROADMAP.md +53 -0
- package/docs/SAFETY.md +21 -0
- package/docs/SECURITY.md +14 -0
- package/docs/TROUBLESHOOTING.md +46 -0
- package/docs/examples/adapter_check_report.sample.json +60 -0
- package/docs/examples/delivery_qc_report.sample.json +30 -0
- package/examples/blender-bridge-queue.mjs +42 -0
- package/examples/blender-e2e.mjs +53 -0
- package/examples/brief.txt +2 -0
- package/examples/minimal.gltf +23 -0
- package/examples/premiere-project-delivery.mjs +72 -0
- package/examples/premiere-qc-e2e.mjs +77 -0
- package/examples/sample-tool-call.json +2 -0
- package/package.json +77 -0
- package/packages/blender-gpl-adapters/LICENSE +6 -0
- package/packages/blender-gpl-adapters/dist/index.d.ts +10 -0
- package/packages/blender-gpl-adapters/dist/index.js +28 -0
- package/packages/blender-gpl-adapters/dist/index.js.map +1 -0
- package/packages/blender-gpl-adapters/package.json +15 -0
- package/packages/blender-gpl-adapters/src/index.ts +38 -0
- package/packages/blender-pro-mcp/dist/adapters/blenderBridge.d.ts +34 -0
- package/packages/blender-pro-mcp/dist/adapters/blenderBridge.js +79 -0
- package/packages/blender-pro-mcp/dist/adapters/blenderBridge.js.map +1 -0
- package/packages/blender-pro-mcp/dist/adapters/cli.d.ts +11 -0
- package/packages/blender-pro-mcp/dist/adapters/cli.js +121 -0
- package/packages/blender-pro-mcp/dist/adapters/cli.js.map +1 -0
- package/packages/blender-pro-mcp/dist/adapters/gltf.d.ts +21 -0
- package/packages/blender-pro-mcp/dist/adapters/gltf.js +99 -0
- package/packages/blender-pro-mcp/dist/adapters/gltf.js.map +1 -0
- package/packages/blender-pro-mcp/dist/adapters/preview.d.ts +1 -0
- package/packages/blender-pro-mcp/dist/adapters/preview.js +11 -0
- package/packages/blender-pro-mcp/dist/adapters/preview.js.map +1 -0
- package/packages/blender-pro-mcp/dist/index.d.ts +3 -0
- package/packages/blender-pro-mcp/dist/index.js +4 -0
- package/packages/blender-pro-mcp/dist/index.js.map +1 -0
- package/packages/blender-pro-mcp/dist/server.d.ts +1 -0
- package/packages/blender-pro-mcp/dist/server.js +4 -0
- package/packages/blender-pro-mcp/dist/server.js.map +1 -0
- package/packages/blender-pro-mcp/dist/tools/assetTools.d.ts +2 -0
- package/packages/blender-pro-mcp/dist/tools/assetTools.js +646 -0
- package/packages/blender-pro-mcp/dist/tools/assetTools.js.map +1 -0
- package/packages/blender-pro-mcp/dist/tools/shared.d.ts +3 -0
- package/packages/blender-pro-mcp/dist/tools/shared.js +94 -0
- package/packages/blender-pro-mcp/dist/tools/shared.js.map +1 -0
- package/packages/blender-pro-mcp/package.json +18 -0
- package/packages/blender-pro-mcp/src/adapters/blenderBridge.ts +106 -0
- package/packages/blender-pro-mcp/src/adapters/cli.ts +130 -0
- package/packages/blender-pro-mcp/src/adapters/gltf.ts +132 -0
- package/packages/blender-pro-mcp/src/adapters/preview.ts +11 -0
- package/packages/blender-pro-mcp/src/index.ts +3 -0
- package/packages/blender-pro-mcp/src/server.ts +4 -0
- package/packages/blender-pro-mcp/src/tools/assetTools.ts +663 -0
- package/packages/blender-pro-mcp/src/tools/shared.ts +99 -0
- package/packages/core/dist/approvalPolicy.d.ts +13 -0
- package/packages/core/dist/approvalPolicy.js +48 -0
- package/packages/core/dist/approvalPolicy.js.map +1 -0
- package/packages/core/dist/artifactStore.d.ts +14 -0
- package/packages/core/dist/artifactStore.js +77 -0
- package/packages/core/dist/artifactStore.js.map +1 -0
- package/packages/core/dist/coreTools.d.ts +2 -0
- package/packages/core/dist/coreTools.js +34 -0
- package/packages/core/dist/coreTools.js.map +1 -0
- package/packages/core/dist/index.d.ts +11 -0
- package/packages/core/dist/index.js +12 -0
- package/packages/core/dist/index.js.map +1 -0
- package/packages/core/dist/jobQueue.d.ts +11 -0
- package/packages/core/dist/jobQueue.js +14 -0
- package/packages/core/dist/jobQueue.js.map +1 -0
- package/packages/core/dist/licenseManifest.d.ts +7 -0
- package/packages/core/dist/licenseManifest.js +42 -0
- package/packages/core/dist/licenseManifest.js.map +1 -0
- package/packages/core/dist/mcpServer.d.ts +19 -0
- package/packages/core/dist/mcpServer.js +120 -0
- package/packages/core/dist/mcpServer.js.map +1 -0
- package/packages/core/dist/qcReport.d.ts +21 -0
- package/packages/core/dist/qcReport.js +25 -0
- package/packages/core/dist/qcReport.js.map +1 -0
- package/packages/core/dist/router.d.ts +7 -0
- package/packages/core/dist/router.js +55 -0
- package/packages/core/dist/router.js.map +1 -0
- package/packages/core/dist/schemaValidator.d.ts +8 -0
- package/packages/core/dist/schemaValidator.js +21 -0
- package/packages/core/dist/schemaValidator.js.map +1 -0
- package/packages/core/dist/server.d.ts +1 -0
- package/packages/core/dist/server.js +3 -0
- package/packages/core/dist/server.js.map +1 -0
- package/packages/core/dist/toolRegistry.d.ts +8 -0
- package/packages/core/dist/toolRegistry.js +25 -0
- package/packages/core/dist/toolRegistry.js.map +1 -0
- package/packages/core/dist/types.d.ts +58 -0
- package/packages/core/dist/types.js +2 -0
- package/packages/core/dist/types.js.map +1 -0
- package/packages/core/package.json +18 -0
- package/packages/core/src/approvalPolicy.ts +51 -0
- package/packages/core/src/artifactStore.ts +93 -0
- package/packages/core/src/coreTools.ts +36 -0
- package/packages/core/src/index.ts +11 -0
- package/packages/core/src/jobQueue.ts +22 -0
- package/packages/core/src/licenseManifest.ts +47 -0
- package/packages/core/src/mcpServer.ts +131 -0
- package/packages/core/src/qcReport.ts +53 -0
- package/packages/core/src/router.ts +61 -0
- package/packages/core/src/schemaValidator.ts +26 -0
- package/packages/core/src/server.ts +3 -0
- package/packages/core/src/toolRegistry.ts +31 -0
- package/packages/core/src/types.ts +78 -0
- package/packages/dashboard/dist/server.d.ts +1 -0
- package/packages/dashboard/dist/server.js +486 -0
- package/packages/dashboard/dist/server.js.map +1 -0
- package/packages/dashboard/package.json +18 -0
- package/packages/dashboard/src/server.ts +539 -0
- package/packages/director-agent/dist/index.d.ts +1 -0
- package/packages/director-agent/dist/index.js +2 -0
- package/packages/director-agent/dist/index.js.map +1 -0
- package/packages/director-agent/dist/server.d.ts +1 -0
- package/packages/director-agent/dist/server.js +4 -0
- package/packages/director-agent/dist/server.js.map +1 -0
- package/packages/director-agent/dist/tools.d.ts +2 -0
- package/packages/director-agent/dist/tools.js +115 -0
- package/packages/director-agent/dist/tools.js.map +1 -0
- package/packages/director-agent/package.json +18 -0
- package/packages/director-agent/src/index.ts +2 -0
- package/packages/director-agent/src/server.ts +4 -0
- package/packages/director-agent/src/tools.ts +116 -0
- package/packages/premiere-cep-panel/CSXS/manifest.xml +43 -0
- package/packages/premiere-cep-panel/README.md +21 -0
- package/packages/premiere-cep-panel/index.html +26 -0
- package/packages/premiere-cep-panel/js/main.js +107 -0
- package/packages/premiere-cep-panel/jsx/host.jsx +208 -0
- package/packages/premiere-cep-panel/package.json +8 -0
- package/packages/premiere-pro-mcp/dist/adapters/ffmpegQc.d.ts +21 -0
- package/packages/premiere-pro-mcp/dist/adapters/ffmpegQc.js +78 -0
- package/packages/premiere-pro-mcp/dist/adapters/ffmpegQc.js.map +1 -0
- package/packages/premiere-pro-mcp/dist/adapters/ffprobe.d.ts +25 -0
- package/packages/premiere-pro-mcp/dist/adapters/ffprobe.js +73 -0
- package/packages/premiere-pro-mcp/dist/adapters/ffprobe.js.map +1 -0
- package/packages/premiere-pro-mcp/dist/adapters/optionalTools.d.ts +11 -0
- package/packages/premiere-pro-mcp/dist/adapters/optionalTools.js +82 -0
- package/packages/premiere-pro-mcp/dist/adapters/optionalTools.js.map +1 -0
- package/packages/premiere-pro-mcp/dist/adapters/premiereCep.d.ts +34 -0
- package/packages/premiere-pro-mcp/dist/adapters/premiereCep.js +79 -0
- package/packages/premiere-pro-mcp/dist/adapters/premiereCep.js.map +1 -0
- package/packages/premiere-pro-mcp/dist/adapters/srt.d.ts +8 -0
- package/packages/premiere-pro-mcp/dist/adapters/srt.js +37 -0
- package/packages/premiere-pro-mcp/dist/adapters/srt.js.map +1 -0
- package/packages/premiere-pro-mcp/dist/index.d.ts +2 -0
- package/packages/premiere-pro-mcp/dist/index.js +3 -0
- package/packages/premiere-pro-mcp/dist/index.js.map +1 -0
- package/packages/premiere-pro-mcp/dist/server.d.ts +1 -0
- package/packages/premiere-pro-mcp/dist/server.js +4 -0
- package/packages/premiere-pro-mcp/dist/server.js.map +1 -0
- package/packages/premiere-pro-mcp/dist/tools/mediaTools.d.ts +2 -0
- package/packages/premiere-pro-mcp/dist/tools/mediaTools.js +935 -0
- package/packages/premiere-pro-mcp/dist/tools/mediaTools.js.map +1 -0
- package/packages/premiere-pro-mcp/dist/tools/shared.d.ts +3 -0
- package/packages/premiere-pro-mcp/dist/tools/shared.js +136 -0
- package/packages/premiere-pro-mcp/dist/tools/shared.js.map +1 -0
- package/packages/premiere-pro-mcp/package.json +18 -0
- package/packages/premiere-pro-mcp/src/adapters/ffmpegQc.ts +106 -0
- package/packages/premiere-pro-mcp/src/adapters/ffprobe.ts +105 -0
- package/packages/premiere-pro-mcp/src/adapters/optionalTools.ts +94 -0
- package/packages/premiere-pro-mcp/src/adapters/premiereCep.ts +106 -0
- package/packages/premiere-pro-mcp/src/adapters/srt.ts +46 -0
- package/packages/premiere-pro-mcp/src/index.ts +3 -0
- package/packages/premiere-pro-mcp/src/server.ts +4 -0
- package/packages/premiere-pro-mcp/src/tools/mediaTools.ts +961 -0
- package/packages/premiere-pro-mcp/src/tools/shared.ts +147 -0
- package/packages/premiere-windows-adapter/dist/index.d.ts +12 -0
- package/packages/premiere-windows-adapter/dist/index.js +17 -0
- package/packages/premiere-windows-adapter/dist/index.js.map +1 -0
- package/packages/premiere-windows-adapter/package.json +12 -0
- package/packages/premiere-windows-adapter/src/index.ts +25 -0
- package/scripts/blender-bridge-worker.mjs +311 -0
- package/scripts/check-adapters.mjs +57 -0
- package/scripts/install-premiere-cep.mjs +43 -0
- package/scripts/package-premiere-cep.mjs +236 -0
- package/scripts/release-assets.mjs +146 -0
- package/scripts/simulate-premiere-cep.mjs +194 -0
- package/third_party_licenses/README.md +19 -0
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
import { existsSync, statSync } from "node:fs";
|
|
2
|
+
import { basename, parse } from "node:path";
|
|
3
|
+
import type { ToolDefinition } from "@creative-pipeline-mcp/core";
|
|
4
|
+
import {
|
|
5
|
+
enqueueBlenderBridgeCommand,
|
|
6
|
+
findBlenderBridgeStatus,
|
|
7
|
+
listBlenderBridgeStatuses
|
|
8
|
+
} from "../adapters/blenderBridge.js";
|
|
9
|
+
import { optimizeWithCli, renderWithHeadlessBlender, runHeadlessBlenderScript } from "../adapters/cli.js";
|
|
10
|
+
import { placeholderPng } from "../adapters/preview.js";
|
|
11
|
+
import { artifactName, inspectAndReport, requirePath } from "./shared.js";
|
|
12
|
+
|
|
13
|
+
export const blenderTools: ToolDefinition[] = [
|
|
14
|
+
{
|
|
15
|
+
name: "blender.read_bridge_status",
|
|
16
|
+
description: "Read Blender bridge status JSON files produced by a trusted bridge adapter.",
|
|
17
|
+
category: "blender",
|
|
18
|
+
risk: "read",
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: "object",
|
|
21
|
+
properties: {},
|
|
22
|
+
additionalProperties: false
|
|
23
|
+
},
|
|
24
|
+
async execute() {
|
|
25
|
+
const statuses = await listBlenderBridgeStatuses();
|
|
26
|
+
return {
|
|
27
|
+
ok: true,
|
|
28
|
+
message: `${statuses.length} Blender bridge status records found`,
|
|
29
|
+
data: { statuses }
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "blender.await_bridge_status",
|
|
35
|
+
description: "Poll Blender bridge status files until a matching command status is available.",
|
|
36
|
+
category: "blender",
|
|
37
|
+
risk: "read",
|
|
38
|
+
inputSchema: {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: {
|
|
41
|
+
commandId: { type: "string" },
|
|
42
|
+
commandType: {
|
|
43
|
+
type: "string",
|
|
44
|
+
enum: ["create_scene", "create_asset", "modify_asset", "apply_material", "run_safe_script"]
|
|
45
|
+
},
|
|
46
|
+
timeoutMs: { type: "number" },
|
|
47
|
+
pollIntervalMs: { type: "number" }
|
|
48
|
+
},
|
|
49
|
+
additionalProperties: false
|
|
50
|
+
},
|
|
51
|
+
async execute(_context, input) {
|
|
52
|
+
const timeoutMs = Math.max(0, Math.min(typeof input.timeoutMs === "number" ? input.timeoutMs : 0, 120000));
|
|
53
|
+
const pollIntervalMs = Math.max(100, Math.min(typeof input.pollIntervalMs === "number" ? input.pollIntervalMs : 1000, 10000));
|
|
54
|
+
const deadline = Date.now() + timeoutMs;
|
|
55
|
+
do {
|
|
56
|
+
const match = await findBlenderBridgeStatus({
|
|
57
|
+
commandId: typeof input.commandId === "string" ? input.commandId : undefined,
|
|
58
|
+
commandType: isBlenderBridgeCommandType(input.commandType) ? input.commandType : undefined
|
|
59
|
+
});
|
|
60
|
+
if (match) {
|
|
61
|
+
return {
|
|
62
|
+
ok: true,
|
|
63
|
+
message: `Blender bridge status found: ${match.status.status}`,
|
|
64
|
+
data: match
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (Date.now() >= deadline) {
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
await sleep(pollIntervalMs);
|
|
71
|
+
} while (true);
|
|
72
|
+
return {
|
|
73
|
+
ok: false,
|
|
74
|
+
message: "Blender bridge status not found before timeout",
|
|
75
|
+
data: { commandId: input.commandId, commandType: input.commandType, timeoutMs }
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: "blender.create_scene",
|
|
81
|
+
description: "Create a scene-generation manifest for an external Blender bridge.",
|
|
82
|
+
category: "blender",
|
|
83
|
+
risk: "safe_write",
|
|
84
|
+
inputSchema: {
|
|
85
|
+
type: "object",
|
|
86
|
+
properties: {
|
|
87
|
+
prompt: { type: "string", maxLength: 2000 },
|
|
88
|
+
targetEngine: { type: "string", enum: ["Roblox", "Unity", "Unreal", "WebGL"] }
|
|
89
|
+
},
|
|
90
|
+
required: ["prompt"],
|
|
91
|
+
additionalProperties: false
|
|
92
|
+
},
|
|
93
|
+
async execute(context, input) {
|
|
94
|
+
const manifest = {
|
|
95
|
+
prompt: String(input.prompt ?? ""),
|
|
96
|
+
targetEngine: String(input.targetEngine ?? "WebGL"),
|
|
97
|
+
outputs: ["scene.glb", "scene_preview.png", "scene_qc_report.json"],
|
|
98
|
+
bridge: "queued_blender_bridge_required"
|
|
99
|
+
};
|
|
100
|
+
const artifact = await context.artifactStore.writeJson("blender/create_scene_manifest.json", manifest);
|
|
101
|
+
const queued = await enqueueBlenderBridgeCommand("create_scene", manifest);
|
|
102
|
+
return {
|
|
103
|
+
ok: true,
|
|
104
|
+
message: "Scene manifest written and Blender bridge command queued",
|
|
105
|
+
artifacts: [artifact, queued.path],
|
|
106
|
+
data: { manifest, command: queued.command }
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: "blender.apply_material",
|
|
112
|
+
description: "Create a material-application manifest for MaterialX or Blender material adapters.",
|
|
113
|
+
category: "blender",
|
|
114
|
+
risk: "safe_write",
|
|
115
|
+
inputSchema: {
|
|
116
|
+
type: "object",
|
|
117
|
+
properties: { path: { type: "string" }, material: { type: "object" } },
|
|
118
|
+
required: ["path"],
|
|
119
|
+
additionalProperties: false
|
|
120
|
+
},
|
|
121
|
+
async execute(context, input) {
|
|
122
|
+
const path = requirePath(input);
|
|
123
|
+
await context.artifactStore.assertReadableFile(path);
|
|
124
|
+
const manifest = { source: path, material: input.material ?? {}, adapter: "MaterialX_or_Blender_bridge" };
|
|
125
|
+
const artifact = await context.artifactStore.writeJson(artifactName(path, "_material_apply_manifest.json"), manifest);
|
|
126
|
+
const queued = await enqueueBlenderBridgeCommand("apply_material", manifest);
|
|
127
|
+
return {
|
|
128
|
+
ok: true,
|
|
129
|
+
message: "Material application manifest written and Blender bridge command queued",
|
|
130
|
+
artifacts: [artifact, queued.path],
|
|
131
|
+
data: { manifest, command: queued.command }
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: "blender.modify_asset",
|
|
137
|
+
description: "Create a non-destructive modification manifest for an existing asset.",
|
|
138
|
+
category: "blender",
|
|
139
|
+
risk: "safe_write",
|
|
140
|
+
inputSchema: {
|
|
141
|
+
type: "object",
|
|
142
|
+
properties: { path: { type: "string" }, instructions: { type: "string", maxLength: 4000 } },
|
|
143
|
+
required: ["path", "instructions"],
|
|
144
|
+
additionalProperties: false
|
|
145
|
+
},
|
|
146
|
+
async execute(context, input) {
|
|
147
|
+
const path = requirePath(input);
|
|
148
|
+
await context.artifactStore.assertReadableFile(path);
|
|
149
|
+
const manifest = {
|
|
150
|
+
source: path,
|
|
151
|
+
instructions: String(input.instructions ?? ""),
|
|
152
|
+
mode: "copy_then_modify",
|
|
153
|
+
requiredQcAfterModify: true
|
|
154
|
+
};
|
|
155
|
+
const artifact = await context.artifactStore.writeJson(artifactName(path, "_modify_manifest.json"), manifest);
|
|
156
|
+
const queued = await enqueueBlenderBridgeCommand("modify_asset", manifest);
|
|
157
|
+
return {
|
|
158
|
+
ok: true,
|
|
159
|
+
message: "Asset modification manifest written and Blender bridge command queued",
|
|
160
|
+
artifacts: [artifact, queued.path],
|
|
161
|
+
data: { manifest, command: queued.command }
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: "blender.create_asset",
|
|
167
|
+
description: "Alias-style manifest for creating a Blender asset through the external bridge.",
|
|
168
|
+
category: "blender",
|
|
169
|
+
risk: "safe_write",
|
|
170
|
+
inputSchema: {
|
|
171
|
+
type: "object",
|
|
172
|
+
properties: { prompt: { type: "string", maxLength: 2000 } },
|
|
173
|
+
required: ["prompt"],
|
|
174
|
+
additionalProperties: false
|
|
175
|
+
},
|
|
176
|
+
async execute(context, input) {
|
|
177
|
+
const manifest = {
|
|
178
|
+
prompt: String(input.prompt ?? ""),
|
|
179
|
+
outputs: ["asset.glb", "asset_preview.png", "asset_qc_report.json"],
|
|
180
|
+
bridge: "queued_blender_bridge_required"
|
|
181
|
+
};
|
|
182
|
+
const artifact = await context.artifactStore.writeJson("blender/create_asset_manifest.json", manifest);
|
|
183
|
+
const queued = await enqueueBlenderBridgeCommand("create_asset", manifest);
|
|
184
|
+
return {
|
|
185
|
+
ok: true,
|
|
186
|
+
message: "Asset creation manifest written and Blender bridge command queued",
|
|
187
|
+
artifacts: [artifact, queued.path],
|
|
188
|
+
data: { manifest, command: queued.command }
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: "blender.inspect_scene",
|
|
194
|
+
description: "Inspect a .glb/.gltf asset or report that external Blender is required for .blend.",
|
|
195
|
+
category: "blender",
|
|
196
|
+
risk: "read",
|
|
197
|
+
inputSchema: {
|
|
198
|
+
type: "object",
|
|
199
|
+
properties: { path: { type: "string" } },
|
|
200
|
+
required: ["path"],
|
|
201
|
+
additionalProperties: false
|
|
202
|
+
},
|
|
203
|
+
async execute(context, input) {
|
|
204
|
+
const path = requirePath(input);
|
|
205
|
+
await context.artifactStore.assertReadableFile(path);
|
|
206
|
+
if (!existsSync(path)) {
|
|
207
|
+
throw new Error(`Asset not found: ${path}`);
|
|
208
|
+
}
|
|
209
|
+
const report = await inspectAndReport(path);
|
|
210
|
+
return {
|
|
211
|
+
ok: report.summary.status !== "fail",
|
|
212
|
+
message: `Scene inspection finished: ${report.summary.status}`,
|
|
213
|
+
data: report as unknown as Record<string, unknown>
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
name: "blender.configure_engine_profile",
|
|
219
|
+
description: "Create a target engine profile for Roblox, Unity, Unreal, or WebGL asset budgets.",
|
|
220
|
+
category: "blender",
|
|
221
|
+
risk: "safe_write",
|
|
222
|
+
inputSchema: {
|
|
223
|
+
type: "object",
|
|
224
|
+
properties: { engine: { type: "string", enum: ["Roblox", "Unity", "Unreal", "WebGL"] } },
|
|
225
|
+
required: ["engine"],
|
|
226
|
+
additionalProperties: false
|
|
227
|
+
},
|
|
228
|
+
async execute(context, input) {
|
|
229
|
+
const engine = String(input.engine ?? "WebGL");
|
|
230
|
+
const profiles: Record<string, Record<string, unknown>> = {
|
|
231
|
+
Roblox: { maxTriangles: 10000, textureMax: 1024, formats: ["glb"] },
|
|
232
|
+
Unity: { maxTriangles: 50000, textureMax: 2048, formats: ["glb", "fbx"] },
|
|
233
|
+
Unreal: { maxTriangles: 100000, textureMax: 4096, formats: ["glb", "usd"] },
|
|
234
|
+
WebGL: { maxTriangles: 30000, textureMax: 2048, formats: ["glb"] }
|
|
235
|
+
};
|
|
236
|
+
const profile = profiles[engine] ?? profiles.WebGL;
|
|
237
|
+
const artifact = await context.artifactStore.writeJson(`blender/engine_profile_${engine}.json`, profile);
|
|
238
|
+
return { ok: true, message: "Engine profile written", artifacts: [artifact], data: profile };
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
name: "blender.create_usd_pipeline",
|
|
243
|
+
description: "Create a USD pipeline manifest for v2 asset exchange workflows.",
|
|
244
|
+
category: "blender",
|
|
245
|
+
risk: "safe_write",
|
|
246
|
+
inputSchema: {
|
|
247
|
+
type: "object",
|
|
248
|
+
properties: { path: { type: "string" } },
|
|
249
|
+
required: ["path"],
|
|
250
|
+
additionalProperties: false
|
|
251
|
+
},
|
|
252
|
+
async execute(context, input) {
|
|
253
|
+
const path = requirePath(input);
|
|
254
|
+
await context.artifactStore.assertReadableFile(path);
|
|
255
|
+
const manifest = {
|
|
256
|
+
source: path,
|
|
257
|
+
outputs: ["asset.usd", "asset_manifest.json"],
|
|
258
|
+
adapters: ["OpenUSD", "MaterialX", "OCIO/ACES"],
|
|
259
|
+
status: "external_adapter_required"
|
|
260
|
+
};
|
|
261
|
+
const artifact = await context.artifactStore.writeJson(artifactName(path, "_usd_pipeline_manifest.json"), manifest);
|
|
262
|
+
return { ok: true, message: "USD pipeline manifest written", artifacts: [artifact], data: manifest };
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
name: "blender.create_materialx_workflow",
|
|
267
|
+
description: "Create a MaterialX and OCIO/ACES lookdev workflow manifest.",
|
|
268
|
+
category: "blender",
|
|
269
|
+
risk: "safe_write",
|
|
270
|
+
inputSchema: {
|
|
271
|
+
type: "object",
|
|
272
|
+
properties: { path: { type: "string" }, look: { type: "string", maxLength: 200 } },
|
|
273
|
+
required: ["path"],
|
|
274
|
+
additionalProperties: false
|
|
275
|
+
},
|
|
276
|
+
async execute(context, input) {
|
|
277
|
+
const path = requirePath(input);
|
|
278
|
+
await context.artifactStore.assertReadableFile(path);
|
|
279
|
+
const manifest = {
|
|
280
|
+
source: path,
|
|
281
|
+
look: String(input.look ?? "neutral_pbr"),
|
|
282
|
+
adapters: ["MaterialX", "OpenImageIO", "OpenColorIO", "ACES"],
|
|
283
|
+
outputs: ["lookdev.mtlx", "color_report.json"]
|
|
284
|
+
};
|
|
285
|
+
const artifact = await context.artifactStore.writeJson(artifactName(path, "_materialx_workflow.json"), manifest);
|
|
286
|
+
return { ok: true, message: "MaterialX workflow manifest written", artifacts: [artifact], data: manifest };
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
name: "blender.plan_rig_animation",
|
|
291
|
+
description: "Create an advanced rig/animation plan without executing raw Blender scripts.",
|
|
292
|
+
category: "blender",
|
|
293
|
+
risk: "safe_write",
|
|
294
|
+
inputSchema: {
|
|
295
|
+
type: "object",
|
|
296
|
+
properties: { path: { type: "string" }, animationBrief: { type: "string", maxLength: 4000 } },
|
|
297
|
+
required: ["path"],
|
|
298
|
+
additionalProperties: false
|
|
299
|
+
},
|
|
300
|
+
async execute(context, input) {
|
|
301
|
+
const path = requirePath(input);
|
|
302
|
+
await context.artifactStore.assertReadableFile(path);
|
|
303
|
+
const plan = {
|
|
304
|
+
source: path,
|
|
305
|
+
animationBrief: String(input.animationBrief ?? ""),
|
|
306
|
+
steps: ["inspect_armature", "build_control_rig", "apply_animation", "export_preview", "validate_motion"],
|
|
307
|
+
rawBpy: "approval_required"
|
|
308
|
+
};
|
|
309
|
+
const artifact = await context.artifactStore.writeJson(artifactName(path, "_rig_animation_plan.json"), plan);
|
|
310
|
+
return { ok: true, message: "Rig/animation plan written", artifacts: [artifact], data: plan };
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
name: "blender.validate_asset",
|
|
315
|
+
description: "Write a standardized asset QC report for a .glb/.gltf/.blend target.",
|
|
316
|
+
category: "blender",
|
|
317
|
+
risk: "safe_write",
|
|
318
|
+
inputSchema: {
|
|
319
|
+
type: "object",
|
|
320
|
+
properties: {
|
|
321
|
+
path: { type: "string" },
|
|
322
|
+
maxTriangles: { type: "number" }
|
|
323
|
+
},
|
|
324
|
+
required: ["path"],
|
|
325
|
+
additionalProperties: false
|
|
326
|
+
},
|
|
327
|
+
async execute(context, input) {
|
|
328
|
+
const path = requirePath(input);
|
|
329
|
+
await context.artifactStore.assertReadableFile(path);
|
|
330
|
+
const maxTriangles = typeof input.maxTriangles === "number" ? input.maxTriangles : 50000;
|
|
331
|
+
const report = await inspectAndReport(path, maxTriangles);
|
|
332
|
+
const artifact = await context.artifactStore.writeJson(artifactName(path, "_asset_qc_report.json"), report);
|
|
333
|
+
return {
|
|
334
|
+
ok: report.summary.status !== "fail",
|
|
335
|
+
message: `Asset QC report written: ${report.summary.status}`,
|
|
336
|
+
artifacts: [artifact],
|
|
337
|
+
data: report as unknown as Record<string, unknown>
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
name: "blender.render_preview",
|
|
343
|
+
description: "Create a deterministic placeholder preview unless an external Blender renderer is configured.",
|
|
344
|
+
category: "blender",
|
|
345
|
+
risk: "safe_write",
|
|
346
|
+
inputSchema: {
|
|
347
|
+
type: "object",
|
|
348
|
+
properties: { path: { type: "string" } },
|
|
349
|
+
required: ["path"],
|
|
350
|
+
additionalProperties: false
|
|
351
|
+
},
|
|
352
|
+
async execute(context, input) {
|
|
353
|
+
const path = requirePath(input);
|
|
354
|
+
await context.artifactStore.assertReadableFile(path);
|
|
355
|
+
const artifact = await context.artifactStore.writeBytes(artifactName(path, "_preview.png"), placeholderPng());
|
|
356
|
+
const render = await renderWithHeadlessBlender(path, artifact);
|
|
357
|
+
if (render.available && !render.error) {
|
|
358
|
+
return {
|
|
359
|
+
ok: true,
|
|
360
|
+
message: "Headless Blender preview rendered",
|
|
361
|
+
artifacts: [artifact],
|
|
362
|
+
data: { renderer: "blender_headless", command: render.command }
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
return {
|
|
366
|
+
ok: true,
|
|
367
|
+
message: "Preview placeholder written; Blender headless renderer unavailable",
|
|
368
|
+
artifacts: [artifact],
|
|
369
|
+
data: { renderer: "placeholder", source: path, blender: render }
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
name: "blender.optimize_asset",
|
|
375
|
+
description: "Create an optimized artifact slot; copies source when external glTF optimizer is unavailable.",
|
|
376
|
+
category: "blender",
|
|
377
|
+
risk: "safe_write",
|
|
378
|
+
inputSchema: {
|
|
379
|
+
type: "object",
|
|
380
|
+
properties: { path: { type: "string" } },
|
|
381
|
+
required: ["path"],
|
|
382
|
+
additionalProperties: false
|
|
383
|
+
},
|
|
384
|
+
async execute(context, input) {
|
|
385
|
+
const path = requirePath(input);
|
|
386
|
+
await context.artifactStore.assertReadableFile(path);
|
|
387
|
+
const target = artifactName(path, "_optimized.glb");
|
|
388
|
+
const artifact = await context.artifactStore.writeBytes(target, new Uint8Array());
|
|
389
|
+
const optimized = await optimizeWithCli(path, artifact);
|
|
390
|
+
if (optimized.available && !optimized.error) {
|
|
391
|
+
const sourceBytes = statSync(path).size;
|
|
392
|
+
const optimizedBytes = statSync(artifact).size;
|
|
393
|
+
return {
|
|
394
|
+
ok: true,
|
|
395
|
+
message: "Optimized artifact written with external glTF optimizer",
|
|
396
|
+
artifacts: [artifact],
|
|
397
|
+
data: {
|
|
398
|
+
optimizer: optimized.command,
|
|
399
|
+
source: path,
|
|
400
|
+
sourceBytes,
|
|
401
|
+
optimizedBytes,
|
|
402
|
+
deltaBytes: optimizedBytes - sourceBytes,
|
|
403
|
+
ratio: sourceBytes > 0 ? optimizedBytes / sourceBytes : null
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
const fallback = await context.artifactStore.copyIn(path, target);
|
|
408
|
+
const sourceBytes = statSync(path).size;
|
|
409
|
+
const fallbackBytes = statSync(fallback).size;
|
|
410
|
+
return {
|
|
411
|
+
ok: true,
|
|
412
|
+
message: "Optimized artifact written by copy fallback; glTF optimizer unavailable or failed",
|
|
413
|
+
artifacts: [fallback],
|
|
414
|
+
data: {
|
|
415
|
+
optimizer: "copy_fallback",
|
|
416
|
+
source: path,
|
|
417
|
+
sourceBytes,
|
|
418
|
+
optimizedBytes: fallbackBytes,
|
|
419
|
+
deltaBytes: fallbackBytes - sourceBytes,
|
|
420
|
+
ratio: sourceBytes > 0 ? fallbackBytes / sourceBytes : null,
|
|
421
|
+
cli: optimized
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
name: "blender.export_game_ready",
|
|
428
|
+
description: "Export a game-ready artifact copy and QC report.",
|
|
429
|
+
category: "blender",
|
|
430
|
+
risk: "project_write",
|
|
431
|
+
inputSchema: {
|
|
432
|
+
type: "object",
|
|
433
|
+
properties: {
|
|
434
|
+
path: { type: "string" },
|
|
435
|
+
maxTriangles: { type: "number" }
|
|
436
|
+
},
|
|
437
|
+
required: ["path"],
|
|
438
|
+
additionalProperties: false
|
|
439
|
+
},
|
|
440
|
+
async execute(context, input) {
|
|
441
|
+
const path = requirePath(input);
|
|
442
|
+
await context.artifactStore.assertReadableFile(path);
|
|
443
|
+
const report = await inspectAndReport(path, typeof input.maxTriangles === "number" ? input.maxTriangles : 50000);
|
|
444
|
+
const exported = await context.artifactStore.copyIn(path, artifactName(path, "_game_ready.glb"));
|
|
445
|
+
const qc = await context.artifactStore.writeJson(artifactName(path, "_game_ready_qc.json"), report);
|
|
446
|
+
return {
|
|
447
|
+
ok: report.summary.status !== "fail",
|
|
448
|
+
message: `Game-ready export fallback completed: ${report.summary.status}`,
|
|
449
|
+
artifacts: [exported, qc],
|
|
450
|
+
data: report as unknown as Record<string, unknown>
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
name: "blender.create_game_asset",
|
|
456
|
+
description: "Create a production job manifest for an external Blender bridge to generate a game asset.",
|
|
457
|
+
category: "blender",
|
|
458
|
+
risk: "safe_write",
|
|
459
|
+
inputSchema: {
|
|
460
|
+
type: "object",
|
|
461
|
+
properties: {
|
|
462
|
+
prompt: { type: "string", maxLength: 2000 },
|
|
463
|
+
budget: { type: "object" }
|
|
464
|
+
},
|
|
465
|
+
required: ["prompt"],
|
|
466
|
+
additionalProperties: false
|
|
467
|
+
},
|
|
468
|
+
async execute(context, input) {
|
|
469
|
+
const prompt = String(input.prompt ?? "");
|
|
470
|
+
const manifest = {
|
|
471
|
+
prompt,
|
|
472
|
+
target: "game_ready_glb",
|
|
473
|
+
excluded: ["3D-Agent"],
|
|
474
|
+
requiredQc: ["triangle_budget", "origin", "scale", "normals", "textures", "export_success"],
|
|
475
|
+
bridge: "external_blender_required",
|
|
476
|
+
safeScript: "blender/create_game_asset_safe.py"
|
|
477
|
+
};
|
|
478
|
+
const artifact = await context.artifactStore.writeJson("blender/create_game_asset_job.json", manifest);
|
|
479
|
+
const script = await context.artifactStore.writeText(
|
|
480
|
+
"blender/create_game_asset_safe.py",
|
|
481
|
+
safeBlenderAssetScript(prompt)
|
|
482
|
+
);
|
|
483
|
+
const queued = await enqueueBlenderBridgeCommand("run_safe_script", {
|
|
484
|
+
...manifest,
|
|
485
|
+
scriptPath: script
|
|
486
|
+
});
|
|
487
|
+
return {
|
|
488
|
+
ok: true,
|
|
489
|
+
message: "Game asset job manifest and safe Blender bridge command written",
|
|
490
|
+
artifacts: [artifact, script, queued.path],
|
|
491
|
+
data: { manifest, command: queued.command }
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
name: "blender.create_material_pack",
|
|
497
|
+
description: "Create a material-pack manifest for MaterialX/OpenImageIO/OpenColorIO-capable adapters.",
|
|
498
|
+
category: "blender",
|
|
499
|
+
risk: "safe_write",
|
|
500
|
+
inputSchema: {
|
|
501
|
+
type: "object",
|
|
502
|
+
properties: { prompt: { type: "string", maxLength: 2000 } },
|
|
503
|
+
required: ["prompt"],
|
|
504
|
+
additionalProperties: false
|
|
505
|
+
},
|
|
506
|
+
async execute(context, input) {
|
|
507
|
+
const artifact = await context.artifactStore.writeJson("blender/material_pack_manifest.json", {
|
|
508
|
+
prompt: String(input.prompt ?? ""),
|
|
509
|
+
adapters: ["MaterialX", "OpenImageIO", "OpenColorIO"],
|
|
510
|
+
status: "external_adapter_required"
|
|
511
|
+
});
|
|
512
|
+
return { ok: true, message: "Material pack manifest written", artifacts: [artifact] };
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
name: "blender.fix_asset_issues",
|
|
517
|
+
description: "Create a repair plan from an asset QC report.",
|
|
518
|
+
category: "blender",
|
|
519
|
+
risk: "safe_write",
|
|
520
|
+
inputSchema: {
|
|
521
|
+
type: "object",
|
|
522
|
+
properties: { path: { type: "string" } },
|
|
523
|
+
required: ["path"],
|
|
524
|
+
additionalProperties: false
|
|
525
|
+
},
|
|
526
|
+
async execute(context, input) {
|
|
527
|
+
const path = requirePath(input);
|
|
528
|
+
await context.artifactStore.assertReadableFile(path);
|
|
529
|
+
const report = await inspectAndReport(path);
|
|
530
|
+
const plan = {
|
|
531
|
+
source: basename(path),
|
|
532
|
+
fixes: report.checks
|
|
533
|
+
.filter((check) => check.status === "warn" || check.status === "fail")
|
|
534
|
+
.map((check) => ({ check: check.id, action: `route_to_adapter_for_${check.id.replaceAll(".", "_")}` }))
|
|
535
|
+
};
|
|
536
|
+
const artifact = await context.artifactStore.writeJson(
|
|
537
|
+
`blender/${parse(basename(path)).name}_repair_plan.json`,
|
|
538
|
+
plan
|
|
539
|
+
);
|
|
540
|
+
return { ok: true, message: "Asset repair plan written", artifacts: [artifact], data: plan };
|
|
541
|
+
}
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
name: "blender.repair_basic_asset",
|
|
545
|
+
description: "Run a template-based Blender repair pass for scale, normals, triangulation, and GLB export.",
|
|
546
|
+
category: "blender",
|
|
547
|
+
risk: "safe_write",
|
|
548
|
+
inputSchema: {
|
|
549
|
+
type: "object",
|
|
550
|
+
properties: {
|
|
551
|
+
path: { type: "string" },
|
|
552
|
+
maxTriangles: { type: "number" }
|
|
553
|
+
},
|
|
554
|
+
required: ["path"],
|
|
555
|
+
additionalProperties: false
|
|
556
|
+
},
|
|
557
|
+
async execute(context, input) {
|
|
558
|
+
const path = requirePath(input);
|
|
559
|
+
await context.artifactStore.assertReadableFile(path);
|
|
560
|
+
const repairedTarget = artifactName(path, "_repaired.glb");
|
|
561
|
+
const repaired = `${context.artifactStore.root}/${repairedTarget}`;
|
|
562
|
+
const scriptText = repairBasicAssetScript(path, repaired);
|
|
563
|
+
const script = await context.artifactStore.writeText(artifactName(path, "_repair_basic.py"), scriptText);
|
|
564
|
+
const run = await runHeadlessBlenderScript(scriptText);
|
|
565
|
+
if (!run.available || run.error) {
|
|
566
|
+
return {
|
|
567
|
+
ok: false,
|
|
568
|
+
message: "Basic repair script written; headless Blender repair was not completed",
|
|
569
|
+
artifacts: [script],
|
|
570
|
+
data: { blender: run }
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
if (!existsSync(repaired)) {
|
|
574
|
+
return {
|
|
575
|
+
ok: false,
|
|
576
|
+
message: "Basic repair script ran but did not produce a repaired GLB",
|
|
577
|
+
artifacts: [script],
|
|
578
|
+
data: { blender: run, expectedOutput: repaired }
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
const report = await inspectAndReport(repaired, typeof input.maxTriangles === "number" ? input.maxTriangles : 50000);
|
|
582
|
+
const qc = await context.artifactStore.writeJson(artifactName(path, "_repaired_qc.json"), report);
|
|
583
|
+
return {
|
|
584
|
+
ok: report.summary.status !== "fail",
|
|
585
|
+
message: `Basic Blender repair completed: ${report.summary.status}`,
|
|
586
|
+
artifacts: [script, repaired, qc],
|
|
587
|
+
data: { blender: run, qc: report }
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
];
|
|
592
|
+
|
|
593
|
+
function safeBlenderAssetScript(prompt: string): string {
|
|
594
|
+
const name = prompt
|
|
595
|
+
.replace(/[^a-z0-9]+/giu, "_")
|
|
596
|
+
.replace(/^_+|_+$/gu, "")
|
|
597
|
+
.slice(0, 48) || "CreativePipelineAsset";
|
|
598
|
+
return `import bpy
|
|
599
|
+
|
|
600
|
+
bpy.ops.object.select_all(action="SELECT")
|
|
601
|
+
bpy.ops.object.delete()
|
|
602
|
+
bpy.ops.mesh.primitive_cube_add(size=1)
|
|
603
|
+
asset = bpy.context.object
|
|
604
|
+
asset.name = ${JSON.stringify(name)}
|
|
605
|
+
asset.location = (0, 0, 0)
|
|
606
|
+
|
|
607
|
+
mat = bpy.data.materials.new(name=${JSON.stringify(`${name}_Material`)})
|
|
608
|
+
mat.use_nodes = True
|
|
609
|
+
asset.data.materials.append(mat)
|
|
610
|
+
|
|
611
|
+
bpy.ops.export_scene.gltf(filepath=${JSON.stringify(`${name}.glb`)}, export_format="GLB")
|
|
612
|
+
`;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function repairBasicAssetScript(source: string, target: string): string {
|
|
616
|
+
return `import bpy
|
|
617
|
+
|
|
618
|
+
source = ${JSON.stringify(source)}
|
|
619
|
+
target = ${JSON.stringify(target)}
|
|
620
|
+
|
|
621
|
+
bpy.ops.object.select_all(action="SELECT")
|
|
622
|
+
bpy.ops.object.delete()
|
|
623
|
+
|
|
624
|
+
if source.lower().endswith((".glb", ".gltf")):
|
|
625
|
+
bpy.ops.import_scene.gltf(filepath=source)
|
|
626
|
+
else:
|
|
627
|
+
raise RuntimeError("repair_basic_asset supports .glb and .gltf inputs")
|
|
628
|
+
|
|
629
|
+
for obj in list(bpy.context.scene.objects):
|
|
630
|
+
if obj.type != "MESH":
|
|
631
|
+
continue
|
|
632
|
+
bpy.ops.object.select_all(action="DESELECT")
|
|
633
|
+
obj.select_set(True)
|
|
634
|
+
bpy.context.view_layer.objects.active = obj
|
|
635
|
+
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
|
|
636
|
+
try:
|
|
637
|
+
bpy.ops.object.origin_set(type="ORIGIN_GEOMETRY", center="BOUNDS")
|
|
638
|
+
except Exception:
|
|
639
|
+
pass
|
|
640
|
+
bpy.ops.object.mode_set(mode="EDIT")
|
|
641
|
+
bpy.ops.mesh.select_all(action="SELECT")
|
|
642
|
+
bpy.ops.mesh.normals_make_consistent(inside=False)
|
|
643
|
+
bpy.ops.object.mode_set(mode="OBJECT")
|
|
644
|
+
modifier = obj.modifiers.new(name="CreativePipelineTriangulate", type="TRIANGULATE")
|
|
645
|
+
bpy.ops.object.modifier_apply(modifier=modifier.name)
|
|
646
|
+
|
|
647
|
+
bpy.ops.export_scene.gltf(filepath=target, export_format="GLB")
|
|
648
|
+
`;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function sleep(ms: number): Promise<void> {
|
|
652
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function isBlenderBridgeCommandType(
|
|
656
|
+
value: unknown
|
|
657
|
+
): value is "create_scene" | "create_asset" | "modify_asset" | "apply_material" | "run_safe_script" {
|
|
658
|
+
return value === "create_scene"
|
|
659
|
+
|| value === "create_asset"
|
|
660
|
+
|| value === "modify_asset"
|
|
661
|
+
|| value === "apply_material"
|
|
662
|
+
|| value === "run_safe_script";
|
|
663
|
+
}
|