@volter/editor-blender 0.1.133 → 0.1.134

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.
@@ -43,7 +43,7 @@ import type {
43
43
  BlenderUvLayout,
44
44
  } from '@volter/blender-engine/browser/rna';
45
45
  import { createBlenderDisplayTransform, blenderDisplaySettingsForRender } from '@volter/blender-engine/browser/three/blender-display-transform';
46
- import type { BlenderRuntimeView } from '@volter/blender-engine/browser/three/blender-runtime-view';
46
+ import { BlenderRuntimeView } from '@volter/blender-engine/browser/three/blender-runtime-view';
47
47
  import type { EditorCommandResult } from '@volter/editor-sdk/commands';
48
48
  import { editorHost } from '@volter/editor-sdk/host';
49
49
  import { invokeViewVerb, type ViewVerbContribution } from '@volter/editor-sdk/views';
@@ -821,6 +821,7 @@ function driveNodeView(cmd: { type: string; [key: string]: unknown }): NodeViewS
821
821
  }
822
822
 
823
823
  interface RuntimeView {
824
+ readonly root: THREE.Group;
824
825
  stageFrame(part: { session: string; revision: number; abort?: boolean; mesh?: string; image?: string; piece?: unknown }): void;
825
826
  applyFrame(frame: unknown): unknown;
826
827
  snapshot(): ReturnType<BlenderRuntimeView['snapshot']>;
@@ -903,6 +904,7 @@ const isRuntimeView = (value: unknown): value is RuntimeView =>
903
904
  typeof (value as RuntimeView).applyFrame === 'function' &&
904
905
  typeof (value as RuntimeView).stageFrame === 'function' &&
905
906
  typeof (value as RuntimeView).snapshot === 'function' &&
907
+ (value as RuntimeView).root?.isObject3D === true &&
906
908
  typeof (value as RuntimeView).captureSnapshot === 'function' &&
907
909
  typeof (value as RuntimeView).recordPresentation === 'function' &&
908
910
  typeof (value as RuntimeView).recordPhotograph === 'function';
@@ -951,11 +953,20 @@ export function blenderRuntime(): BlenderRuntime {
951
953
  captureLifetime = lifetime;
952
954
  let photographing = false;
953
955
  let streamedFrame: { view: RuntimeView; session: string; revision: number } | null = null;
956
+ let renderFrame: { view: BlenderRuntimeView; source: RuntimeView; sourceFrame: ReturnType<RuntimeView['snapshot']>; documentId: string; session: string; revision: number } | null = null;
957
+ const discardRenderFrame = () => {
958
+ const owned = renderFrame;
959
+ renderFrame = null;
960
+ owned?.view.dispose();
961
+ owned?.view.root.removeFromParent();
962
+ };
954
963
  const discardStreamedFrame = () => {
955
964
  const staged = streamedFrame;
956
965
  streamedFrame = null;
957
966
  if (staged) staged.view.stageFrame({ session: staged.session, revision: staged.revision, abort: true });
967
+ discardRenderFrame();
958
968
  };
969
+ lifetime.signal.addEventListener('abort', discardStreamedFrame, { once: true });
959
970
  runtime = new BlenderRuntime({
960
971
  work: beginBlenderWork,
961
972
  history: (entries) => {
@@ -997,6 +1008,20 @@ export function blenderRuntime(): BlenderRuntime {
997
1008
  const view = await runtimeView();
998
1009
  lifetime.signal.throwIfAborted();
999
1010
  if (presentationDocumentId() !== documentId) throw new Error('Blender document changed during frame transfer');
1011
+ if (part.evaluation === 'render') {
1012
+ if (!renderFrame) {
1013
+ if (part.mesh !== undefined || part.image !== undefined) throw new Error('Render resource arrived before its frame');
1014
+ const detached = BlenderRuntimeView.forPhotograph();
1015
+ view.root.updateWorldMatrix(true, false);
1016
+ detached.root.matrix.copy(view.root.matrixWorld);
1017
+ renderFrame = { view: detached, source: view, sourceFrame: view.snapshot(), documentId, session: part.session, revision: part.revision };
1018
+ }
1019
+ if (renderFrame.source !== view || renderFrame.sourceFrame !== view.snapshot() || renderFrame.documentId !== documentId || renderFrame.session !== part.session || renderFrame.revision !== part.revision)
1020
+ throw new Error('Blender capture source changed during render transfer');
1021
+ renderFrame.view.stageFrame(part);
1022
+ return;
1023
+ }
1024
+ if (renderFrame) throw new Error('Viewport frame arrived during render transfer');
1000
1025
  if (streamedFrame && streamedFrame.view !== view)
1001
1026
  throw new Error('Blender presenter changed during frame transfer');
1002
1027
  view.stageFrame(part);
@@ -1027,6 +1052,12 @@ export function blenderRuntime(): BlenderRuntime {
1027
1052
  throw new Error('Blender document changed before its frame could be presented');
1028
1053
  if (streamedFrame && streamedFrame.view !== view)
1029
1054
  throw new Error('Blender presenter changed before its frame could be presented');
1055
+ const rendering = capture?.evaluation === 'render' ? renderFrame : null;
1056
+ if (capture?.evaluation === 'render' && !rendering)
1057
+ throw new Error('Render-evaluated frame arrived without its staged resources');
1058
+ if (rendering && (rendering.source !== view || rendering.sourceFrame !== view.snapshot() || rendering.documentId !== documentId))
1059
+ throw new Error('Blender capture source changed before render presentation');
1060
+ const presenter = rendering?.view ?? view;
1030
1061
  // WHAT THE PRESENTER HELD BEFORE THIS FRAME, carried back to the session
1031
1062
  // beside whatever this present produced. The session only has a RECORD of
1032
1063
  // what it sent; this view is the authority on what it actually holds, and
@@ -1036,7 +1067,7 @@ export function blenderRuntime(): BlenderRuntime {
1036
1067
  // all, rather than a wrong `null`.
1037
1068
  const endApply = beginBlenderWork('applying frame to Model');
1038
1069
  const applied = (() => {
1039
- try { return view.applyFrame(frame) as { held?: unknown } | null | undefined; }
1070
+ try { return presenter.applyFrame(frame) as { held?: unknown } | null | undefined; }
1040
1071
  finally { endApply(); }
1041
1072
  })();
1042
1073
  streamedFrame = null;
@@ -1049,7 +1080,7 @@ export function blenderRuntime(): BlenderRuntime {
1049
1080
  // THE RECORD OF WHAT WAS SUBMITTED, kept only once the frame was taken:
1050
1081
  // a refused frame is not displayed and must not be described as if it
1051
1082
  // were.
1052
- view.recordPresentation(description);
1083
+ presenter.recordPresentation(description);
1053
1084
  if (!capture) return answer(undefined);
1054
1085
  lastCapture = capture;
1055
1086
  if (!capture.render) return answer(undefined);
@@ -1091,7 +1122,7 @@ export function blenderRuntime(): BlenderRuntime {
1091
1122
  'A Blender render capture must carry the scene camera position, target and up',
1092
1123
  );
1093
1124
  if (photographing) throw new Error('A Blender render capture is already in progress');
1094
- const snapshot = view.captureSnapshot();
1125
+ const snapshot = rendering ? rendering.view.takeCaptureSnapshot() : view.captureSnapshot();
1095
1126
  photographing = true;
1096
1127
  lifetime.signal.addEventListener('abort', snapshot.dispose, { once: true });
1097
1128
  try {
@@ -1150,6 +1181,7 @@ export function blenderRuntime(): BlenderRuntime {
1150
1181
  photographing = false;
1151
1182
  lifetime.signal.removeEventListener('abort', snapshot.dispose);
1152
1183
  snapshot.dispose();
1184
+ if (rendering && renderFrame === rendering) renderFrame = null;
1153
1185
  }
1154
1186
  },
1155
1187
  // The worker's OWN stdout and stderr, forwarded so a developer can read
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@volter/editor-blender",
3
3
  "author": "Volter AI, Inc.",
4
4
  "license": "AGPL-3.0-only AND GPL-3.0-or-later",
5
- "version": "0.1.133",
5
+ "version": "0.1.134",
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },
@@ -72,15 +72,15 @@
72
72
  },
73
73
  "dependencies": {
74
74
  "@modelcontextprotocol/sdk": "1.32.1",
75
- "@volter/blender-engine": "0.1.131",
76
- "@volter/editor-threejs": "0.5.194",
75
+ "@volter/blender-engine": "0.1.132",
76
+ "@volter/editor-threejs": "0.5.195",
77
77
  "zod": "^4.3.6",
78
78
  "@fortawesome/free-solid-svg-icons": "^7.2.0"
79
79
  },
80
80
  "peerDependencies": {
81
- "@volter/editor-live": "0.5.194",
82
- "@volter/editor-project": "0.5.194",
83
- "@volter/editor-sdk": "0.5.194",
81
+ "@volter/editor-live": "0.5.195",
82
+ "@volter/editor-project": "0.5.195",
83
+ "@volter/editor-sdk": "0.5.195",
84
84
  "react": "^19.0.0",
85
85
  "react-dom": "^19.0.0",
86
86
  "three": "^0.180.0",