@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.
- package/contributions/blender-outliner-authoring.ts +33 -6
- package/contributions/blender-play-skin.ts +285 -186
- package/contributions/blender-pose.ts +621 -0
- package/contributions/blender-runtime-skin.ts +221 -439
- package/contributions/blender-runtime.document.tsx +16 -35
- package/contributions/blender.command.ts +0 -1
- package/host/blender-runtime-host.ts +2 -27
- package/package.json +6 -6
|
@@ -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 {
|
|
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
|
|
914
|
-
//
|
|
915
|
-
//
|
|
916
|
-
void
|
|
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 =
|
|
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
|
|
1060
|
-
*
|
|
1061
|
-
*
|
|
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
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
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
|
|
1460
|
-
//
|
|
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.
|
|
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.
|
|
76
|
-
"@volter/editor-threejs": "0.5.
|
|
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.
|
|
82
|
-
"@volter/editor-project": "0.5.
|
|
83
|
-
"@volter/editor-sdk": "0.5.
|
|
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",
|