@volter/editor-blender 0.1.24 → 0.1.26
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.
|
@@ -54,6 +54,8 @@ import {
|
|
|
54
54
|
type BlenderOutlinerHandle,
|
|
55
55
|
blenderCreatableKinds,
|
|
56
56
|
blenderOutlinerHandle,
|
|
57
|
+
blenderOutlinerHandlesVersion,
|
|
58
|
+
subscribeBlenderOutlinerHandles,
|
|
57
59
|
runBlenderObjectOperator,
|
|
58
60
|
} from './blender-outliner-authoring';
|
|
59
61
|
import { blenderOutlinerVersion, subscribeBlenderOutliner } from './blender-outliner-model';
|
|
@@ -258,6 +260,8 @@ export function BlenderObjectModeHeader({
|
|
|
258
260
|
// The tree is what says which objects exist and which are selected, and both
|
|
259
261
|
// menus read it, so the bar re-renders on the Outliner's own version.
|
|
260
262
|
useSyncExternalStore(subscribeBlenderOutliner, blenderOutlinerVersion, blenderOutlinerVersion);
|
|
263
|
+
// And on the set of live Outliners: the handle this bar acts through may arrive after its first render.
|
|
264
|
+
useSyncExternalStore(subscribeBlenderOutlinerHandles, blenderOutlinerHandlesVersion, blenderOutlinerHandlesVersion);
|
|
261
265
|
const [open, setOpen] = useState<MenuId | null>(null);
|
|
262
266
|
const rootRef = useRef<HTMLDivElement>(null);
|
|
263
267
|
const viewRef = useRef<HTMLDivElement>(null);
|
|
@@ -219,6 +219,31 @@ export interface BlenderOutlinerHandle {
|
|
|
219
219
|
*/
|
|
220
220
|
const liveOutliners = new Set<BlenderOutlinerHandle>();
|
|
221
221
|
|
|
222
|
+
/**
|
|
223
|
+
* A HANDLE ARRIVING IS NEWS. A header drawn before its document's stage made an Outliner has no
|
|
224
|
+
* subject and draws nothing; the tree's own version does not move when a handle registers, so
|
|
225
|
+
* without this it stayed empty until the next edit (measured: a cold load showed no View, Select,
|
|
226
|
+
* Add or Object until a script changed the tree). Notified on the next turn, never inside the
|
|
227
|
+
* render that made the handle.
|
|
228
|
+
*/
|
|
229
|
+
let liveOutlinersVersion = 0;
|
|
230
|
+
const liveOutlinerListeners = new Set<() => void>();
|
|
231
|
+
function liveOutlinersChanged(): void {
|
|
232
|
+
queueMicrotask(() => {
|
|
233
|
+
liveOutlinersVersion++;
|
|
234
|
+
for (const listener of liveOutlinerListeners) listener();
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
export function subscribeBlenderOutlinerHandles(listener: () => void): () => void {
|
|
238
|
+
liveOutlinerListeners.add(listener);
|
|
239
|
+
return () => {
|
|
240
|
+
liveOutlinerListeners.delete(listener);
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
export function blenderOutlinerHandlesVersion(): number {
|
|
244
|
+
return liveOutlinersVersion;
|
|
245
|
+
}
|
|
246
|
+
|
|
222
247
|
/**
|
|
223
248
|
* The Outliner handle for a document, for the header that draws over it: the one its own stage
|
|
224
249
|
* made (a stage's adapter names its document `object3d-document:<id>`).
|
|
@@ -831,24 +856,82 @@ function py(name: string): string {
|
|
|
831
856
|
return JSON.stringify(name);
|
|
832
857
|
}
|
|
833
858
|
|
|
834
|
-
/** Blender's own name for each operator, as its undo history labels the step (`ot->name`). */
|
|
835
|
-
const operatorNames = new Map<string, string>();
|
|
836
|
-
|
|
837
859
|
/**
|
|
838
860
|
* THE STEP'S LABEL IS THE OPERATOR'S OWN NAME, the one Blender's Edit menu shows: Add › Cone is
|
|
839
|
-
* "Undo Add Cone", not "Undo Blender Python".
|
|
840
|
-
*
|
|
861
|
+
* "Undo Add Cone", not "Undo Blender Python". Blender's names do not follow the menu rows (a Point
|
|
862
|
+
* light is "Add Light"), so they are Blender's, read from this build with
|
|
863
|
+
* `bpy.ops.<id>.get_rna_type().name` (`ot->name`) for every operator these menus run and every
|
|
864
|
+
* `object.*_add`. An id not here keeps the script door's label.
|
|
865
|
+
*
|
|
866
|
+
* A TABLE, NOT A QUESTION PER CLICK. Asking Blender first (a history-free execute before the
|
|
867
|
+
* operator) never answered on the browser-substrate page, so Add and Apply did nothing there
|
|
868
|
+
* (measured 2026-09-28 in the hosted tab; why that execute hangs there is not established).
|
|
841
869
|
*/
|
|
842
|
-
|
|
870
|
+
const OPERATOR_NAMES: Readonly<Record<string, string>> = {
|
|
871
|
+
'curve.primitive_bezier_circle_add': "Add B\u00e9zier Circle",
|
|
872
|
+
'curve.primitive_bezier_curve_add': "Add B\u00e9zier",
|
|
873
|
+
'curve.primitive_nurbs_circle_add': "Add Nurbs Circle",
|
|
874
|
+
'curve.primitive_nurbs_curve_add': "Add Nurbs Curve",
|
|
875
|
+
'curve.primitive_nurbs_path_add': "Add Path",
|
|
876
|
+
'image.import_as_mesh_planes': "Import Images as Planes",
|
|
877
|
+
'mesh.primitive_circle_add': "Add Circle",
|
|
878
|
+
'mesh.primitive_cone_add': "Add Cone",
|
|
879
|
+
'mesh.primitive_cube_add': "Add Cube",
|
|
880
|
+
'mesh.primitive_cylinder_add': "Add Cylinder",
|
|
881
|
+
'mesh.primitive_grid_add': "Add Grid",
|
|
882
|
+
'mesh.primitive_ico_sphere_add': "Add Ico Sphere",
|
|
883
|
+
'mesh.primitive_monkey_add': "Add Monkey",
|
|
884
|
+
'mesh.primitive_plane_add': "Add Plane",
|
|
885
|
+
'mesh.primitive_torus_add': "Add Torus",
|
|
886
|
+
'mesh.primitive_uv_sphere_add': "Add UV Sphere",
|
|
887
|
+
'object.armature_add': "Add Armature",
|
|
888
|
+
'object.camera_add': "Add Camera",
|
|
889
|
+
'object.collection_add': "Add to Collection",
|
|
890
|
+
'object.collection_instance_add': "Add Collection Instance",
|
|
891
|
+
'object.constraint_add': "Add Constraint",
|
|
892
|
+
'object.curves_empty_hair_add': "Add Empty Curves",
|
|
893
|
+
'object.curves_random_add': "Add Random Curves",
|
|
894
|
+
'object.data_instance_add': "Add Object Data Instance",
|
|
895
|
+
'object.delete': "Delete",
|
|
896
|
+
'object.duplicate': "Duplicate Objects",
|
|
897
|
+
'object.effector_add': "Add Effector",
|
|
898
|
+
'object.empty_add': "Add Empty",
|
|
899
|
+
'object.empty_image_add': "Add Empty Image/Drop Image to Empty",
|
|
900
|
+
'object.grease_pencil_add': "Add Grease Pencil",
|
|
901
|
+
'object.grease_pencil_dash_modifier_segment_add': "Add Segment",
|
|
902
|
+
'object.grease_pencil_time_modifier_segment_add': "Add Segment",
|
|
903
|
+
'object.lattice_add_to_selected': "Add Lattice Deformer",
|
|
904
|
+
'object.light_add': "Add Light",
|
|
905
|
+
'object.lightprobe_add': "Add Light Probe",
|
|
906
|
+
'object.location_clear': "Clear Location",
|
|
907
|
+
'object.material_slot_add': "Add Material Slot",
|
|
908
|
+
'object.metaball_add': "Add Metaball",
|
|
909
|
+
'object.modifier_add': "Add Modifier",
|
|
910
|
+
'object.origin_set': "Set Origin",
|
|
911
|
+
'object.particle_system_add': "Add Particle System Slot",
|
|
912
|
+
'object.pointcloud_random_add': "Add Point Cloud",
|
|
913
|
+
'object.quick_fur': "Quick Fur",
|
|
914
|
+
'object.rotation_clear': "Clear Rotation",
|
|
915
|
+
'object.scale_clear': "Clear Scale",
|
|
916
|
+
'object.shaderfx_add': "Add Effect",
|
|
917
|
+
'object.shape_key_add': "Add Shape Key",
|
|
918
|
+
'object.speaker_add': "Add Speaker",
|
|
919
|
+
'object.text_add': "Add Text",
|
|
920
|
+
'object.transform_apply': "Apply Object Transform",
|
|
921
|
+
'object.vertex_group_add': "Add Vertex Group",
|
|
922
|
+
'object.volume_add': "Add Volume",
|
|
923
|
+
'object.volume_import': "Import OpenVDB Volume",
|
|
924
|
+
'surface.primitive_nurbs_surface_circle_add': "Add Surface Circle",
|
|
925
|
+
'surface.primitive_nurbs_surface_curve_add': "Add Surface Curve",
|
|
926
|
+
'surface.primitive_nurbs_surface_cylinder_add': "Add Surface Cylinder",
|
|
927
|
+
'surface.primitive_nurbs_surface_sphere_add': "Add Surface Sphere",
|
|
928
|
+
'surface.primitive_nurbs_surface_surface_add': "Add Surface Patch",
|
|
929
|
+
'surface.primitive_nurbs_surface_torus_add': "Add Surface Torus",
|
|
930
|
+
};
|
|
931
|
+
|
|
932
|
+
function operatorLabel(body: string): string | undefined {
|
|
843
933
|
const id = /bpy\.ops\.([a-z_]+\.[a-z0-9_]+)\(/.exec(body)?.[1];
|
|
844
|
-
|
|
845
|
-
const known = operatorNames.get(id);
|
|
846
|
-
if (known !== undefined) return known;
|
|
847
|
-
const answer = await blenderExecute(`import bpy\nprint(bpy.ops.${id}.get_rna_type().name)`, false);
|
|
848
|
-
const name = answer.error === null ? answer.result.trim() : '';
|
|
849
|
-
if (name === '') return 'Blender Python';
|
|
850
|
-
operatorNames.set(id, name);
|
|
851
|
-
return name;
|
|
934
|
+
return id === undefined ? undefined : OPERATOR_NAMES[id];
|
|
852
935
|
}
|
|
853
936
|
|
|
854
937
|
/**
|
|
@@ -877,7 +960,7 @@ async function runBlenderOperator(
|
|
|
877
960
|
'made = [o.name for o in bpy.data.objects if o.name not in before]',
|
|
878
961
|
'print("\\n".join(made))',
|
|
879
962
|
].join('\n');
|
|
880
|
-
const answer = await blenderExecute(code, true, label ??
|
|
963
|
+
const answer = await blenderExecute(code, true, label ?? operatorLabel(body));
|
|
881
964
|
// THE ENGINE'S REFUSAL, VERBATIM. `session.py::execute` answers with the
|
|
882
965
|
// traceback in `error` rather than raising, and a paraphrase here is how a
|
|
883
966
|
// refusal becomes a shrug.
|
|
@@ -1754,6 +1837,7 @@ export function blenderOutlinerAuthoringFor(
|
|
|
1754
1837
|
},
|
|
1755
1838
|
};
|
|
1756
1839
|
liveOutliners.add(handle);
|
|
1840
|
+
liveOutlinersChanged();
|
|
1757
1841
|
|
|
1758
1842
|
// THE ENGINE'S SELECTION ARRIVES ON ITS OWN, and this is what listens for
|
|
1759
1843
|
// it: a frame (every present carries `selected`/`active`) and a tree read
|
|
@@ -1773,6 +1857,7 @@ export function blenderOutlinerAuthoringFor(
|
|
|
1773
1857
|
stopEngineFrames();
|
|
1774
1858
|
stopEngineTree();
|
|
1775
1859
|
liveOutliners.delete(handle);
|
|
1860
|
+
liveOutlinersChanged();
|
|
1776
1861
|
listeners.clear();
|
|
1777
1862
|
},
|
|
1778
1863
|
};
|
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.26",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
8
8
|
},
|
|
@@ -70,14 +70,14 @@
|
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
72
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
73
|
-
"@volter/blender-engine": "0.1.
|
|
74
|
-
"@volter/editor-threejs": "0.5.
|
|
73
|
+
"@volter/blender-engine": "0.1.24",
|
|
74
|
+
"@volter/editor-threejs": "0.5.86",
|
|
75
75
|
"zod": "^4.3.6"
|
|
76
76
|
},
|
|
77
77
|
"peerDependencies": {
|
|
78
|
-
"@volter/editor-live": "0.5.
|
|
79
|
-
"@volter/editor-project": "0.5.
|
|
80
|
-
"@volter/editor-sdk": "0.5.
|
|
78
|
+
"@volter/editor-live": "0.5.86",
|
|
79
|
+
"@volter/editor-project": "0.5.86",
|
|
80
|
+
"@volter/editor-sdk": "0.5.86",
|
|
81
81
|
"react": "^19.0.0",
|
|
82
82
|
"react-dom": "^19.0.0",
|
|
83
83
|
"three": "^0.180.0",
|