@volter/blender-engine 0.1.147 → 0.1.149

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.
Files changed (245) hide show
  1. package/LICENSE +16 -0
  2. package/README.md +4 -11
  3. package/browser/addons/vrm/LICENSE_(OPTION1)_MIT.txt +22 -0
  4. package/browser/addons/vrm/LICENSE_(OPTION2)_GPL-3_0.txt +674 -0
  5. package/browser/addons/vrm/LICENSE_MAIN.txt +1 -0
  6. package/browser/addons/vrm/README.txt +3 -0
  7. package/browser/addons/vrm/__init__.py +41 -0
  8. package/browser/addons/vrm/blender_manifest.toml +16 -0
  9. package/browser/addons/vrm/common/__init__.py +1 -0
  10. package/browser/addons/vrm/common/animation.py +58 -0
  11. package/browser/addons/vrm/common/blender_manifest.py +82 -0
  12. package/browser/addons/vrm/common/char.py +16 -0
  13. package/browser/addons/vrm/common/convert.py +287 -0
  14. package/browser/addons/vrm/common/convert_any.py +48 -0
  15. package/browser/addons/vrm/common/debug.py +40 -0
  16. package/browser/addons/vrm/common/deep.py +162 -0
  17. package/browser/addons/vrm/common/error_dialog.py +274 -0
  18. package/browser/addons/vrm/common/fs.py +71 -0
  19. package/browser/addons/vrm/common/gl.py +20 -0
  20. package/browser/addons/vrm/common/gltf.py +1149 -0
  21. package/browser/addons/vrm/common/human_bone_mapper/__init__.py +1 -0
  22. package/browser/addons/vrm/common/human_bone_mapper/biped_mapping.py +84 -0
  23. package/browser/addons/vrm/common/human_bone_mapper/cats_blender_plugin_fix_model_mapping.py +63 -0
  24. package/browser/addons/vrm/common/human_bone_mapper/human_bone_mapper.py +203 -0
  25. package/browser/addons/vrm/common/human_bone_mapper/microsoft_rocketbox_mapping.py +69 -0
  26. package/browser/addons/vrm/common/human_bone_mapper/mixamo_mapping.py +62 -0
  27. package/browser/addons/vrm/common/human_bone_mapper/mmd_mapping.py +115 -0
  28. package/browser/addons/vrm/common/human_bone_mapper/mpfb_mapping.py +120 -0
  29. package/browser/addons/vrm/common/human_bone_mapper/ready_player_me_mapping.py +65 -0
  30. package/browser/addons/vrm/common/human_bone_mapper/reallusion_mapping.py +94 -0
  31. package/browser/addons/vrm/common/human_bone_mapper/rigify_meta_rig_mapping.py +65 -0
  32. package/browser/addons/vrm/common/human_bone_mapper/structure_based_mapping.py +891 -0
  33. package/browser/addons/vrm/common/human_bone_mapper/unreal_mapping.py +62 -0
  34. package/browser/addons/vrm/common/human_bone_mapper/vrm_addon_mapping.py +112 -0
  35. package/browser/addons/vrm/common/human_bone_mapper/vroid_mapping.py +89 -0
  36. package/browser/addons/vrm/common/legacy_gltf.py +16 -0
  37. package/browser/addons/vrm/common/logger.py +84 -0
  38. package/browser/addons/vrm/common/mtoon0.blend +0 -0
  39. package/browser/addons/vrm/common/mtoon1.blend +0 -0
  40. package/browser/addons/vrm/common/mtoon1_outline.blend +0 -0
  41. package/browser/addons/vrm/common/mtoon_auto_setup.blend +0 -0
  42. package/browser/addons/vrm/common/mtoon_unversioned.py +62 -0
  43. package/browser/addons/vrm/common/ops/__init__.py +10 -0
  44. package/browser/addons/vrm/common/ops/export_scene.py +67 -0
  45. package/browser/addons/vrm/common/ops/icyp.py +53 -0
  46. package/browser/addons/vrm/common/ops/import_scene.py +57 -0
  47. package/browser/addons/vrm/common/ops/vrm.py +2380 -0
  48. package/browser/addons/vrm/common/ops/wm.py +167 -0
  49. package/browser/addons/vrm/common/preferences.py +362 -0
  50. package/browser/addons/vrm/common/progress.py +87 -0
  51. package/browser/addons/vrm/common/rotation.py +89 -0
  52. package/browser/addons/vrm/common/safe_removal.py +46 -0
  53. package/browser/addons/vrm/common/scene_watcher.py +230 -0
  54. package/browser/addons/vrm/common/shader.py +2243 -0
  55. package/browser/addons/vrm/common/shape_key_mapper/__init__.py +1 -0
  56. package/browser/addons/vrm/common/shape_key_mapper/arkit_mapping.py +168 -0
  57. package/browser/addons/vrm/common/shape_key_mapper/mmd_mapping.py +62 -0
  58. package/browser/addons/vrm/common/shape_key_mapper/ready_player_me_mapping.py +52 -0
  59. package/browser/addons/vrm/common/shape_key_mapper/vrchat_mapping.py +17 -0
  60. package/browser/addons/vrm/common/third_party_user_extension.py +214 -0
  61. package/browser/addons/vrm/common/transparent_z_write.blend +0 -0
  62. package/browser/addons/vrm/common/version.py +161 -0
  63. package/browser/addons/vrm/common/vrm0/__init__.py +1 -0
  64. package/browser/addons/vrm/common/vrm0/human_bone.py +662 -0
  65. package/browser/addons/vrm/common/vrm0/material_property.py +346 -0
  66. package/browser/addons/vrm/common/vrm1/__init__.py +1 -0
  67. package/browser/addons/vrm/common/vrm1/expression_preset.py +207 -0
  68. package/browser/addons/vrm/common/vrm1/human_bone.py +822 -0
  69. package/browser/addons/vrm/common/workspace.py +227 -0
  70. package/browser/addons/vrm/common/writable_context.py +77 -0
  71. package/browser/addons/vrm/documentation/en-us/animation/index.md +63 -0
  72. package/browser/addons/vrm/documentation/en-us/create-humanoid-vrm-from-scratch/index.md +131 -0
  73. package/browser/addons/vrm/documentation/en-us/create-simple-vrm-from-scratch/index.md +30 -0
  74. package/browser/addons/vrm/documentation/en-us/development/index.md +136 -0
  75. package/browser/addons/vrm/documentation/en-us/index.md +41 -0
  76. package/browser/addons/vrm/documentation/en-us/installation/index.md +152 -0
  77. package/browser/addons/vrm/documentation/en-us/material-mtoon/index.md +122 -0
  78. package/browser/addons/vrm/documentation/en-us/material-pbr/index.md +94 -0
  79. package/browser/addons/vrm/documentation/en-us/scripting-api/index.md +391 -0
  80. package/browser/addons/vrm/documentation/en-us/ui/export_scene.vrm/index.md +57 -0
  81. package/browser/addons/vrm/documentation/en-us/ui/export_scene.vrma/index.md +17 -0
  82. package/browser/addons/vrm/documentation/en-us/ui/import_scene.vrm/index.md +61 -0
  83. package/browser/addons/vrm/documentation/en-us/ui/import_scene.vrma/index.md +13 -0
  84. package/browser/addons/vrm/documentation/ja-jp/animation/index.md +57 -0
  85. package/browser/addons/vrm/documentation/ja-jp/create-humanoid-vrm-from-scratch/index.md +129 -0
  86. package/browser/addons/vrm/documentation/ja-jp/create-simple-vrm-from-scratch/index.md +29 -0
  87. package/browser/addons/vrm/documentation/ja-jp/development/index.md +137 -0
  88. package/browser/addons/vrm/documentation/ja-jp/index.md +37 -0
  89. package/browser/addons/vrm/documentation/ja-jp/installation/index.md +150 -0
  90. package/browser/addons/vrm/documentation/ja-jp/material-mtoon/index.md +113 -0
  91. package/browser/addons/vrm/documentation/ja-jp/material-pbr/index.md +90 -0
  92. package/browser/addons/vrm/documentation/ja-jp/scripting-api/index.md +389 -0
  93. package/browser/addons/vrm/documentation/ja-jp/ui/export_scene.vrm/index.md +55 -0
  94. package/browser/addons/vrm/documentation/ja-jp/ui/export_scene.vrma/index.md +19 -0
  95. package/browser/addons/vrm/documentation/ja-jp/ui/import_scene.vrm/index.md +56 -0
  96. package/browser/addons/vrm/documentation/ja-jp/ui/import_scene.vrma/index.md +14 -0
  97. package/browser/addons/vrm/editor/__init__.py +1 -0
  98. package/browser/addons/vrm/editor/extension.py +425 -0
  99. package/browser/addons/vrm/editor/extension_accessor.py +75 -0
  100. package/browser/addons/vrm/editor/handler.py +84 -0
  101. package/browser/addons/vrm/editor/khr_character/__init__.py +1 -0
  102. package/browser/addons/vrm/editor/khr_character/panel.py +69 -0
  103. package/browser/addons/vrm/editor/khr_character/property_group.py +27 -0
  104. package/browser/addons/vrm/editor/khr_xmp_json_ld/__init__.py +1 -0
  105. package/browser/addons/vrm/editor/khr_xmp_json_ld/ops.py +464 -0
  106. package/browser/addons/vrm/editor/khr_xmp_json_ld/panel.py +136 -0
  107. package/browser/addons/vrm/editor/khr_xmp_json_ld/property_group.py +83 -0
  108. package/browser/addons/vrm/editor/khr_xmp_json_ld/ui_list.py +198 -0
  109. package/browser/addons/vrm/editor/make_armature.py +709 -0
  110. package/browser/addons/vrm/editor/menu.py +147 -0
  111. package/browser/addons/vrm/editor/migration.py +297 -0
  112. package/browser/addons/vrm/editor/mtoon1/__init__.py +1 -0
  113. package/browser/addons/vrm/editor/mtoon1/handler.py +20 -0
  114. package/browser/addons/vrm/editor/mtoon1/migration.py +536 -0
  115. package/browser/addons/vrm/editor/mtoon1/ops.py +304 -0
  116. package/browser/addons/vrm/editor/mtoon1/panel.py +442 -0
  117. package/browser/addons/vrm/editor/mtoon1/property_group.py +4977 -0
  118. package/browser/addons/vrm/editor/mtoon1/scene_watcher.py +381 -0
  119. package/browser/addons/vrm/editor/node_constraint1/__init__.py +1 -0
  120. package/browser/addons/vrm/editor/node_constraint1/panel.py +317 -0
  121. package/browser/addons/vrm/editor/node_constraint1/property_group.py +25 -0
  122. package/browser/addons/vrm/editor/ops.py +471 -0
  123. package/browser/addons/vrm/editor/panel.py +315 -0
  124. package/browser/addons/vrm/editor/property_group.py +888 -0
  125. package/browser/addons/vrm/editor/search.py +682 -0
  126. package/browser/addons/vrm/editor/spring_bone1/__init__.py +1 -0
  127. package/browser/addons/vrm/editor/spring_bone1/handler.py +994 -0
  128. package/browser/addons/vrm/editor/spring_bone1/menu.py +246 -0
  129. package/browser/addons/vrm/editor/spring_bone1/migration.py +86 -0
  130. package/browser/addons/vrm/editor/spring_bone1/ops.py +1679 -0
  131. package/browser/addons/vrm/editor/spring_bone1/panel.py +605 -0
  132. package/browser/addons/vrm/editor/spring_bone1/property_group.py +1457 -0
  133. package/browser/addons/vrm/editor/spring_bone1/ui_list.py +408 -0
  134. package/browser/addons/vrm/editor/subscription.py +123 -0
  135. package/browser/addons/vrm/editor/t_pose.py +713 -0
  136. package/browser/addons/vrm/editor/validation.py +1271 -0
  137. package/browser/addons/vrm/editor/vrm0/__init__.py +1 -0
  138. package/browser/addons/vrm/editor/vrm0/gizmo_group.py +63 -0
  139. package/browser/addons/vrm/editor/vrm0/handler.py +23 -0
  140. package/browser/addons/vrm/editor/vrm0/menu.py +145 -0
  141. package/browser/addons/vrm/editor/vrm0/migration.py +761 -0
  142. package/browser/addons/vrm/editor/vrm0/ops.py +2145 -0
  143. package/browser/addons/vrm/editor/vrm0/panel.py +1333 -0
  144. package/browser/addons/vrm/editor/vrm0/property_group.py +1818 -0
  145. package/browser/addons/vrm/editor/vrm0/ui_list.py +688 -0
  146. package/browser/addons/vrm/editor/vrm1/__init__.py +1 -0
  147. package/browser/addons/vrm/editor/vrm1/handler.py +28 -0
  148. package/browser/addons/vrm/editor/vrm1/menu.py +76 -0
  149. package/browser/addons/vrm/editor/vrm1/migration.py +321 -0
  150. package/browser/addons/vrm/editor/vrm1/ops.py +2679 -0
  151. package/browser/addons/vrm/editor/vrm1/panel.py +1243 -0
  152. package/browser/addons/vrm/editor/vrm1/property_group.py +2087 -0
  153. package/browser/addons/vrm/editor/vrm1/scene_watcher.py +90 -0
  154. package/browser/addons/vrm/editor/vrm1/ui_list.py +533 -0
  155. package/browser/addons/vrm/exporter/__init__.py +1 -0
  156. package/browser/addons/vrm/exporter/abstract_base_vrm_exporter.py +628 -0
  157. package/browser/addons/vrm/exporter/export_scene.py +930 -0
  158. package/browser/addons/vrm/exporter/gltf2_export_user_extension.py +45 -0
  159. package/browser/addons/vrm/exporter/khr_character_exporter.py +34 -0
  160. package/browser/addons/vrm/exporter/vrm0_exporter.py +4020 -0
  161. package/browser/addons/vrm/exporter/vrm1_exporter.py +3682 -0
  162. package/browser/addons/vrm/exporter/vrm_animation_exporter.py +1035 -0
  163. package/browser/addons/vrm/external/__init__.py +1 -0
  164. package/browser/addons/vrm/external/auto_rig_pro_support.py +3 -0
  165. package/browser/addons/vrm/external/io_scene_gltf2_support.py +418 -0
  166. package/browser/addons/vrm/external/rigify_support.py +1 -0
  167. package/browser/addons/vrm/importer/__init__.py +1 -0
  168. package/browser/addons/vrm/importer/abstract_base_vrm_importer.py +1548 -0
  169. package/browser/addons/vrm/importer/file_handler.py +34 -0
  170. package/browser/addons/vrm/importer/gltf2_import_user_extension.py +268 -0
  171. package/browser/addons/vrm/importer/import_scene.py +646 -0
  172. package/browser/addons/vrm/importer/license_validation.py +198 -0
  173. package/browser/addons/vrm/importer/vrm0_importer.py +1723 -0
  174. package/browser/addons/vrm/importer/vrm1_importer.py +2014 -0
  175. package/browser/addons/vrm/importer/vrm_animation_importer.py +763 -0
  176. package/browser/addons/vrm/importer/vrm_diff.py +316 -0
  177. package/browser/addons/vrm/locale/__init__.py +1 -0
  178. package/browser/addons/vrm/locale/ja_jp.py +826 -0
  179. package/browser/addons/vrm/locale/ruff.toml +8 -0
  180. package/browser/addons/vrm/locale/translation_dictionary.py +10 -0
  181. package/browser/addons/vrm/locale/zh_hans.py +928 -0
  182. package/browser/addons/vrm/registration.py +670 -0
  183. package/browser/addons/vrm.json +18 -0
  184. package/browser/blender-emscripten-engine.mts +60 -128
  185. package/browser/blender-engine.mts +20 -186
  186. package/browser/blender-wali-engine.mts +35 -81
  187. package/browser/describe-thrown.mts +3 -6
  188. package/browser/document-chunks.mts +4 -16
  189. package/browser/gltf_export.py +3161 -0
  190. package/browser/heap-growth.mts +23 -0
  191. package/browser/index.ts +2 -0
  192. package/browser/protocol.ts +119 -35
  193. package/browser/pull-job.mts +2 -10
  194. package/browser/rna.ts +190 -73
  195. package/browser/runtime.ts +197 -82
  196. package/browser/session-addons.mts +33 -0
  197. package/browser/session-frame.mts +5 -24
  198. package/browser/session.py +1338 -1427
  199. package/browser/three/attach-presenter.ts +11 -39
  200. package/browser/three/blender-base64.ts +1 -36
  201. package/browser/three/blender-corner-normals.ts +1 -23
  202. package/browser/three/blender-display-shaders.json +1 -1
  203. package/browser/three/blender-display-shaders.lut +0 -0
  204. package/browser/three/blender-display-transform.ts +2 -1
  205. package/browser/three/blender-frame-columns.ts +5 -13
  206. package/browser/three/blender-graph-material.ts +18 -3
  207. package/browser/three/blender-node-graph.ts +10 -34
  208. package/browser/three/blender-physical-material.ts +2 -31
  209. package/browser/three/blender-runtime-armature.ts +7 -60
  210. package/browser/three/blender-runtime-frame.ts +9 -23
  211. package/browser/three/blender-runtime-geometry.ts +22 -26
  212. package/browser/three/blender-runtime-lighting.ts +48 -178
  213. package/browser/three/blender-runtime-shadows.ts +187 -1
  214. package/browser/three/blender-runtime-skeleton.ts +53 -1
  215. package/browser/three/blender-runtime-view.ts +505 -283
  216. package/browser/three/blender-runtime-weights.ts +9 -72
  217. package/browser/three/blender-sky.ts +13 -33
  218. package/browser/three/blender-triangulate.ts +7 -35
  219. package/browser/three/blender-viewport-sampling.ts +235 -0
  220. package/browser/three/blender-workbench-material.ts +25 -29
  221. package/browser/three/blender-workbench-outline.ts +199 -0
  222. package/browser/three/blender-world-volume.ts +62 -10
  223. package/browser/three/presenter.ts +10 -26
  224. package/browser/three/release.ts +5 -21
  225. package/browser/three/sky-precompute-worker.ts +1 -18
  226. package/browser/three/sky-worker.ts +5 -23
  227. package/browser/three/world-field-sampler.ts +12 -26
  228. package/browser/volter_three.py +9 -105
  229. package/browser/worker.ts +156 -151
  230. package/package.json +5 -2
  231. package/wasm/BUNDLE.json +17 -17
  232. package/wasm/DEPENDENCY-LICENSES.txt +14 -0
  233. package/wasm/blender_browser.data.br +0 -0
  234. package/wasm/blender_browser.js +1 -1
  235. package/wasm/blender_browser.wasm.br +0 -0
  236. package/browser/frame-transfer.mts +0 -19
  237. package/browser/three/agx-base-srgb.lut +0 -0
  238. package/browser/three/agx-look-medium-high-contrast.lut +0 -0
  239. package/browser/three/agx-look-punchy.lut +0 -0
  240. package/browser/three/blender-agx.ts +0 -235
  241. package/browser/three/blender-display-lut.ts +0 -145
  242. package/browser/three/blender-filmic.ts +0 -49
  243. package/browser/three/blender-standard.ts +0 -68
  244. package/browser/three/blender-world-visibility.ts +0 -278
  245. package/browser/three/filmic-srgb.lut +0 -0
@@ -0,0 +1,23 @@
1
+ import { HEAP_GROWTH_REFUSED } from './protocol';
2
+
3
+ /** Report WebAssembly.Memory.grow refusals from each thread before allocation aborts obscure the failed growth request. */
4
+ export function watchHeapGrowthSource(report: string): string {
5
+ return `(() => {
6
+ const grow = WebAssembly.Memory.prototype.grow;
7
+ if (grow.volterWatched) return;
8
+ const watched = function (pages) {
9
+ let before = 0;
10
+ try { before = this.buffer.byteLength; } catch (_) {}
11
+ try { return grow.call(this, pages); }
12
+ catch (error) {
13
+ const line = '${HEAP_GROWTH_REFUSED}: the browser refused ' + Math.round(pages * 65536 / 1048576) + ' MB more at ' +
14
+ Math.round(before / 1048576) + ' MB (' + String(error && error.message || error) + '); allocations fail from here.';
15
+ try { (${report})(line); } catch (_) {}
16
+ throw error;
17
+ }
18
+ };
19
+ watched.volterWatched = true;
20
+ WebAssembly.Memory.prototype.grow = watched;
21
+ })();
22
+ `;
23
+ }
package/browser/index.ts CHANGED
@@ -3,5 +3,7 @@ export {
3
3
  BlenderRuntime,
4
4
  type BlenderRuntimeOptions,
5
5
  type PresentAnswer,
6
+ type SavedDocument,
7
+ type SaveScript,
6
8
  type ScreenshotView,
7
9
  } from './runtime';
@@ -1,10 +1,21 @@
1
1
  import type { FrameChunk } from './frame-stream.mts';
2
+
3
+ /** The start of the line a Blender thread prints when the browser refuses to grow the engine's heap
4
+ * (`blender-emscripten-engine.mts`, `watchHeapGrowthSource`); the tab keeps it (`runtime.ts`). */
5
+ export const HEAP_GROWTH_REFUSED = 'Blender heap could not grow';
2
6
  /** Messages between the editor tab and its Blender worker. One session per
3
7
  * worker; the model lives in the worker's Python and nowhere else. */
4
8
 
5
9
  export type WorkerRequest =
6
10
  | { id: number; op: 'load-next'; token: string }
7
11
  | { id: number; op: 'flush-document' }
12
+ /** The document's bytes as a save would write them, for a backup: nothing is carried to the
13
+ * project and the document stays dirty. */
14
+ | { id: number; op: 'snapshot-document' }
15
+ /** Reopen the document from the project's file, discarding what it holds. */
16
+ | { id: number; op: 'revert-document' }
17
+ /** Reopen the document as `bytes` (a backup), which is then its unsaved state. */
18
+ | { id: number; op: 'restore-document'; bytes: Uint8Array }
8
19
  | { id: number; op: 'history-begin' | 'history-end' }
9
20
  | { id: number; op: 'history-step'; token: string; direction: 'undo' | 'redo' }
10
21
  /** `document` is the session's `.blend`, PROJECT-RELATIVE (`models/model.blend`
@@ -19,10 +30,8 @@ export type WorkerRequest =
19
30
  | { id: number; op: 'scene-info' }
20
31
  | { id: number; op: 'object-info'; name: string }
21
32
  | { id: number; op: 'screenshot-view'; maxSize: number }
22
- /** PRESENT WHAT THE ENGINE ALREADY HOLDS, with no capture — the frame a
23
- * freshly opened Model document asks for (`session.py`'s `present` op,
24
- * which `dispatch` has always answered; nothing sent it until the document
25
- * did). Every mutation presents, so this is only ever the FIRST one. */
33
+ /** Present the engine's current document state without capture. Used for initial display and
34
+ * explicit refreshes of an already-open document (`session.py`'s `present` op). */
26
35
  | { id: number; op: 'present' }
27
36
  /** THE RNA DOOR (`./rna.ts` is its contract, `session.py`'s `rna_view`
28
37
  * its answer): one datablock's `bl_rna.properties` as typed rows. `path` is
@@ -73,6 +82,21 @@ export type WorkerRequest =
73
82
  * plus the scene's frame range/fps and the summary row's key columns.
74
83
  * READ ONLY: keying, moving a key and setting a range are edits. */
75
84
  | { id: number; op: 'action-clip'; object?: string; bake?: boolean; action?: string; summary?: boolean }
85
+ /** THE SCENE'S MOVIE (`session.py`'s `rna_scene_movie`): its range, markers and cameras, and every
86
+ * object's transform stack and curves, for Timeline playback and glTF export. READ ONLY. */
87
+ | { id: number; op: 'scene-movie' }
88
+ /** Scene sound strips and speakers over frames first to last. READ ONLY;
89
+ * speaker sampling restores the scene frame before returning. */
90
+ | { id: number; op: 'sound-scene'; first: number; last: number }
91
+ /** Export a component or level through Blender’s glTF exporter. Restore temporary state before
92
+ * answering; do not create history, save or present. */
93
+ | { id: number; op: 'gltf-export'; kind: GltfExportKind; force?: boolean; previous?: GltfExportPrevious; blendSha256?: string; luts?: Readonly<Record<string, string>> }
94
+ /** A SCRIPT RUN AS `blender -b <document> -P <script> -- <args>` runs one (`session.py`'s `_run_script`): its
95
+ * working directory a folder emptied for it, whose files the tab carries into the project. The script puts back
96
+ * what it changes: no history, save or present. */
97
+ | { id: number; op: 'run-script'; source: string; args: readonly string[]; dir: string }
98
+ /** AN ASSET LIBRARY IN BLENDER'S PREFERENCES (`session.py`'s `_asset_library`): a preference, not an edit. */
99
+ | { id: number; op: 'asset-library'; library: BlenderAssetLibrary }
76
100
  /** ONE RESTRICTION COLUMN, written (`session.py`'s `outliner_set`). A column
77
101
  * Blender draws on no row of that type is refused by name. */
78
102
  | { id: number; op: 'outliner-set'; path: string; column: string; value: boolean }
@@ -81,7 +105,6 @@ export type WorkerRequest =
81
105
  | { id: number; op: 'view-shading'; shading: 'WIREFRAME' | 'SOLID' | 'MATERIAL' | 'RENDERED' }
82
106
  | { id: number; op: 'read-file'; path: string }
83
107
  | { id: number; op: 'write-file'; path: string; bytes: Uint8Array }
84
- | { id: number; op: 'list-files'; path: string }
85
108
  /** The tab's answer to a `present` the worker asked for.
86
109
  *
87
110
  * `held` is the PRESENTER'S OWN report of what it held BEFORE this frame
@@ -99,6 +122,8 @@ export type WorkerRequest =
99
122
  export type WorkerReply =
100
123
  | { op: 'frame-stream'; id: number; chunk: FrameChunk }
101
124
  | { op: 'document-dirty'; dirty: boolean }
125
+ /** A protected document is not being written; null follows a successful project save. */
126
+ | { op: 'document-saving'; document: string; note: string | null }
102
127
  /** THE DOCUMENT FOLLOWED BLENDER: a script's `save_as_mainfile` (or `open_mainfile`) left
103
128
  * another project `.blend` open, and the session's saves now go there
104
129
  * (`session.py::follow_open_file`). Both paths are project-relative. Posted before the
@@ -119,21 +144,7 @@ export type WorkerReply =
119
144
  * displayed-versus-submitted comparison otherwise does not have. */
120
145
  | { op: 'present'; id: number; frame: unknown; description: unknown; capture?: CaptureRequest }
121
146
  | { op: 'log'; level: 'log' | 'warn' | 'error'; text: string }
122
- /**
123
- * HOW BIG THE MODULE'S LINEAR MEMORY IS, posted after every call.
124
- *
125
- * The one number about the engine's memory that only the worker can read
126
- * (`Module.HEAPU8.length`), and the one the tab's census had no field for:
127
- * `heapUsedMB` is the PAGE's JS heap and says nothing about the wasm. wasm32
128
- * memory never shrinks, so this value is also the session's high-water mark
129
- * — a reader needs no separate peak.
130
- *
131
- * MEASURED 2026-09-18: 512 MB reserved at boot of which ~166 MB is ever
132
- * touched (so the reservation itself costs no resident pages), growing to
133
- * 1036 MB across `17-workshop-interior`. Every byte dlmalloc takes inside it
134
- * stays taken: there is no `madvise` in wasm, so a scene's peak allocation is
135
- * a permanent cost for the life of the tab.
136
- */
147
+ /** The worker’s current wasm linear-memory size, posted after each call. This differs from the page’s JS heap and also records the wasm high-water mark because wasm32 memory does not shrink. */
137
148
  | { op: 'memory'; bytes: number };
138
149
 
139
150
  export type NativeHistoryEntry = { reset: true } | {
@@ -193,15 +204,7 @@ export interface CaptureRequest {
193
204
  render?: RenderRequest;
194
205
  }
195
206
 
196
- /**
197
- * `bpy.ops.render.render()` asking for the scene camera's own picture.
198
- *
199
- * three.js IS the renderer (ARCHITECTURE-CORE, "No second implementation of a
200
- * substrate capability ships"), so a render is the Model document photographed
201
- * through the scene's camera at `scene.render`'s exact resolution — and unlike
202
- * a screenshot, whose view is only REMEMBERED for the transport to photograph
203
- * later, the pixels come back to the caller inside the operator that asked.
204
- */
207
+
205
208
  export interface RenderRequest {
206
209
  /** Omit the visible world while retaining its illumination. */
207
210
  transparent?: boolean;
@@ -218,7 +221,7 @@ export interface RenderRequest {
218
221
  /** The scene's own view transform, as the three.js tone mapping of the same
219
222
  * NAME (`bpy/_render_three.py`). A transform with no curve here never
220
223
  * reaches this point: Python refuses it. */
221
- toneMapping: 'none' | 'agx' | 'filmic' | 'neutral';
224
+ toneMapping: 'none' | 'agx' | 'filmic' | 'neutral' | 'aces13' | 'aces20';
222
225
  /** The colour-management LOOK the scene asks for, as Blender names it.
223
226
  * `'None'` means the base transform; anything else is a look the view holds
224
227
  * a baked table for, because the renderer refuses the rest by name. */
@@ -241,6 +244,90 @@ export interface RenderRequest {
241
244
  orthographic: boolean;
242
245
  }
243
246
 
247
+ /**
248
+ * Export a component .blend or a level that links components.
249
+ */
250
+ export type GltfExportKind = 'component' | 'level';
251
+
252
+ /** The project's earlier export, as the tab holds it: its manifest and its file's SHA-256. */
253
+ export interface GltfExportPrevious {
254
+ readonly manifest: GltfExportManifest;
255
+ readonly sha256: string | null;
256
+ }
257
+
258
+ /** One placement marker of a level (`extras.component`, `extras.action`, `extras.tint`, visibility). */
259
+ export interface GltfExportMarker {
260
+ readonly name: string;
261
+ readonly component: { readonly file: string; readonly collection: string };
262
+ readonly action: string | null;
263
+ readonly visible: boolean;
264
+ readonly tint: unknown;
265
+ readonly parent: string | null;
266
+ }
267
+
268
+ /** The manifest written beside each export (`<name>.json`): what made it, and what it holds. */
269
+ export interface GltfExportManifest {
270
+ readonly format: 1;
271
+ readonly kind: GltfExportKind;
272
+ /** The `.blend`, project-relative, and the SHA-256 of its bytes when it was exported. */
273
+ readonly blend: string;
274
+ readonly blendSha256: string;
275
+ /** Blender's version, its glTF add-on's, and the first 16 hex digits of `gltf_export.py`'s SHA-256. */
276
+ readonly exporter: { readonly blender: string; readonly gltf: string; readonly script: string };
277
+ readonly settings: Readonly<Record<string, string | boolean>>;
278
+ readonly file: string;
279
+ readonly sha256: string;
280
+ readonly bytes: number;
281
+ readonly nodes: number;
282
+ readonly meshes: number;
283
+ readonly skins: number;
284
+ readonly animations: readonly string[];
285
+ readonly extensionsUsed: readonly string[];
286
+ readonly hiddenNodes: number;
287
+ readonly warnings: readonly string[];
288
+ /**
289
+ * A component’s collections, named after the component.
290
+ */
291
+ readonly collections?: readonly string[];
292
+ /** A level's placements, the components they name, and the override edits no marker carries. */
293
+ readonly markers?: readonly GltfExportMarker[];
294
+ readonly components?: readonly { readonly file: string; readonly collection: string }[];
295
+ readonly unsupported?: readonly { readonly placement: string; readonly edit: string }[];
296
+ }
297
+
298
+ /** What `gltf-export` answers. `files` are the module-filesystem paths of the written `.glb`
299
+ * and manifest (none when the earlier export is `current`), for the tab to carry out. */
300
+ export interface GltfExportAnswer {
301
+ readonly kind: GltfExportKind;
302
+ readonly name: string;
303
+ readonly document: string;
304
+ readonly current: boolean;
305
+ readonly ms: number;
306
+ readonly manifest: GltfExportManifest;
307
+ readonly files: readonly string[];
308
+ /** What this Blender writes for either kind: the exporter's version and each kind's settings. */
309
+ readonly exporter: GltfExportManifest['exporter'];
310
+ readonly settings: Readonly<Record<GltfExportKind, GltfExportManifest['settings']>>;
311
+ }
312
+
313
+ /** What `run-script` answers: the script's printed output and its raise (`error`, null when it ran to its end), and
314
+ * each file it wrote under its own working directory (the run's `dir`), by its path from there, with its bytes.
315
+ * `unstaged` names the project files that could not be copied into Blender before it ran. */
316
+ export interface ScriptRunAnswer {
317
+ readonly output: string;
318
+ readonly error: string | null;
319
+ readonly files: readonly { readonly path: string; readonly bytes: Uint8Array }[];
320
+ readonly unstaged: readonly string[];
321
+ }
322
+
323
+ /** An asset library as Preferences > File Paths > Asset Libraries lists one: its name, its folder by its path from the
324
+ * project, and how an asset dragged from it is imported. */
325
+ export interface BlenderAssetLibrary {
326
+ readonly name: string;
327
+ readonly directory: string;
328
+ readonly importMethod: 'LINK' | 'APPEND' | 'APPEND_REUSE' | 'PACK';
329
+ }
330
+
244
331
  export interface FileEntry {
245
332
  path: string;
246
333
  size: number;
@@ -251,11 +338,8 @@ export interface FileEntry {
251
338
  export interface RuntimeStart {
252
339
  session: string;
253
340
  python: string;
254
- /** Which build of Blender answered: the standalone Emscripten module, or
255
- * Blender as a WALI program on browser-substrate. One gate, both skews --
256
- * a board that cannot name the skew it measured cannot call a difference
257
- * between them a defect in either. */
258
- skew?: 'emscripten' | 'wali';
341
+ /** The engine implementation that answered: standalone Emscripten or WALI on browser-substrate. */
342
+ skew: 'emscripten' | 'wali';
259
343
  /** The session's document, project-relative, when one was named. */
260
344
  document?: string;
261
345
  /** True when the project already held that `.blend` and it was OPENED —
@@ -10,15 +10,7 @@ export class PullJob<T> {
10
10
  private expected: string | null = null;
11
11
  private resume: (() => void) | null = null;
12
12
  private waiter: { resolve: (value: PullResult<T>) => void; reject: (error: unknown) => void } | null = null;
13
- /**
14
- * How the work ended, when it ended while nobody was waiting on a step. Work normally ends only
15
- * while a step waits, but work that races something against a checkpoint (an engine compiling
16
- * beside an import) can fail in the gap after a `continue` was handed out. The outcome is kept
17
- * for the step that comes next, so the caller is told what actually happened. Before this, the
18
- * failure was thrown at nobody and the next step answered "Invalid or replayed load
19
- * continuation", which named the protocol instead of the fault (measured 2026-10-08: a Blender
20
- * start refused by a content security policy showed only that message, on every retry).
21
- */
13
+ /** Keep a terminal result that arrives between cooperative steps so the next step reports the original failure. */
22
14
  private ended: { readonly value: T } | { readonly error: unknown } | null = null;
23
15
  constructor(private readonly work: (checkpoint: (phase: string) => Promise<void>) => Promise<T>) {}
24
16
  step(token?: string): Promise<PullResult<T>> {
@@ -48,7 +40,7 @@ export class PullJob<T> {
48
40
  if (waiter) waiter.reject(error);
49
41
  else {
50
42
  this.ended = { error };
51
- // Said now as well: if the owner never asks for the next step, nobody would collect it.
43
+ // Report the failure now while retaining it for the next step.
52
44
  console.warn('A Blender load failed between steps; the next step reports it:', error);
53
45
  }
54
46
  });
package/browser/rna.ts CHANGED
@@ -1,17 +1,4 @@
1
- /**
2
- * THE RNA DOOR'S CONTRACT — what `session.py`'s `rna_view`, `rna_context` and
3
- * `rna_set` answer, as types. TYPES ONLY, no imports and no values: the
4
- * Properties sections (`../contributions/blender-properties-*`) read these
5
- * shapes, and a type-only module costs their closure nothing.
6
- *
7
- * Everything here is Blender's own introspection of Blender's own data
8
- * (`bl_rna.properties`) — the ruling's "everything Blender SHOWS about a
9
- * datablock, we show, in OUR panels, read from the engine through bpy and RNA"
10
- * (ARCHITECTURE-CORE §Blender north star, "Inspection parity, not editing
11
- * parity"). Nothing in this file is a volter invention over RNA: every field is
12
- * a `PropertyRNA` member, a `bpy.types` name, or an address `path_from_id()`
13
- * produced.
14
- */
1
+
15
2
 
16
3
  /** `PropertyRNA.type` — Blender's own property-type enum. */
17
4
  export type BlenderRnaType =
@@ -71,9 +58,7 @@ export interface BlenderRnaRow {
71
58
  * swatch instead of three numbers. */
72
59
  readonly subtype: string;
73
60
  readonly description: string;
74
- /** The RNA STRUCT that declares this property (`Object`, `ID`, `Mesh`) —
75
- * the only grouping RNA exposes, and the one the generic view groups by.
76
- * Blender's panel layout is not this and is not read (the ruling). */
61
+ /** The declaring RNA struct (Object, ID, Mesh), used to group properties independently of Blender’s panel layout. */
77
62
  readonly group: string;
78
63
  readonly groupName: string;
79
64
  readonly arrayLength: number;
@@ -86,7 +71,7 @@ export interface BlenderRnaRow {
86
71
  * `ViewLayer.depsgraph` (`rna_layer.cc:733`), the NLA tweak storage
87
72
  * (`rna_animation.cc:1701,1713`). The door reports it; the generic view
88
73
  * OMITS it, and no curated list names one. */
89
- readonly hidden?: boolean;
74
+ readonly hidden: boolean;
90
75
  /** UNDRAWN STATE — RNA never gave this property UI text, so
91
76
  * `rna_define.cc:1311-1312`'s defaults still stand: `name` is the
92
77
  * identifier and `description` is empty. The modifier panel-open booleans
@@ -238,8 +223,7 @@ export interface BlenderRnaWrite {
238
223
  // ---- the outliner tree -----------------------------------------------------
239
224
  //
240
225
  // WHAT `session.py`'s `rna_outliner` ANSWERS: Blender's VIEW LAYER tree for the
241
- // session's scene, as rows (WORK.md §Blender in the tab is Blender,
242
- // "Inspection parity", I3). Every field below is Blender's own — a `TSE_*`
226
+ // session's scene, as rows. Every field below is Blender's own — a `TSE_*`
243
227
  // element type, an `ICON_*` name `tree_element_get_icon` picks, a restriction
244
228
  // column `outliner_draw_restrictbuts` draws — and nothing here is a volter
245
229
  // invention over the Outliner.
@@ -303,7 +287,7 @@ export interface BlenderOutlinerRow {
303
287
  readonly object?: string;
304
288
  /** A layer collection row's COLLECTION address. The row itself is addressed
305
289
  * by its POSITION in the view layer, because a `LayerCollection` has no
306
- * `path_from_id()` (I2's finding); this is the collection datablock it
290
+ * `path_from_id()`; this is the collection datablock it
307
291
  * shows. */
308
292
  readonly data?: string;
309
293
  readonly selected?: boolean;
@@ -408,7 +392,7 @@ export interface BlenderNode {
408
392
  readonly colorTag: BlenderNodeColorTag;
409
393
  /** Tree-space `[x, y]`: the node's top-left corner, which is where
410
394
  * `node_update_basis` starts laying out (`node_draw.cc:1295-1300`). */
411
- readonly location: readonly number[];
395
+ readonly location: readonly [number, number];
412
396
  readonly width: number;
413
397
  /** `Node.hide` — Blender's COLLAPSED node, drawn as a short rounded bar
414
398
  * (`node_update_collapsed`, `node_draw.cc:1326-1390`). */
@@ -416,23 +400,12 @@ export interface BlenderNode {
416
400
  readonly muted: boolean;
417
401
  readonly selected: boolean;
418
402
  readonly useCustomColor: boolean;
419
- readonly color: readonly number[];
403
+ readonly color: readonly [number, number, number];
420
404
  /** The frame node this one sits inside, by name, or null. */
421
405
  readonly parent: string | null;
422
406
  /** `NODE_DO_OUTPUT` through its one RNA spelling. */
423
407
  readonly activeOutput: boolean;
424
- /**
425
- * HOW MANY SOCKET PANELS THE NODE DECLARES (`Node.panel_states`). A panel
426
- * state carries a `persistent_uid` and `is_collapsed` and nothing else
427
- * (`rna_nodetree.cc:9289-9299`, `:9430-9434`); which SOCKETS belong to a
428
- * panel, and what a panel is CALLED, live in the node's C++ declaration
429
- * that `node_update_basis_from_declaration` (`node_draw.cc:1086-1218`)
430
- * walks, and bpy exposes neither — so that half is TRACED from Blender's
431
- * source (ruled 2026-09-19) and this door owes only the live half. The
432
- * trace and its table belong to the CONSUMER, `@volter/editor-blender`, because they
433
- * read a Blender checkout rather than the running engine; this package
434
- * answers what the engine knows and nothing else.
435
- */
408
+ /** Live Node.panel_states values. Socket-panel labels and membership come from Blender’s C++ declarations and belong to the editor-blender consumer’s source table. */
436
409
  readonly panelCount: number;
437
410
  /**
438
411
  * The states themselves, IN DECLARATION ORDER — Blender indexes
@@ -473,39 +446,14 @@ export interface BlenderNodeTree {
473
446
  readonly links: readonly BlenderNodeLink[];
474
447
  }
475
448
 
476
- /**
477
- * What `rna_uv_layout` answers — ONE MESH'S UV LAYOUT, as Blender's UV editor
478
- * would draw it.
479
- *
480
- * THE PER-CORNER ARRAYS CROSS AS BASE64 typed-array bytes, for the reason
481
- * I4's weights do: a layout is `loops` two-float corners plus a
482
- * triangulation, and the generic RNA door answers a struct at a time.
483
- * `uvBase64` is `Float32Array` of `loops * 2` (u, v per corner, in corner
484
- * order); `triangleBase64` is `Uint32Array` of `triangles * 3` CORNER indices
485
- * into that array — Blender's own `calc_loop_triangles` fan, never one of
486
- * ours; `loopStartBase64` / `loopTotalBase64` are `Uint32Array` of
487
- * `polygons`, which is what makes a face's UV outline a contiguous walk;
488
- * `pinBase64` is `Uint8Array` of `loops` and is null when nothing is pinned.
489
- *
490
- * `active` null with a non-empty `layers` cannot happen (a mesh with UV maps
491
- * has an active one); `active` null with `layers` empty is a mesh that has no
492
- * UV map at all, which is a STATE — `arena-vanguard.blend`'s 17,778-vertex
493
- * body is exactly that, measured 2026-09-19 — and the door answers the header
494
- * alone.
495
- *
496
- * `selection` is null at this pin and says so rather than being absent: UV
497
- * selection lives in the BMesh an edit-mode session holds, and
498
- * `MeshUVLoopLayer` (`rna_mesh.cc:2380-2456`) declares `uv`, `pin`, `name`,
499
- * `active`, `active_render` and `active_clone` and no selection of any kind.
500
- */
449
+ /** One mesh’s UV layout. uvBase64 contains Float32 corner pairs; triangleBase64 contains Uint32 corner indices; loopStartBase64 and loopTotalBase64 delimit polygon corners; pinBase64 is Uint8 or null. A mesh with no UV layers has no active layer. Selection is null because MeshUVLoopLayer exposes no selection field outside edit-mode BMesh. */
501
450
  export interface BlenderUvLayout {
502
451
  readonly object: string | null;
503
452
  readonly mesh: string | null;
504
453
  readonly path?: string;
505
454
  readonly layers: readonly string[];
506
455
  readonly active: string | null;
507
- /** `bpy.context.mode` — reported, never required (orchestrator ruling 1,
508
- * 2026-09-19: inspection is not mode-gated). */
456
+ /** bpy.context.mode, reported without gating inspection on the current mode. */
509
457
  readonly mode: string;
510
458
  readonly loops: number;
511
459
  readonly polygons: number;
@@ -538,6 +486,23 @@ export interface BlenderClipTrack extends BlenderClipCurve {
538
486
  readonly index: number;
539
487
  }
540
488
 
489
+ /** One sampled column of a {@link BlenderClipBone}: `count` samples (one when it never changes),
490
+ * base64 Float32. */
491
+ export interface BlenderClipColumn {
492
+ readonly count: number;
493
+ readonly base64: string;
494
+ }
495
+
496
+ /** One bone of an action as three.js plays it: its local position, quaternion (x, y, z, w) and
497
+ * scale per integer frame of the clip's range, and the frames it is keyed on. */
498
+ export interface BlenderClipBone {
499
+ readonly bone: string;
500
+ readonly keys: readonly number[];
501
+ readonly position: BlenderClipColumn;
502
+ readonly quaternion: BlenderClipColumn;
503
+ readonly scale: BlenderClipColumn;
504
+ }
505
+
541
506
  /** An F-Curve's body: everything Blender evaluates it from, wherever it lives. */
542
507
  export interface BlenderClipCurve {
543
508
  /** `FCurve.extrapolation`: `CONSTANT` or `LINEAR`. */
@@ -604,13 +569,9 @@ export interface BlenderActionClip {
604
569
  readonly slot: string | null;
605
570
  /** The action's object slots: at most one means every armature reads the same curves. */
606
571
  readonly objectSlots?: number;
607
- /** `layered` at this pin — an `Action` has no `.fcurves`; they live at
608
- * `layers[0].strips[0].channelbags[0].fcurves`. `legacy` is the branch for
609
- * a build where the old attribute is still there. */
610
- readonly channels?: string;
611
572
  /** The scene's own name — the Timeline's one write addresses
612
573
  * `bpy.data.scenes["<name>"]`, because an RNA path starts at `bpy.data.`. */
613
- readonly scene?: string;
574
+ readonly scene: string;
614
575
  readonly frameCurrent: number;
615
576
  readonly frameStart: number;
616
577
  readonly frameEnd: number;
@@ -621,7 +582,7 @@ export interface BlenderActionClip {
621
582
  readonly keyframes: readonly BlenderClipKeyColumn[];
622
583
  /** Every animated object in the view layer, for the summary row's
623
584
  * selection filter. Always present; empty for a file with no animation. */
624
- readonly summary?: readonly BlenderClipSummaryObject[];
585
+ readonly summary: readonly BlenderClipSummaryObject[];
625
586
  readonly tracks: readonly BlenderClipTrack[];
626
587
  readonly clipStart?: number;
627
588
  readonly clipEnd?: number;
@@ -629,14 +590,170 @@ export interface BlenderActionClip {
629
590
  * modifier repeats into. */
630
591
  readonly keysStart?: number | null;
631
592
  readonly keysEnd?: number | null;
632
- /** Blender repeats it past its range (Cycles modifiers on its curves, or `use_cyclic`). */
633
- readonly cyclic?: boolean;
634
593
  readonly duration?: number;
635
- readonly sampled?: number;
636
594
  /** Bones the action names that the armature does not have. */
637
595
  readonly unplayedBones?: readonly string[];
638
- /** What the action keys besides bone channels (the object's own transform, a property),
639
- * which plays only in Blender. */
596
+ /** THE ACTION AS THREE.JS PLAYS IT (`session.py`'s `_three_bones`): per bone it keys, the bone's
597
+ * local transform at every integer frame from `clipStart` to `clipEnd`, sampled by Blender. */
598
+ readonly bones?: readonly BlenderClipBone[];
599
+ /** Keyed channels the three.js animation preview does not play (a custom property or delta
600
+ * transform). Object transforms are handled by {@link BlenderSceneMovie}. */
640
601
  readonly unsupported?: readonly string[];
641
602
  readonly reason?: string;
642
603
  }
604
+
605
+ /** One object the scene's movie moves or looks through. */
606
+ export interface BlenderMovieObject {
607
+ readonly name: string;
608
+ readonly type: string;
609
+ readonly parent: string | null;
610
+ /** `Object.rotation_mode`. */
611
+ readonly rotationMode: string;
612
+ /** Its own channels as the file holds them, which a component no action keys keeps. */
613
+ readonly channels: {
614
+ readonly location: readonly number[];
615
+ readonly rotation_quaternion: readonly number[];
616
+ readonly rotation_euler: readonly number[];
617
+ readonly rotation_axis_angle: readonly number[];
618
+ readonly scale: readonly number[];
619
+ };
620
+ /** Its delta transform (the rotation as a quaternion w, x, y, z), applied as `BKE_object_to_mat4`. */
621
+ readonly delta: { readonly location: readonly number[]; readonly rotation: readonly number[]; readonly scale: readonly number[] };
622
+ /** What places its basis in its parent's space: `matrix_parent_inverse` (identity without a parent). */
623
+ readonly pre: readonly (readonly number[])[];
624
+ readonly constraints: readonly {
625
+ readonly name: string;
626
+ readonly type: string;
627
+ readonly enabled: boolean;
628
+ readonly influence: number;
629
+ readonly target?: string;
630
+ readonly trackAxis?: string;
631
+ readonly upAxis?: string;
632
+ readonly useTargetZ?: boolean;
633
+ }[];
634
+ /** A camera's projection as held by the file, with sampled columns when keyed. */
635
+ readonly camera?: {
636
+ readonly type: string;
637
+ readonly lens: number;
638
+ readonly sensor_width: number;
639
+ readonly sensor_height: number;
640
+ readonly sensor_fit: string;
641
+ readonly ortho_scale: number;
642
+ readonly clip_start: number;
643
+ readonly clip_end: number;
644
+ readonly shift_x: number;
645
+ readonly shift_y: number;
646
+ /** Disabled in renders: Blender's marker cuts skip it. */
647
+ readonly renderHidden: boolean;
648
+ /** Its projection at every scene frame, sampled by Blender (present when keyed). */
649
+ readonly sampled?: { readonly start: number; readonly end: number } & Readonly<Record<
650
+ 'lens' | 'sensor_width' | 'sensor_height' | 'ortho_scale' | 'shift_x' | 'shift_y' | 'clip_start' | 'clip_end', BlenderClipColumn>>;
651
+ };
652
+ /** Its local transform at every scene frame, sampled by Blender (`session.py`'s `_sample_object`):
653
+ * `hidden` (1 hidden) when its render visibility is keyed. Present when it is animated. */
654
+ readonly sampled?: {
655
+ readonly start: number;
656
+ readonly end: number;
657
+ readonly position: BlenderClipColumn;
658
+ readonly quaternion: BlenderClipColumn;
659
+ readonly scale: BlenderClipColumn;
660
+ readonly hidden?: BlenderClipColumn;
661
+ };
662
+ /** Animation behavior the three.js preview does not play. */
663
+ readonly unsupported?: readonly string[];
664
+ }
665
+
666
+ /** A sound's file: a path Blender holds it at (the project's, absolute), or a packed sound's copy
667
+ * beside the session, read back through the session's files. */
668
+ export type BlenderSoundFile = { readonly path: string } | { readonly packed: string };
669
+
670
+ /**
671
+ * THE SCENE'S SOUND, DESCRIBED FOR THE PAGE TO MIX (`session.py`'s `rna_sound_scene`), over the
672
+ * movie's frames `first` to `last`. Every per-frame column holds `count` values (stride 1, or 3 for a
673
+ * point), or one when it never changes.
674
+ */
675
+ export interface BlenderSoundScene {
676
+ readonly fps: number;
677
+ readonly first: number;
678
+ readonly last: number;
679
+ readonly count: number;
680
+ /** The scene's own volume, and whether its sequencer sound is muted (`Scene.use_audio`). */
681
+ readonly volume: number;
682
+ readonly muted: boolean;
683
+ /** `Scene.audio_distance_model`: `NONE`, `INVERSE`, `INVERSE_CLAMPED`, `LINEAR`, `LINEAR_CLAMPED`, `EXPONENT`, `EXPONENT_CLAMPED`. */
684
+ readonly distanceModel: string;
685
+ readonly strips: readonly {
686
+ readonly name: string;
687
+ readonly file: BlenderSoundFile | null;
688
+ readonly mono: boolean;
689
+ readonly muted: boolean;
690
+ /** The frame the sound's own start sits at, and the frames the strip is cut to (end exclusive). */
691
+ readonly start: number;
692
+ readonly finalStart: number;
693
+ readonly finalEnd: number;
694
+ readonly speed: number;
695
+ readonly volume: BlenderClipColumn;
696
+ readonly pan: BlenderClipColumn;
697
+ }[];
698
+ readonly speakers: readonly {
699
+ readonly name: string;
700
+ readonly file: BlenderSoundFile | null;
701
+ readonly muted: boolean;
702
+ /** The frames its NLA sound strips start on. */
703
+ readonly starts: readonly number[];
704
+ readonly volume: BlenderClipColumn;
705
+ readonly volumeMin: number;
706
+ readonly volumeMax: number;
707
+ readonly attenuation: number;
708
+ readonly distanceReference: number;
709
+ readonly distanceMax: number;
710
+ /** Radians. */
711
+ readonly coneInner: number;
712
+ readonly coneOuter: number;
713
+ readonly coneOuterVolume: number;
714
+ /** Blender world space, per frame (stride 3); facing is the speaker's -Z. */
715
+ readonly position: BlenderClipColumn;
716
+ readonly facing: BlenderClipColumn;
717
+ }[];
718
+ /** The scene camera's place and axes per frame (stride 3), the speakers' listener; null without speakers. */
719
+ readonly listener: { readonly position: BlenderClipColumn; readonly forward: BlenderClipColumn; readonly up: BlenderClipColumn } | null;
720
+ }
721
+
722
+ /**
723
+ * The scene animation from `session.py`'s `rna_scene_movie`, consumed by the Timeline preview
724
+ * and glTF export.
725
+ */
726
+ export interface BlenderSceneMovie {
727
+ readonly scene: {
728
+ readonly name: string;
729
+ readonly start: number;
730
+ readonly end: number;
731
+ readonly fps: number;
732
+ readonly current: number;
733
+ readonly camera: string | null;
734
+ };
735
+ /** The Timeline's markers by frame, each with the camera it cuts to (`TimelineMarker.camera`). */
736
+ readonly markers: readonly { readonly name: string; readonly frame: number; readonly camera: string | null }[];
737
+ readonly objects: readonly BlenderMovieObject[];
738
+ /** MATERIALS whose Principled inputs a curve keys (`_sample_material`): base colour, emission (colour times strength) and alpha at every scene frame, each
739
+ * present only when keyed. */
740
+ readonly materials: readonly {
741
+ readonly name: string;
742
+ readonly start: number;
743
+ readonly end: number;
744
+ readonly color?: BlenderClipColumn;
745
+ readonly emissive?: BlenderClipColumn;
746
+ readonly opacity?: BlenderClipColumn;
747
+ }[];
748
+ /** MESHES whose shape keys a curve keys (`_sample_shapes`): each keyed key's offset at
749
+ * every vertex (from the key it is relative to), its value at every scene frame and the value the
750
+ * presented geometry already holds; `mapped` false when modifiers change the mesh's vertices. */
751
+ readonly shapes: readonly {
752
+ readonly object: string;
753
+ readonly start: number;
754
+ readonly end: number;
755
+ readonly vertices: number;
756
+ readonly mapped: boolean;
757
+ readonly keys: readonly { readonly name: string; readonly delta: BlenderClipColumn; readonly values: BlenderClipColumn; readonly current: number }[];
758
+ }[];
759
+ }