@volter/editor-blender 0.1.135 → 0.1.137

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.
@@ -71,14 +71,12 @@ import {
71
71
  modelDocumentMayOpen,
72
72
  modelDocumentOwnsPresentation,
73
73
  blenderActionClip,
74
- blenderArmatureActions,
75
- blenderRig,
76
74
  blenderRnaVersion,
77
75
  } from '../host/blender-runtime-host';
78
76
  import { BlenderObjectModeHeader } from './blender-header-menus';
79
77
  import { blenderOutlinerAuthoringFor, createBlenderOutlinerAuthoring } from './blender-outliner-authoring';
80
78
  import { blenderSkin } from './blender-runtime-skin';
81
- import { bindPlayAnimation, readPlayClipLibrary, type PlayAnimation } from './blender-play-skin';
79
+ import { playAnimation, type PlayAnimation } from './blender-play-skin';
82
80
  import { areaSplit, subscribeAreaSplit } from '../src/area-split';
83
81
  import { noteModelDocument } from '../src/play-mode';
84
82
  import { documentPlayExtension, subscribeDocumentPlayExtensions } from '@volter/editor-sdk/kit/document-play-extension';
@@ -910,19 +908,12 @@ function BlenderViewportArea({
910
908
  preparing = true;
911
909
  orbit = stage.rig().orbit;
912
910
  orbit.enabled = false;
913
- // THE COPY'S CHARACTERS ANIMATE: their skins and every clip they can play, read once for
914
- // this run and bound to the copy (`blender-play-skin.ts`). A read that fails costs the game its
915
- // animation, said in the console, never the game itself.
916
- void Promise.all([
917
- view.prepareRendered(stage.rig().drawCamera(), 'render'),
918
- readPlayClipLibrary().catch((error: unknown) => {
919
- editorHost().console.warn(`The game's animation could not be read: ${error instanceof Error ? error.message : String(error)}`, 'blender-play');
920
- return null;
921
- }),
922
- ]).then(([, library]) => {
911
+ // THE COPY'S CHARACTERS ANIMATE: its view bound their skins from the copied frame, and each
912
+ // armature plays the action the file assigns it until the game sets another
913
+ // (`blender-play-skin.ts`). Nothing is read before the game starts.
914
+ void view.prepareRendered(stage.rig().drawCamera(), 'render').then(() => {
923
915
  if (stopped) return;
924
- animation = library ? bindPlayAnimation(view, library) : null;
925
- for (const warning of animation?.warnings ?? []) editorHost().console.warn(warning, 'blender-play');
916
+ animation = playAnimation(view, (armature, action) => blenderActionClip({ object: armature, action }));
926
917
  stopScript = documentPlayExtension('model')?.run({
927
918
  ...(animation ? { animation } : {}),
928
919
  documentId: modelId,
@@ -1056,31 +1047,21 @@ function BlenderViewportArea({
1056
1047
  };
1057
1048
  }, [documentId, view]);
1058
1049
  /**
1059
- * THE MODEL'S SKINS AND ASSIGNED ACTION, AS BLENDER'S VIEWPORT SHOWS THEM: a rigged object is
1060
- * presented skinned, whatever editors are open, and plays the action assigned to it. Bound on
1061
- * every change of the file, a moment after it settles, because `rna_rig` walks every vertex of
1062
- * every rigged mesh; a file with no armature pays only the cheap listing.
1050
+ * THE ASSIGNED ACTION ON THE PRESENTED SKELETONS. The skins themselves come with every frame (the
1051
+ * presenter binds them, `blender-runtime-skeleton.ts`); after each present the director follows
1052
+ * the frame, and reads a clip only when the frame says its action, revision or clock moved.
1063
1053
  */
1064
1054
  const rnaVersion = useSyncExternalStore(subscribeBlenderRna, blenderRnaVersion, blenderRnaVersion);
1065
- /** The armatures, their actions and what each is assigned, as last bound: an edit that changes
1066
- * none of them and replaced no skinned mesh costs no rig read and no bake. */
1067
- const boundListing = useRef<string | null>(null);
1068
1055
  useEffect(() => {
1069
1056
  if (!documentId || !main) return;
1070
1057
  let live = true;
1071
- const timer = setTimeout(() => {
1072
- void (async () => {
1073
- const listing = await blenderArmatureActions();
1074
- if (!live || !listing?.armatures.length) return;
1075
- const key = JSON.stringify([listing.armatures, listing.scene ?? null]);
1076
- if (key === boundListing.current && !blenderSkin.stale(view)) return;
1077
- await blenderSkin.bind(view, { rig: () => blenderRig(), clip: () => blenderActionClip() });
1078
- boundListing.current = key;
1079
- })().catch((thrown: unknown) => {
1080
- if (live) editorHost().console.warn(`This file's rig could not be read: ${thrown instanceof Error ? thrown.message : String(thrown)}`, 'blender-skin');
1081
- });
1082
- }, 400);
1083
- return () => { live = false; clearTimeout(timer); };
1058
+ void blenderSkin.sync(view, {
1059
+ clip: (armature) => blenderActionClip({ object: armature }),
1060
+ bake: (armature, action) => blenderActionClip({ object: armature, action }),
1061
+ }).catch((thrown: unknown) => {
1062
+ if (live) editorHost().console.warn(`This file's action could not be read: ${thrown instanceof Error ? thrown.message : String(thrown)}`, 'blender-skin');
1063
+ });
1064
+ return () => { live = false; };
1084
1065
  }, [documentId, main, view, rnaVersion]);
1085
1066
  /**
1086
1067
  * ATTACH THE SKIN TO THIS STAGE'S TRANSPORT.
@@ -93,7 +93,6 @@ export const commands: CommandContribution['commands'] = {
93
93
  // and the ONE write the Timeline makes (`scene.frame_current`, once on
94
94
  // pause and at scrub-end) goes through `blender-rna-set`, which already
95
95
  // exists and already presents.
96
- 'blender-rig': verb('none', 60_000),
97
96
  'blender-action-clip': verb('none', 120_000),
98
97
  // THE NODE VIEW'S OWN ACTIONS as a session verb — the keyboard ruling's
99
98
  // `volter.*`-command-per-action pattern, and the only way the drawer's view
@@ -36,7 +36,6 @@ import type {
36
36
  BlenderNodeTree,
37
37
  BlenderOutlinerTree,
38
38
  BlenderOutlinerWrite,
39
- BlenderRig,
40
39
  BlenderRnaContext,
41
40
  BlenderRnaView,
42
41
  BlenderRnaWrite,
@@ -661,15 +660,6 @@ export async function blenderUvLayout(options?: {
661
660
  return blenderRuntime().uvLayout(options);
662
661
  }
663
662
 
664
- /** THE RIG DOOR, in-page — one mesh's skin binding, which the presenter turns
665
- * into a `THREE.SkinnedMesh` (`../contributions/blender-runtime-skin.ts`).
666
- * WE VISUALIZE WITH THREE.JS, NOT BLENDER (owner rule, 2026-09-20): this is
667
- * called once per rig, never per played frame. Same rule as the doors above:
668
- * it never starts the engine. */
669
- export async function blenderRig(options?: { object?: string }): Promise<BlenderRig | null> {
670
- if (!blenderSessionStarted()) return null;
671
- return blenderRuntime().rig(options);
672
- }
673
663
 
674
664
  /** THE CLIP DOOR, in-page — one action as three.js keyframe tracks plus the
675
665
  * scene's frame range and the Timeline's summary columns. Called when the
@@ -684,11 +674,6 @@ export async function blenderActionClip(options?: {
684
674
  return blenderRuntime().actionClip(options);
685
675
  }
686
676
 
687
- /** Every armature and the actions that can play on it, for Play's clip library. Never starts the engine. */
688
- export async function blenderArmatureActions(): Promise<{ armatures: { armature: string; current: string | null; actions: string[]; keys?: string }[]; scene?: { frameCurrent: number; frameStart: number; frameEnd: number; fps: number } } | null> {
689
- if (!blenderSessionStarted()) return null;
690
- return blenderRuntime().armatureActions();
691
- }
692
677
 
693
678
  export async function blenderOutlinerSet(
694
679
  path: string,
@@ -1456,18 +1441,8 @@ export async function handleBlenderCommand(cmd: {
1456
1441
  }),
1457
1442
  },
1458
1443
  };
1459
- // THE RIG AND CLIP DOORS on the wire, beside the UV one: the skin
1460
- // binding and the action as three.js tracks, so a `cyclotron eval` reads
1461
- // exactly what the presenter bound and what the Timeline plays.
1462
- case 'blender-rig':
1463
- return {
1464
- ok: true,
1465
- data: {
1466
- result: await session.rig({
1467
- ...(typeof cmd['object'] === 'string' ? { object: cmd['object'] } : {}),
1468
- }),
1469
- },
1470
- };
1444
+ // THE CLIP DOOR on the wire, beside the UV one: an action as three.js tracks, so a
1445
+ // `cyclotron eval` reads exactly what the Timeline and a game play.
1471
1446
  case 'blender-action-clip':
1472
1447
  return {
1473
1448
  ok: true,
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.135",
5
+ "version": "0.1.137",
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.133",
76
- "@volter/editor-threejs": "0.5.196",
75
+ "@volter/blender-engine": "0.1.135",
76
+ "@volter/editor-threejs": "0.5.198",
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.196",
82
- "@volter/editor-project": "0.5.196",
83
- "@volter/editor-sdk": "0.5.196",
81
+ "@volter/editor-live": "0.5.198",
82
+ "@volter/editor-project": "0.5.198",
83
+ "@volter/editor-sdk": "0.5.198",
84
84
  "react": "^19.0.0",
85
85
  "react-dom": "^19.0.0",
86
86
  "three": "^0.180.0",