@volter/blender-engine 0.1.19 → 0.1.21
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/browser/blender-emscripten-engine.mts +2 -4
- package/browser/blender-engine.mts +9 -1
- package/browser/blender-wali-engine.mts +2 -4
- package/browser/protocol.ts +1 -1
- package/browser/runtime.ts +1 -1
- package/browser/three/blender-runtime-view.ts +46 -1
- package/browser/worker.ts +1 -1
- package/package.json +1 -1
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
PAGE_OWNED_DIRECTORIES,
|
|
38
38
|
SESSION_ROOT,
|
|
39
39
|
SESSION_SCRIPT,
|
|
40
|
+
sessionLevel,
|
|
40
41
|
sleep,
|
|
41
42
|
} from './blender-engine.mts';
|
|
42
43
|
import sessionPython from './session.py?raw';
|
|
@@ -195,10 +196,7 @@ export async function startEmscriptenBlenderEngine(
|
|
|
195
196
|
// session's OWN named conditions go there.
|
|
196
197
|
const say = (_level: 'log' | 'error', text: string) => {
|
|
197
198
|
if (text.startsWith('@@VOLTER-READY ')) readyLine = text.slice('@@VOLTER-READY '.length);
|
|
198
|
-
options.log(
|
|
199
|
-
text.startsWith('@@VOLTER-WARN') || text.startsWith('@@VOLTER-ERROR') ? 'error' : 'log',
|
|
200
|
-
text,
|
|
201
|
-
);
|
|
199
|
+
options.log(sessionLevel(text), text);
|
|
202
200
|
};
|
|
203
201
|
const module = await factory({
|
|
204
202
|
arguments: ['--background', '--factory-startup', '--python', SESSION_SCRIPT],
|
|
@@ -107,7 +107,7 @@ export interface BlenderFiles {
|
|
|
107
107
|
export interface BlenderEngineOptions {
|
|
108
108
|
/** The project's absolute path; the engine's filesystem mirrors it there. */
|
|
109
109
|
project: string;
|
|
110
|
-
log(level: 'log' | 'error', text: string): void;
|
|
110
|
+
log(level: 'log' | 'warn' | 'error', text: string): void;
|
|
111
111
|
/** What the session asks the TAB for, mid-call: a frame, and sometimes a
|
|
112
112
|
* photograph of it. Whatever this resolves to is what Python receives.
|
|
113
113
|
*
|
|
@@ -161,6 +161,14 @@ export interface BlenderEngine {
|
|
|
161
161
|
|
|
162
162
|
export const ARTIFACT_BASE = '/__editor/blender-wasm';
|
|
163
163
|
|
|
164
|
+
/** The console level of one line the session printed. Only the session's own named conditions
|
|
165
|
+
* reach the editor's console, each at the severity it names; Blender's streams stay page output. */
|
|
166
|
+
export function sessionLevel(line: string): 'log' | 'warn' | 'error' {
|
|
167
|
+
if (line.startsWith('@@VOLTER-ERROR')) return 'error';
|
|
168
|
+
if (line.startsWith('@@VOLTER-WARN')) return 'warn';
|
|
169
|
+
return 'log';
|
|
170
|
+
}
|
|
171
|
+
|
|
164
172
|
/**
|
|
165
173
|
* An artifact URL, resolved against THE MODULE — never against `self.location`.
|
|
166
174
|
*
|
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
PAGE_OWNED_DIRECTORIES,
|
|
53
53
|
SESSION_ROOT,
|
|
54
54
|
SESSION_SCRIPT,
|
|
55
|
+
sessionLevel,
|
|
55
56
|
sleep,
|
|
56
57
|
} from './blender-engine.mts';
|
|
57
58
|
import sessionPython from './session.py?raw';
|
|
@@ -381,10 +382,7 @@ export async function startWaliBlenderEngine(
|
|
|
381
382
|
// -- the same rule as the standalone engine, and for the same measured
|
|
382
383
|
// reason: only the session's own named conditions belong in the set an
|
|
383
384
|
// agent must drive to zero.
|
|
384
|
-
options.log(
|
|
385
|
-
line.startsWith('@@VOLTER-WARN') || line.startsWith('@@VOLTER-ERROR') ? 'error' : 'log',
|
|
386
|
-
line,
|
|
387
|
-
);
|
|
385
|
+
options.log(sessionLevel(line), line);
|
|
388
386
|
}
|
|
389
387
|
};
|
|
390
388
|
// THE PROGRAM DOES NOT RETURN. `session.py` loops forever, so `run` settles
|
package/browser/protocol.ts
CHANGED
|
@@ -109,7 +109,7 @@ export type WorkerReply =
|
|
|
109
109
|
* as the record of what the session submitted — the second side a
|
|
110
110
|
* displayed-versus-submitted comparison otherwise does not have. */
|
|
111
111
|
| { op: 'present'; id: number; frame: unknown; description: unknown; capture?: CaptureRequest }
|
|
112
|
-
| { op: 'log'; level: 'log' | 'error'; text: string }
|
|
112
|
+
| { op: 'log'; level: 'log' | 'warn' | 'error'; text: string }
|
|
113
113
|
/**
|
|
114
114
|
* HOW BIG THE MODULE'S LINEAR MEMORY IS, posted after every call.
|
|
115
115
|
*
|
package/browser/runtime.ts
CHANGED
|
@@ -44,7 +44,7 @@ export interface BlenderRuntimeOptions {
|
|
|
44
44
|
description: unknown,
|
|
45
45
|
capture?: CaptureRequest,
|
|
46
46
|
): Promise<PresentAnswer> | PresentAnswer;
|
|
47
|
-
log?(level: 'log' | 'error', text: string): void;
|
|
47
|
+
log?(level: 'log' | 'warn' | 'error', text: string): void;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/**
|
|
@@ -744,6 +744,8 @@ export class BlenderRuntimeView {
|
|
|
744
744
|
* showing". Fired after the frame is applied, so a listener that reads
|
|
745
745
|
* the graph sees the new one. */
|
|
746
746
|
private readonly frameListeners = new Set<() => void>();
|
|
747
|
+
/** The views drawing this one's frames in a second area ({@link follow}). */
|
|
748
|
+
private readonly followers = new Set<BlenderRuntimeView>();
|
|
747
749
|
|
|
748
750
|
/** Subscribe to frames. Returns the unsubscribe. */
|
|
749
751
|
subscribeFrames(listener: () => void): () => void {
|
|
@@ -1867,6 +1869,15 @@ export class BlenderRuntimeView {
|
|
|
1867
1869
|
// The model moved: whoever is READING the engine (the Properties sections
|
|
1868
1870
|
// through the RNA door) re-reads now, with the new graph already standing.
|
|
1869
1871
|
for (const listener of [...this.frameListeners]) listener();
|
|
1872
|
+
// EVERY FOLLOWER TAKES THE SAME FRAME, and one that cannot (it lacks geometry the frame only
|
|
1873
|
+
// references) is rebuilt from everything this view now holds.
|
|
1874
|
+
for (const follower of this.followers) {
|
|
1875
|
+
try {
|
|
1876
|
+
follower.applyFrame(input);
|
|
1877
|
+
} catch {
|
|
1878
|
+
follower.applyFrame(this.fullFrame());
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1870
1881
|
return { ...this.inspect(), held };
|
|
1871
1882
|
}
|
|
1872
1883
|
|
|
@@ -1898,7 +1909,10 @@ export class BlenderRuntimeView {
|
|
|
1898
1909
|
* disposable geometry cross this boundary; delta-only frames are sufficient.
|
|
1899
1910
|
* The caller owns this snapshot and must dispose it on every outcome.
|
|
1900
1911
|
*/
|
|
1901
|
-
|
|
1912
|
+
/** Everything this view holds, as ONE self-contained frame another view can be built from:
|
|
1913
|
+
* every mesh's resident geometry and every resident image, with no reference to what a
|
|
1914
|
+
* receiver already holds. */
|
|
1915
|
+
private fullFrame(): Frame {
|
|
1902
1916
|
const source = this.frame;
|
|
1903
1917
|
if (!source) throw new Error('Blender capture requires a presented frame');
|
|
1904
1918
|
// Inspection overlays are not render content. In particular weight-paint
|
|
@@ -1976,6 +1990,37 @@ export class BlenderRuntimeView {
|
|
|
1976
1990
|
};
|
|
1977
1991
|
}
|
|
1978
1992
|
}
|
|
1993
|
+
return frame;
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
/**
|
|
1997
|
+
* A SECOND VIEW OF THE SAME MODEL, for a second area drawing it (Blender's split 3D viewport):
|
|
1998
|
+
* its own graph, because a three.js object has one parent and each area's stage holds its own
|
|
1999
|
+
* root, built from everything this view holds and then handed every frame this one applies, so
|
|
2000
|
+
* the two show one revision. Each view keeps its own shading (Solid in one, Rendered in the
|
|
2001
|
+
* other), its own camera view and its own overlays. Inspection overlays (bones, weights) and a
|
|
2002
|
+
* Timeline pose stay this view's: they are not in a frame another view can be built from.
|
|
2003
|
+
*/
|
|
2004
|
+
follow(): { readonly view: BlenderRuntimeView; dispose(): void } {
|
|
2005
|
+
const follower = new BlenderRuntimeView();
|
|
2006
|
+
if (this.frame) follower.applyFrame(this.fullFrame());
|
|
2007
|
+
this.followers.add(follower);
|
|
2008
|
+
let disposed = false;
|
|
2009
|
+
return {
|
|
2010
|
+
view: follower,
|
|
2011
|
+
dispose: () => {
|
|
2012
|
+
if (disposed) return;
|
|
2013
|
+
disposed = true;
|
|
2014
|
+
this.followers.delete(follower);
|
|
2015
|
+
follower.dispose();
|
|
2016
|
+
follower.root.removeFromParent();
|
|
2017
|
+
},
|
|
2018
|
+
};
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
captureSnapshot() {
|
|
2022
|
+
const frame = this.fullFrame();
|
|
2023
|
+
const source = this.frame!;
|
|
1979
2024
|
const effect = this.createWorldVolumePass();
|
|
1980
2025
|
const detached = new BlenderRuntimeView();
|
|
1981
2026
|
detached.photograph = true;
|
package/browser/worker.ts
CHANGED
|
@@ -41,7 +41,7 @@ import type { CaptureRequest, FileEntry, WorkerReply, WorkerRequest } from './pr
|
|
|
41
41
|
import { columnsToTypedArrays, describeFrame } from './session-frame.mts';
|
|
42
42
|
|
|
43
43
|
const post = (reply: WorkerReply) => (self as unknown as Worker).postMessage(reply);
|
|
44
|
-
const log = (level: 'log' | 'error', text: string) => post({ op: 'log', level, text });
|
|
44
|
+
const log = (level: 'log' | 'warn' | 'error', text: string) => post({ op: 'log', level, text });
|
|
45
45
|
|
|
46
46
|
interface Session {
|
|
47
47
|
start(project: string): Promise<unknown>;
|