@nodetool-ai/blender-nodes 0.8.1

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.
Files changed (83) hide show
  1. package/README.md +36 -0
  2. package/blender_ops/depth.py +133 -0
  3. package/blender_ops/errors.py +46 -0
  4. package/blender_ops/exr.py +205 -0
  5. package/blender_ops/framing.py +75 -0
  6. package/blender_ops/ops/__init__.py +24 -0
  7. package/blender_ops/ops/common.py +310 -0
  8. package/blender_ops/ops/export_model.py +95 -0
  9. package/blender_ops/ops/prepare_for_engine.py +352 -0
  10. package/blender_ops/ops/render_animation.py +382 -0
  11. package/blender_ops/ops/render_image.py +41 -0
  12. package/blender_ops/ops/render_passes.py +363 -0
  13. package/blender_ops/run_job.py +110 -0
  14. package/blender_ops/tests/test_camera_lens.py +107 -0
  15. package/blender_ops/tests/test_depth.py +158 -0
  16. package/blender_ops/tests/test_exr.py +218 -0
  17. package/blender_ops/tests/test_framing.py +50 -0
  18. package/dist/.nodetool-build-stamp +1 -0
  19. package/dist/bake.d.ts +107 -0
  20. package/dist/bake.d.ts.map +1 -0
  21. package/dist/bake.js +209 -0
  22. package/dist/bake.js.map +1 -0
  23. package/dist/blender-binary.d.ts +38 -0
  24. package/dist/blender-binary.d.ts.map +1 -0
  25. package/dist/blender-binary.js +169 -0
  26. package/dist/blender-binary.js.map +1 -0
  27. package/dist/index.d.ts +31 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +40 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/job.d.ts +163 -0
  32. package/dist/job.d.ts.map +1 -0
  33. package/dist/job.js +67 -0
  34. package/dist/job.js.map +1 -0
  35. package/dist/nodes/bake-timeline-clip.d.ts +48 -0
  36. package/dist/nodes/bake-timeline-clip.d.ts.map +1 -0
  37. package/dist/nodes/bake-timeline-clip.js +152 -0
  38. package/dist/nodes/bake-timeline-clip.js.map +1 -0
  39. package/dist/nodes/blender-error.d.ts +21 -0
  40. package/dist/nodes/blender-error.d.ts.map +1 -0
  41. package/dist/nodes/blender-error.js +38 -0
  42. package/dist/nodes/blender-error.js.map +1 -0
  43. package/dist/nodes/defaults.d.ts +18 -0
  44. package/dist/nodes/defaults.d.ts.map +1 -0
  45. package/dist/nodes/defaults.js +18 -0
  46. package/dist/nodes/defaults.js.map +1 -0
  47. package/dist/nodes/export-model.d.ts +49 -0
  48. package/dist/nodes/export-model.d.ts.map +1 -0
  49. package/dist/nodes/export-model.js +127 -0
  50. package/dist/nodes/export-model.js.map +1 -0
  51. package/dist/nodes/prepare-for-engine.d.ts +52 -0
  52. package/dist/nodes/prepare-for-engine.d.ts.map +1 -0
  53. package/dist/nodes/prepare-for-engine.js +127 -0
  54. package/dist/nodes/prepare-for-engine.js.map +1 -0
  55. package/dist/nodes/progress.d.ts +11 -0
  56. package/dist/nodes/progress.d.ts.map +1 -0
  57. package/dist/nodes/progress.js +21 -0
  58. package/dist/nodes/progress.js.map +1 -0
  59. package/dist/nodes/render-animation.d.ts +52 -0
  60. package/dist/nodes/render-animation.d.ts.map +1 -0
  61. package/dist/nodes/render-animation.js +136 -0
  62. package/dist/nodes/render-animation.js.map +1 -0
  63. package/dist/nodes/render-base.d.ts +35 -0
  64. package/dist/nodes/render-base.d.ts.map +1 -0
  65. package/dist/nodes/render-base.js +95 -0
  66. package/dist/nodes/render-base.js.map +1 -0
  67. package/dist/nodes/render-image.d.ts +41 -0
  68. package/dist/nodes/render-image.d.ts.map +1 -0
  69. package/dist/nodes/render-image.js +98 -0
  70. package/dist/nodes/render-image.js.map +1 -0
  71. package/dist/nodes/render-passes.d.ts +54 -0
  72. package/dist/nodes/render-passes.d.ts.map +1 -0
  73. package/dist/nodes/render-passes.js +167 -0
  74. package/dist/nodes/render-passes.js.map +1 -0
  75. package/dist/run-job.d.ts +34 -0
  76. package/dist/run-job.d.ts.map +1 -0
  77. package/dist/run-job.js +137 -0
  78. package/dist/run-job.js.map +1 -0
  79. package/dist/runner.d.ts +124 -0
  80. package/dist/runner.d.ts.map +1 -0
  81. package/dist/runner.js +521 -0
  82. package/dist/runner.js.map +1 -0
  83. package/package.json +73 -0
package/dist/runner.js ADDED
@@ -0,0 +1,521 @@
1
+ /**
2
+ * The runner interface and its local implementation (D6).
3
+ *
4
+ * The runner works on logical files and nothing else. No scratch directory,
5
+ * no argv, and no path crosses the interface, so the local and worker
6
+ * implementations differ only in where the bytes go.
7
+ */
8
+ import { existsSync } from "node:fs";
9
+ import { mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
10
+ import os from "node:os";
11
+ import path from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+ import { createLogger } from "@nodetool-ai/config";
14
+ import { BlenderExecutorError, createPythonBridge, executeBlender, runHostBinary, withSpan } from "@nodetool-ai/runtime";
15
+ import { resolveBlenderBinary } from "./blender-binary.js";
16
+ import { blenderResultSchema } from "./job.js";
17
+ const log = createLogger("nodetool.blender-nodes.runner");
18
+ /** Per-output byte cap, overridable per call. */
19
+ export const MAX_OUTPUT_BYTES = 512 * 1024 * 1024;
20
+ /** Cap on the sum of all outputs, overridable per call. */
21
+ export const MAX_TOTAL_OUTPUT_BYTES = 1024 * 1024 * 1024;
22
+ /** Ceiling on declared outputs. */
23
+ export const MAX_OUTPUT_COUNT = 32;
24
+ /** Last bytes of stderr carried on a `bad_result`. */
25
+ const STDERR_TAIL_BYTES = 4 * 1024;
26
+ /** Exit code `run_job.py` uses for "the script raised" (D5). */
27
+ const PYTHON_EXIT_CODE = 64;
28
+ export class BlenderJobError extends Error {
29
+ code;
30
+ constructor(code, message) {
31
+ super(message);
32
+ this.name = "BlenderJobError";
33
+ this.code = code;
34
+ }
35
+ }
36
+ let workerBridgeFactoryForTesting = null;
37
+ /** Test seam for D7 selection without opening a real worker socket. */
38
+ export function __setWorkerBlenderBridgeFactoryForTesting(factory) {
39
+ workerBridgeFactoryForTesting = factory;
40
+ }
41
+ /**
42
+ * Directory holding the vendored `run_job.py` op script, which differs by
43
+ * deployment and is checked rather than assumed (same shape as
44
+ * `bundledFontsDir` in `@nodetool-ai/timeline`):
45
+ *
46
+ * - a checkout or an npm install resolves `packages/blender-nodes/blender_ops/`,
47
+ * one level above this module's `dist/` output;
48
+ * - the packaged backend is one flat `server.mjs`, so `import.meta.url` is the
49
+ * bundle root and `bundle-backend.mjs` stages the directory beside it as
50
+ * `_blender_ops/`.
51
+ *
52
+ * Throws naming the expected entry point when neither exists, so a missing
53
+ * stage fails here with the fix instead of inside Blender's stderr tail.
54
+ */
55
+ export function resolveOpScriptDir() {
56
+ const here = path.dirname(fileURLToPath(import.meta.url));
57
+ const candidates = [
58
+ path.join(here, "..", "blender_ops"),
59
+ path.join(here, "_blender_ops")
60
+ ];
61
+ for (const dir of candidates) {
62
+ if (existsSync(path.join(dir, "run_job.py")))
63
+ return dir;
64
+ }
65
+ throw new BlenderJobError("bad_result", `Blender op scripts not found. Tried ${candidates.join(" and ")}. ` +
66
+ `In dev, check packages/blender-nodes/blender_ops/ exists; in the ` +
67
+ `packaged app, check bundle-backend.mjs staged _blender_ops/.`);
68
+ }
69
+ /** Env keys the Blender child inherits. Everything else stays out. */
70
+ const ALLOWED_ENV_KEYS = [
71
+ "PATH",
72
+ "HOME",
73
+ "TMPDIR",
74
+ "LANG",
75
+ "SYSTEMROOT",
76
+ "CUDA_VISIBLE_DEVICES"
77
+ ];
78
+ function stderrTail(stderr) {
79
+ return Buffer.from(stderr, "utf8").subarray(-STDERR_TAIL_BYTES).toString("utf8");
80
+ }
81
+ /** Blender prints `Fra:<n>` lines on stderr during animation renders. */
82
+ function parseProgressFrame(line) {
83
+ const match = /^Fra:(\d+)\b/.exec(line.trim());
84
+ return match ? Number(match[1]) : null;
85
+ }
86
+ function progressCounts(job, frame, total = frame) {
87
+ if (job.job.op === "render_animation") {
88
+ const { frame_start, frame_end } = job.job.params;
89
+ if (Number.isInteger(frame_start) &&
90
+ Number.isInteger(frame_end) &&
91
+ frame_end >= frame_start) {
92
+ return [frame - frame_start + 1, frame_end - frame_start + 1];
93
+ }
94
+ }
95
+ return [frame, total];
96
+ }
97
+ export class LocalBlenderRunner {
98
+ kind = "local";
99
+ scratchParent;
100
+ binaryPath;
101
+ constructor(options = {}) {
102
+ this.scratchParent = options.scratchParent;
103
+ this.binaryPath = options.binaryPath;
104
+ }
105
+ async run(job, inputs, options) {
106
+ // The wall clock this run started on: a `.crash.txt` older than this
107
+ // belongs to an earlier run (or the user's own interactive Blender on
108
+ // a shared `$TMPDIR`) and must not be attached to this run's failure.
109
+ const runStartMs = Date.now();
110
+ // Refuse before resolving a binary: without a scratch parent there is
111
+ // no seam to stage through, and no tmpdir fallback exists.
112
+ const parent = options.scratchParent ?? this.scratchParent;
113
+ if (parent === undefined) {
114
+ throw new BlenderJobError("bad_job", `LocalBlenderRunner has no scratch directory: pass scratchParent ` +
115
+ `in BlenderRunOptions (runBlenderJob derives it from ` +
116
+ `context.workspace.scratchDir()).`);
117
+ }
118
+ const binary = await resolveBlenderBinary({
119
+ configuredPath: this.binaryPath
120
+ });
121
+ const maxOutputBytes = options.maxOutputBytes ?? MAX_OUTPUT_BYTES;
122
+ const maxTotalOutputBytes = options.maxTotalOutputBytes ?? MAX_TOTAL_OUTPUT_BYTES;
123
+ // Step 1: own a scratch directory under the workspace seam; write each
124
+ // input under its declared bare file name, then `job.json`. Only the
125
+ // per-run directory the runner creates is deleted in step 6 — never the
126
+ // parent, which on a local workspace is the workspace itself.
127
+ const cwd = await mkdtemp(path.join(parent, "nodetool-blender-"));
128
+ try {
129
+ for (const [name, bytes] of Object.entries(inputs)) {
130
+ const file = declaredInputFile(job, name);
131
+ if (file === undefined) {
132
+ throw new BlenderJobError("bad_job", `Input "${name}" is not declared in job.inputs.`);
133
+ }
134
+ await writeFile(path.join(cwd, file), bytes);
135
+ }
136
+ await writeFile(path.join(cwd, "job.json"), JSON.stringify(job));
137
+ // The user's add-ons and startup scripts never load: the BLENDER_USER_*
138
+ // dirs point at an empty directory the runner owns.
139
+ const userDir = path.join(cwd, "blender-user");
140
+ await mkdir(path.join(userDir, "config"), { recursive: true });
141
+ await mkdir(path.join(userDir, "scripts"), { recursive: true });
142
+ await mkdir(path.join(userDir, "extensions"), { recursive: true });
143
+ const env = {};
144
+ for (const key of ALLOWED_ENV_KEYS) {
145
+ const value = process.env[key];
146
+ if (value !== undefined)
147
+ env[key] = value;
148
+ }
149
+ env["BLENDER_USER_CONFIG"] = path.join(userDir, "config");
150
+ env["BLENDER_USER_SCRIPTS"] = path.join(userDir, "scripts");
151
+ env["BLENDER_USER_EXTENSIONS"] = path.join(userDir, "extensions");
152
+ // Blender's Python byte-compiles the op scripts where it finds them,
153
+ // which for a source checkout is the tree itself. Two runs at once then
154
+ // disagree about what blender_ops holds.
155
+ env["PYTHONDONTWRITEBYTECODE"] = "1";
156
+ // Step 2: spawn through the bounded host binary runner.
157
+ const argv = [
158
+ "-b",
159
+ "--factory-startup",
160
+ "--disable-autoexec",
161
+ "--python-exit-code",
162
+ String(PYTHON_EXIT_CODE),
163
+ "--python",
164
+ path.join(resolveOpScriptDir(), "run_job.py"),
165
+ "--",
166
+ "job.json"
167
+ ];
168
+ const onProgress = options.onProgress;
169
+ let result;
170
+ try {
171
+ result = await runHostBinary(binary.path, argv, {
172
+ cwd,
173
+ timeoutMs: options.timeoutMs,
174
+ signal: options.signal,
175
+ concurrencyClass: "render",
176
+ env,
177
+ onStderrLine: onProgress === undefined
178
+ ? undefined
179
+ : (line) => {
180
+ const frame = parseProgressFrame(line);
181
+ if (frame !== null)
182
+ onProgress(...progressCounts(job, frame));
183
+ }
184
+ });
185
+ }
186
+ catch (err) {
187
+ // Cancellation rejects with the abort reason: pass it through
188
+ // unwrapped so the node rejects with the abort reason.
189
+ if (options.signal?.aborted)
190
+ throw err;
191
+ throw new BlenderJobError("bad_result", `Blender run failed before producing a result: ` +
192
+ `${err instanceof Error ? err.message : String(err)}`);
193
+ }
194
+ if (isTimeout(result)) {
195
+ throw new BlenderJobError("timeout", `Blender render timed out after ${options.timeoutMs}ms. ` +
196
+ `Lower the samples, use EEVEE, or raise the timeout.`);
197
+ }
198
+ // Step 3: read `result.json`. A missing or unparsable file is
199
+ // `bad_result` carrying the last 4 KiB of stderr (plus the `.crash.txt`
200
+ // Blender leaves on a segfault, when present).
201
+ const parsed = await readResult(cwd, result.stderr, runStartMs);
202
+ // Step 4: the op's own failure.
203
+ if (!parsed.ok) {
204
+ throw new BlenderJobError(parsed.error.code, parsed.error.message);
205
+ }
206
+ // Step 5: read only the outputs `job.outputs` declared. A name in
207
+ // `produced` the job did not declare is ignored at warn; a path inside
208
+ // `result.json` is never opened — the result carries no paths at all.
209
+ const declared = Object.entries(job.outputs);
210
+ const produced = new Set(parsed.produced);
211
+ for (const name of parsed.produced) {
212
+ if (!(name in job.outputs)) {
213
+ log.warn(`Blender produced undeclared output "${name}"; ignoring.`);
214
+ }
215
+ }
216
+ // Stat every declared output before reading any byte, so both caps
217
+ // throw before a file is read into memory.
218
+ let total = 0;
219
+ for (const [name, file] of declared) {
220
+ if (!produced.has(name)) {
221
+ throw new BlenderJobError("missing_output", `Blender did not produce declared output "${name}" (file "${file}").`);
222
+ }
223
+ let size;
224
+ try {
225
+ size = (await stat(path.join(cwd, file))).size;
226
+ }
227
+ catch {
228
+ throw new BlenderJobError("missing_output", `Blender did not produce declared output "${name}": file "${file}" is missing.`);
229
+ }
230
+ if (size > maxOutputBytes) {
231
+ throw new BlenderJobError("output_too_large", `Output "${name}" is ${size} bytes, above the ${maxOutputBytes}-byte per-output cap.`);
232
+ }
233
+ total += size;
234
+ if (total > maxTotalOutputBytes) {
235
+ throw new BlenderJobError("output_too_large", `Outputs total ${total} bytes, above the ${maxTotalOutputBytes}-byte total cap (reached at "${name}").`);
236
+ }
237
+ }
238
+ const outputs = {};
239
+ for (const [name, file] of declared) {
240
+ outputs[name] = new Uint8Array(await readFile(path.join(cwd, file)));
241
+ }
242
+ return {
243
+ outputs,
244
+ stats: parsed.stats,
245
+ exitCode: result.exitCode,
246
+ queuedMs: result.queuedMs
247
+ };
248
+ }
249
+ finally {
250
+ // Step 6: the scratch directory goes away on every path — abort,
251
+ // timeout, and the cap errors included.
252
+ await rm(cwd, { recursive: true, force: true });
253
+ }
254
+ }
255
+ }
256
+ /**
257
+ * Worker implementation of the runner contract (Stage 4b, D6).
258
+ *
259
+ * The worker image carries Blender but not the NodeTool ops. Every run sends
260
+ * the complete ops tree as extra blobs, keyed by paths relative to
261
+ * `blender_ops/`, so its scripts match the NodeTool release that issued the
262
+ * job. Workflow inputs remain the only entries in `job.inputs`.
263
+ */
264
+ export class WorkerBlenderRunner {
265
+ kind = "worker";
266
+ bridge;
267
+ constructor(options = {}) {
268
+ this.bridge = options.bridge;
269
+ }
270
+ /** Connect and check the worker status without retaining an owned socket. */
271
+ async assertAvailable(options = {}) {
272
+ await withSpan("blender.worker.status", { "rpc.system": "websocket", "rpc.method": "worker.status" }, () => this.withBridge(async (bridge) => {
273
+ if (!bridge.supportsBlender()) {
274
+ throw workerUnavailable();
275
+ }
276
+ }, options.signal, options.timeoutMs));
277
+ }
278
+ async run(job, inputs, options) {
279
+ const runStartedAt = Date.now();
280
+ const extraBlobs = await readBlenderOpBlobs();
281
+ try {
282
+ return await this.withBridge(async (bridge) => {
283
+ if (!bridge.supportsBlender()) {
284
+ throw workerUnavailable();
285
+ }
286
+ const remainingTimeoutMs = Math.max(1, options.timeoutMs - (Date.now() - runStartedAt));
287
+ const result = await executeBlender(bridge, job, inputs, {
288
+ timeoutMs: remainingTimeoutMs,
289
+ signal: options.signal,
290
+ onProgress: options.onProgress
291
+ ? (frame, total) => options.onProgress?.(...progressCounts(job, frame, total))
292
+ : undefined,
293
+ maxOutputBytes: options.maxOutputBytes ?? MAX_OUTPUT_BYTES,
294
+ maxTotalOutputBytes: options.maxTotalOutputBytes ?? MAX_TOTAL_OUTPUT_BYTES,
295
+ extraBlobs
296
+ });
297
+ return { outputs: result.outputs, stats: result.stats };
298
+ }, options.signal, Math.max(1, options.timeoutMs - (Date.now() - runStartedAt)));
299
+ }
300
+ catch (err) {
301
+ if (err instanceof BlenderExecutorError) {
302
+ throw workerErrorToJobError(err);
303
+ }
304
+ throw err;
305
+ }
306
+ }
307
+ async withBridge(operation, signal, timeoutMs) {
308
+ const bridge = this.bridge ??
309
+ workerBridgeFactoryForTesting?.() ??
310
+ createConfiguredWorkerBridge();
311
+ const ownsBridge = this.bridge === undefined;
312
+ try {
313
+ if (signal?.aborted) {
314
+ throw signal.reason ?? new Error("Operation aborted.");
315
+ }
316
+ await waitForAbort(bridge.ensureConnected(), signal, timeoutMs);
317
+ return await operation(bridge);
318
+ }
319
+ finally {
320
+ if (ownsBridge)
321
+ bridge.close();
322
+ }
323
+ }
324
+ }
325
+ /** Wait for a bridge connection without leaving a cancelled run pending. */
326
+ async function waitForAbort(promise, signal, timeoutMs) {
327
+ if (signal === undefined && timeoutMs === undefined)
328
+ return promise;
329
+ if (signal?.aborted)
330
+ throw signal.reason ?? new Error("Operation aborted.");
331
+ let abortHandler;
332
+ let timeoutHandle;
333
+ const aborted = signal === undefined
334
+ ? null
335
+ : new Promise((_, reject) => {
336
+ abortHandler = () => reject(signal.reason ?? new Error("Operation aborted."));
337
+ signal.addEventListener("abort", abortHandler, { once: true });
338
+ });
339
+ const timedOut = timeoutMs === undefined
340
+ ? null
341
+ : new Promise((_, reject) => {
342
+ timeoutHandle = setTimeout(() => {
343
+ reject(new BlenderJobError("timeout", `Blender worker connection timed out after ${timeoutMs}ms.`));
344
+ }, Math.max(1, timeoutMs));
345
+ });
346
+ try {
347
+ const racers = [promise];
348
+ if (aborted !== null)
349
+ racers.push(aborted);
350
+ if (timedOut !== null)
351
+ racers.push(timedOut);
352
+ return await Promise.race(racers);
353
+ }
354
+ finally {
355
+ if (abortHandler && signal)
356
+ signal.removeEventListener("abort", abortHandler);
357
+ if (timeoutHandle !== undefined)
358
+ clearTimeout(timeoutHandle);
359
+ }
360
+ }
361
+ function createConfiguredWorkerBridge() {
362
+ if (!process.env["NODETOOL_WORKER_URL"]?.trim()) {
363
+ throw new BlenderJobError("bad_job", "WorkerBlenderRunner requires NODETOOL_WORKER_URL.");
364
+ }
365
+ return createPythonBridge();
366
+ }
367
+ function workerUnavailable() {
368
+ return new BlenderJobError("worker_unavailable", "The NodeTool worker at NODETOOL_WORKER_URL does not report Blender as " +
369
+ "enabled. Install Blender on the worker, or unset NODETOOL_WORKER_URL " +
370
+ "to run Blender locally.");
371
+ }
372
+ function isBlenderJobErrorCode(code) {
373
+ switch (code) {
374
+ case "import_failed":
375
+ case "no_geometry":
376
+ case "no_camera":
377
+ case "unsupported_format":
378
+ case "render_failed":
379
+ case "export_failed":
380
+ case "bake_failed":
381
+ case "bad_job":
382
+ case "bad_result":
383
+ case "missing_output":
384
+ case "output_too_large":
385
+ case "timeout":
386
+ case "worker_unavailable":
387
+ return true;
388
+ default:
389
+ return false;
390
+ }
391
+ }
392
+ function workerErrorToJobError(error) {
393
+ if (isBlenderJobErrorCode(error.code)) {
394
+ return new BlenderJobError(error.code, error.message);
395
+ }
396
+ return new BlenderJobError("bad_result", `Blender worker returned unknown error code "${error.code}": ${error.message}`);
397
+ }
398
+ async function readBlenderOpBlobs() {
399
+ const root = resolveOpScriptDir();
400
+ const blobs = {};
401
+ await collectOpBlobs(root, root, blobs);
402
+ return blobs;
403
+ }
404
+ /** Python's byte-cache directory, written beside the sources it compiles. */
405
+ const PYTHON_BYTECODE_DIR = "__pycache__";
406
+ async function collectOpBlobs(root, dir, blobs) {
407
+ const entries = await readdir(dir, { withFileTypes: true });
408
+ for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
409
+ const file = path.join(dir, entry.name);
410
+ if (entry.isDirectory()) {
411
+ // Bytecode a previous run left behind is not ours to ship: it is keyed
412
+ // to one Python version, and the worker recompiles from the sources.
413
+ if (entry.name === PYTHON_BYTECODE_DIR)
414
+ continue;
415
+ await collectOpBlobs(root, file, blobs);
416
+ continue;
417
+ }
418
+ if (!entry.isFile())
419
+ continue;
420
+ const relativePath = path.relative(root, file).split(path.sep).join("/");
421
+ blobs[relativePath] = new Uint8Array(await readFile(file));
422
+ }
423
+ }
424
+ /**
425
+ * The file a job declares for a logical input name. `BlenderJob.inputs` names
426
+ * exactly one input, so anything else is an undeclared input the caller
427
+ * passed and the runner refuses.
428
+ */
429
+ function declaredInputFile(job, name) {
430
+ return name === "model" ? job.inputs.model : undefined;
431
+ }
432
+ function isTimeout(result) {
433
+ return result.exitCode === 124 && result.stderr.includes("timed out after");
434
+ }
435
+ async function readResult(cwd, stderr, runStartMs) {
436
+ let raw;
437
+ try {
438
+ raw = await readFile(path.join(cwd, "result.json"), "utf8");
439
+ }
440
+ catch {
441
+ throw new BlenderJobError("bad_result", await badResultMessage(cwd, stderr, runStartMs));
442
+ }
443
+ let json;
444
+ try {
445
+ json = JSON.parse(raw);
446
+ }
447
+ catch {
448
+ throw new BlenderJobError("bad_result", await badResultMessage(cwd, stderr, runStartMs));
449
+ }
450
+ const parsed = blenderResultSchema.safeParse(json);
451
+ if (!parsed.success) {
452
+ throw new BlenderJobError("bad_result", await badResultMessage(cwd, stderr, runStartMs));
453
+ }
454
+ const result = parsed.data;
455
+ if (!result.ok) {
456
+ return { ok: false, error: result.error };
457
+ }
458
+ return { ok: true, produced: result.produced, stats: result.stats };
459
+ }
460
+ async function badResultMessage(cwd, stderr, runStartMs) {
461
+ let message = `Blender finished without a parsable result.json. ` +
462
+ `Stderr tail: ${stderrTail(stderr)}`;
463
+ const crash = await readCrashLog(cwd, runStartMs);
464
+ if (crash)
465
+ message += ` Crash log: ${crash}`;
466
+ return message;
467
+ }
468
+ /**
469
+ * Contents of the `.crash.txt` Blender leaves on a segfault, when present.
470
+ *
471
+ * Blender writes `<name>.crash.txt` into its temp directory (`$TMPDIR`,
472
+ * which the env allowlist forwards to the child), never into the run's
473
+ * cwd — a run with no `.blend` file open leaves `blender.crash.txt` there.
474
+ * Both places are checked, the temp directory first, since that is where
475
+ * Blender really writes.
476
+ *
477
+ * Only a log written at or after `runStartMs` counts: the temp directory
478
+ * is shared, so a `blender.crash.txt` from the user's own interactive
479
+ * Blender — or from an earlier run on a shared server — would otherwise be
480
+ * attached to every later `bad_result` for as long as it exists. The first
481
+ * fresh log in sorted order wins.
482
+ */
483
+ async function readCrashLog(cwd, runStartMs) {
484
+ const tmpdir = process.env["TMPDIR"] ?? process.env["TEMP"] ?? process.env["TMP"] ??
485
+ os.tmpdir();
486
+ for (const dir of [tmpdir, cwd]) {
487
+ if (dir === undefined || dir === "")
488
+ continue;
489
+ let entries;
490
+ try {
491
+ entries = await readdir(dir);
492
+ }
493
+ catch {
494
+ continue;
495
+ }
496
+ const crashFiles = entries
497
+ .filter((name) => name.endsWith(".crash.txt"))
498
+ .sort();
499
+ for (const crashFile of crashFiles) {
500
+ const file = path.join(dir, crashFile);
501
+ let mtimeMs;
502
+ try {
503
+ mtimeMs = (await stat(file)).mtimeMs;
504
+ }
505
+ catch {
506
+ continue;
507
+ }
508
+ if (mtimeMs < runStartMs)
509
+ continue;
510
+ try {
511
+ const text = await readFile(file, "utf8");
512
+ return Buffer.from(text, "utf8").subarray(0, STDERR_TAIL_BYTES).toString("utf8");
513
+ }
514
+ catch {
515
+ continue;
516
+ }
517
+ }
518
+ }
519
+ return null;
520
+ }
521
+ //# sourceMappingURL=runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@nodetool-ai/blender-nodes",
3
+ "version": "0.8.1",
4
+ "description": "NodeTool Blender headless nodes: render and mesh preparation over glTF",
5
+ "type": "module",
6
+ "engines": {
7
+ "node": ">=22.0.0"
8
+ },
9
+ "main": "dist/index.js",
10
+ "types": "dist/index.d.ts",
11
+ "scripts": {
12
+ "build": "node ../../scripts/build-typescript-workspace.mjs",
13
+ "test": "node ../../scripts/run-vitest.mjs run",
14
+ "test:watch": "node ../../scripts/run-vitest.mjs",
15
+ "lint": "node ../../scripts/run-tsc.mjs --noEmit"
16
+ },
17
+ "dependencies": {
18
+ "@nodetool-ai/config": "^0.8.1",
19
+ "@nodetool-ai/node-sdk": "^0.8.1",
20
+ "@nodetool-ai/nodes-utils": "^0.8.1",
21
+ "@nodetool-ai/protocol": "^0.8.1",
22
+ "@nodetool-ai/runtime": "^0.8.1",
23
+ "zod": "^4.0.0"
24
+ },
25
+ "devDependencies": {
26
+ "@nodetool-ai/model3d": "*",
27
+ "@nodetool-ai/timeline": "*",
28
+ "@types/node": "^22.0.0",
29
+ "typescript": "npm:@typescript/typescript6@6.0.2",
30
+ "vitest": "^5.0.1"
31
+ },
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/index.d.ts",
35
+ "import": "./dist/index.js",
36
+ "default": "./dist/index.js"
37
+ },
38
+ "./nodes/*": {
39
+ "types": "./dist/nodes/*.d.ts",
40
+ "import": "./dist/nodes/*.js",
41
+ "default": "./dist/nodes/*.js"
42
+ }
43
+ },
44
+ "license": "MIT",
45
+ "files": [
46
+ "dist",
47
+ "blender_ops/**/*.py",
48
+ "README.md"
49
+ ],
50
+ "repository": {
51
+ "type": "git",
52
+ "url": "https://github.com/nodetool-ai/nodetool.git",
53
+ "directory": "packages/blender-nodes"
54
+ },
55
+ "homepage": "https://nodetool.ai",
56
+ "bugs": {
57
+ "url": "https://github.com/nodetool-ai/nodetool/issues"
58
+ },
59
+ "publishConfig": {
60
+ "access": "public"
61
+ },
62
+ "keywords": [
63
+ "nodetool",
64
+ "ai",
65
+ "workflow",
66
+ "workflow-automation",
67
+ "typescript",
68
+ "3d",
69
+ "3d-models",
70
+ "blender",
71
+ "render"
72
+ ]
73
+ }