@volter/editor-blender 0.1.150 → 0.1.152
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/LICENSE +18 -9
- package/README.md +5 -5
- package/contributions/blender-animation-editors.tsx +17 -69
- package/contributions/blender-display-settings.ts +36 -0
- package/contributions/blender-export-gltf.ts +228 -0
- package/contributions/blender-export.verbs.ts +39 -0
- package/contributions/blender-gltf-export.verbs.ts +312 -0
- package/contributions/blender-header-menus.tsx +37 -133
- package/contributions/blender-icon-trace.mjs +8 -98
- package/contributions/blender-icons.source.mjs +96 -860
- package/contributions/blender-mixer-pose.ts +369 -0
- package/contributions/blender-model-opening.tsx +26 -56
- package/contributions/blender-node-editor.document.tsx +30 -181
- package/contributions/blender-node-geometry.ts +44 -230
- package/contributions/blender-node-panels.source.mjs +7 -121
- package/contributions/blender-outliner-authoring.ts +61 -415
- package/contributions/blender-outliner-model.ts +15 -112
- package/contributions/blender-play-area.tsx +99 -0
- package/contributions/blender-playback-movie.ts +409 -0
- package/contributions/blender-pose.ts +18 -368
- package/contributions/blender-properties-context.ts +1 -1
- package/contributions/blender-properties-model.ts +59 -91
- package/contributions/blender-properties-tab.tsx +3 -50
- package/contributions/blender-properties-view.tsx +39 -181
- package/contributions/blender-remedy.ts +14 -0
- package/contributions/blender-runtime-skin.ts +103 -99
- package/contributions/blender-runtime.document.tsx +170 -618
- package/contributions/blender-scene-mixer.ts +51 -0
- package/contributions/blender-screenshot.service.ts +28 -0
- package/contributions/blender-timeline-geometry.ts +16 -136
- package/contributions/blender-timeline.document.tsx +145 -333
- package/contributions/blender-uv-editor.document.tsx +48 -112
- package/contributions/blender-uv-geometry.ts +14 -179
- package/contributions/blender-version.status.tsx +45 -31
- package/contributions/blender.environment.ts +2 -2
- package/contributions/blender.icons.json +0 -9
- package/contributions/blender.keymap.ts +9 -51
- package/contributions/blender.node-panels.json +4 -13
- package/contributions/blender.page.ts +64 -0
- package/contributions/blender.status.tsx +8 -150
- package/contributions/blender.style.ts +25 -152
- package/contributions/blender.verbs.ts +52 -0
- package/contributions/model.layout.ts +2 -34
- package/contributions/models.finder.ts +3 -30
- package/contributions/properties-bone-constraints.inspector.tsx +1 -16
- package/contributions/properties-bone.inspector.tsx +2 -31
- package/contributions/properties-collection.inspector.tsx +1 -34
- package/contributions/properties-constraints.inspector.tsx +1 -21
- package/contributions/properties-data.inspector.tsx +2 -39
- package/contributions/properties-material.inspector.tsx +2 -33
- package/contributions/properties-modifiers.inspector.tsx +2 -30
- package/contributions/properties-object.inspector.tsx +2 -44
- package/contributions/properties-output.inspector.tsx +1 -27
- package/contributions/properties-particles.inspector.tsx +2 -46
- package/contributions/properties-physics.inspector.tsx +2 -41
- package/contributions/properties-render.inspector.tsx +1 -45
- package/contributions/properties-scene.inspector.tsx +1 -35
- package/contributions/properties-texture.inspector.tsx +1 -49
- package/contributions/properties-view-layer.inspector.tsx +1 -29
- package/contributions/properties-world.inspector.tsx +2 -39
- package/contributions/shading.layout.ts +3 -78
- package/contributions/uv-editing.layout.ts +3 -70
- package/dist-node/serving.mjs +13 -32
- package/host/blender-runtime-host.ts +795 -412
- package/host/scene-sound.ts +143 -0
- package/node/blender-mcp.ts +52 -95
- package/package.json +1221 -46
- package/producticons/blender-icons.otf +0 -0
- package/producticons/blender-product-icon-theme.json +129 -0
- package/serving/blender-routes.ts +9 -48
- package/serving/blender-wasm-artifact.ts +29 -166
- package/serving/index.ts +1 -1
- package/src/api.ts +9 -0
- package/src/layouts.tsx +1 -17
- package/src/looks.ts +1 -12
- package/src/model-document-saving.ts +25 -0
- package/src/node-view-state.ts +3 -25
- package/src/play-mode.ts +13 -42
- package/src/play-tool.ts +44 -0
- package/src/presentation.ts +6 -18
- package/src/regions.ts +3 -43
- package/src/start-document.ts +15 -0
- package/src/timeline-view-state.ts +4 -39
- package/src/uv-view-state.ts +4 -20
- package/themes/blender.json +299 -0
- package/view/blender-view-routes.ts +1 -1
- package/contributions/blender-game-panel.tsx +0 -809
- package/contributions/blender-palette.source.mjs +0 -350
- package/contributions/blender-play-skin.ts +0 -508
- package/contributions/blender-screenshot.menu.ts +0 -32
- package/contributions/blender.command.ts +0 -109
- package/contributions/blender.palette.json +0 -102
- package/src/play-live.ts +0 -27
|
@@ -970,9 +970,6 @@
|
|
|
970
970
|
"object-group": {
|
|
971
971
|
"path": "M2.45 5.59L13.55 5.59A1.43 1.43 0 0 0 13.55 2.72L2.45 2.72A1.43 1.43 0 0 0 2.45 5.59ZM12.12 4.16L12.12 11.84A1.43 1.43 0 0 0 14.99 11.84L14.99 4.16A1.43 1.43 0 0 0 12.12 4.16ZM13.55 10.41L2.45 10.41A1.43 1.43 0 0 0 2.45 13.28L13.55 13.28A1.43 1.43 0 0 0 13.55 10.41ZM3.88 11.84L3.88 4.16A1.43 1.43 0 0 0 1.01 4.16L1.01 11.84A1.43 1.43 0 0 0 3.88 11.84ZM-0.66 0.68A0.37 0.37 0 0 0 -1.03 1.05L-1.03 4.1A0.37 0.37 0 0 0 -0.66 4.46L2.39 4.46A0.37 0.37 0 0 0 2.75 4.1L2.75 1.05A0.37 0.37 0 0 0 2.39 0.68ZM13.61 0.68A0.37 0.37 0 0 0 13.25 1.05L13.25 4.1A0.37 0.37 0 0 0 13.61 4.46L16.66 4.46A0.37 0.37 0 0 0 17.03 4.1L17.03 1.05A0.37 0.37 0 0 0 16.66 0.68ZM-0.66 11.54A0.37 0.37 0 0 0 -1.03 11.9L-1.03 14.95A0.37 0.37 0 0 0 -0.66 15.32L2.39 15.32A0.37 0.37 0 0 0 2.75 14.95L2.75 11.9A0.37 0.37 0 0 0 2.39 11.54ZM13.61 11.54A0.37 0.37 0 0 0 13.25 11.9L13.25 14.95A0.37 0.37 0 0 0 13.61 15.32L16.66 15.32A0.37 0.37 0 0 0 17.03 14.95L17.03 11.9A0.37 0.37 0 0 0 16.66 11.54Z"
|
|
972
972
|
},
|
|
973
|
-
"outliner": {
|
|
974
|
-
"path": "M-0.23 3.29L5.65 3.29L5.65 -0.23L-0.23 -0.23ZM2.12 15.06L3.29 15.06L3.29 4.47L2.12 4.47ZM2.12 9.17L6.83 9.17L6.83 8L2.12 8ZM2.12 15.05L6.83 15.05L6.83 13.88L2.12 13.88ZM8 10.35L16.24 10.35L16.24 6.83L8 6.83ZM8 16.24L16.24 16.24L16.24 12.71L8 12.71Z"
|
|
975
|
-
},
|
|
976
973
|
"outliner-camera": {
|
|
977
974
|
"path": "M1.53 4.58A1.1 1.1 0 0 0 0.44 5.68L0.44 11.54A1.1 1.1 0 0 0 1.53 12.64L9.34 12.64A1.1 1.1 0 0 0 10.44 11.54L10.44 5.68A1.1 1.1 0 0 0 9.34 4.58ZM15.56 12.39L15.56 4.83L10.93 8.61ZM1.47 2.14A1.53 1.53 0 1 0 4.52 2.14A1.53 1.53 0 1 0 1.47 2.14ZM5.62 2.14A1.53 1.53 0 1 0 8.67 2.14A1.53 1.53 0 1 0 5.62 2.14Z",
|
|
978
975
|
"tone": "object"
|
|
@@ -1030,9 +1027,6 @@
|
|
|
1030
1027
|
"plus": {
|
|
1031
1028
|
"path": "M7.11 1.9L7.11 14.1A0.89 0.89 0 0 0 8.89 14.1L8.89 1.9A0.89 0.89 0 0 0 7.11 1.9ZM1.9 8.89L14.1 8.89A0.89 0.89 0 0 0 14.1 7.11L1.9 7.11A0.89 0.89 0 0 0 1.9 8.89Z"
|
|
1032
1029
|
},
|
|
1033
|
-
"properties": {
|
|
1034
|
-
"path": "M2.71 -0.23A2.94 2.94 0 0 0 -0.23 2.71L-0.23 3.89A2.94 2.94 0 0 0 2.71 6.83L13.29 6.83A2.94 2.94 0 0 0 16.24 3.89L16.24 2.71A2.94 2.94 0 0 0 13.29 -0.23ZM6.83 0.94L12.71 0.94L13.32 1.02L13.89 1.25L14.37 1.63L14.75 2.11L14.98 2.68L15.06 3.29L14.98 3.9L14.75 4.47L14.37 4.96L13.89 5.33L13.32 5.57L12.71 5.65L6.83 5.65ZM2.71 9.17A2.94 2.94 0 0 0 -0.23 12.11L-0.23 13.29A2.94 2.94 0 0 0 2.71 16.24L13.29 16.24A2.94 2.94 0 0 0 16.24 13.29L16.24 12.11A2.94 2.94 0 0 0 13.29 9.17ZM10.35 10.34L12.71 10.34L13.32 10.42L13.89 10.66L14.37 11.03L14.75 11.52L14.98 12.09L15.06 12.7L14.98 13.31L14.75 13.87L14.37 14.36L13.89 14.74L13.32 14.97L12.71 15.05L10.35 15.05Z"
|
|
1035
|
-
},
|
|
1036
1030
|
"properties-bone": {
|
|
1037
1031
|
"path": "M5.5 1C4.68 1 4 1.68 4 2.5L4 3.5L4 4L3.5 4L2.5 4C1.68 4 1 4.68 1 5.5C1 6.32 1.68 7 2.5 7L3.5 7C3.93 7 4.27 6.79 4.54 6.5L9.5 11.46C9.21 11.73 9 12.07 9 12.5L9 13.5C9 14.32 9.67 15 10.5 15C11.32 15 12 14.32 12 13.5L12 12.5L12 12L12.5 12L13.5 12C14.32 12 15 11.32 15 10.5C15 9.68 14.32 9 13.5 9L12.5 9C12.07 9 11.73 9.21 11.46 9.5L6.5 4.54C6.79 4.27 7 3.93 7 3.5L7 2.5C7 1.68 6.33 1 5.5 1Z",
|
|
1038
1032
|
"tone": "data"
|
|
@@ -1227,9 +1221,6 @@
|
|
|
1227
1221
|
"up-right-and-down-left-from-center": {
|
|
1228
1222
|
"path": "M2.75 14.46L14.46 2.75A0.85 0.85 0 0 0 13.25 1.54L1.54 13.25A0.85 0.85 0 0 0 2.75 14.46ZM9.22 2.27L14.59 2.27A0.85 0.85 0 0 0 14.59 0.56L9.22 0.56A0.85 0.85 0 0 0 9.22 2.27ZM13.73 1.41L13.73 6.78A0.85 0.85 0 0 0 15.44 6.78L15.44 1.41A0.85 0.85 0 0 0 13.73 1.41ZM6.78 13.73L1.41 13.73A0.85 0.85 0 0 0 1.41 15.44L6.78 15.44A0.85 0.85 0 0 0 6.78 13.73ZM2.27 14.59L2.27 9.22A0.85 0.85 0 0 0 0.56 9.22L0.56 14.59A0.85 0.85 0 0 0 2.27 14.59Z"
|
|
1229
1223
|
},
|
|
1230
|
-
"users": {
|
|
1231
|
-
"path": "M1.78 4.34A3.05 3.05 0 1 0 7.88 4.34A3.05 3.05 0 1 0 1.78 4.34ZM0.31 13.61L0.37 12.91L0.53 12.22L0.81 11.56L1.18 10.96L1.64 10.42L2.17 9.96L2.78 9.59L3.43 9.32L4.12 9.15L4.83 9.1L5.53 9.15L6.22 9.32L6.88 9.59L7.48 9.96L8.02 10.42L8.48 10.96L8.85 11.56L9.12 12.22L9.29 12.91L9.34 13.61L11.05 13.61L10.97 12.64L10.75 11.69L10.37 10.79L9.86 9.95L9.23 9.21L8.49 8.58L7.65 8.07L6.75 7.69L5.8 7.47L4.83 7.39L3.85 7.47L2.91 7.69L2 8.07L1.17 8.58L0.43 9.21L-0.21 9.95L-0.72 10.79L-1.09 11.69L-1.32 12.64L-1.39 13.61ZM-1.39 13.61A0.85 0.85 0 1 0 0.31 13.61A0.85 0.85 0 1 0 -1.39 13.61ZM9.34 13.61A0.85 0.85 0 1 0 11.05 13.61A0.85 0.85 0 1 0 9.34 13.61ZM11.54 3.16L11.8 3.09L12.07 3.05L12.35 3.06L12.61 3.11L12.87 3.21L13.11 3.33L13.33 3.5L13.52 3.7L13.67 3.92L13.79 4.16L13.88 4.42L13.92 4.69L13.92 4.96L13.88 5.23L13.79 5.49L13.67 5.74L13.52 5.96L13.33 6.16L13.11 6.32L12.87 6.45L12.61 6.54L12.35 6.59L12.07 6.6L11.8 6.57L11.54 6.5L11 7.97L11.5 8.11L12.01 8.17L12.52 8.15L13.03 8.06L13.51 7.89L13.96 7.64L14.37 7.33L14.73 6.96L15.02 6.54L15.25 6.08L15.41 5.59L15.49 5.08L15.49 4.57L15.41 4.06L15.25 3.57L15.02 3.11L14.73 2.69L14.37 2.32L13.96 2.01L13.51 1.77L13.03 1.6L12.52 1.5L12.01 1.48L11.5 1.54L11 1.68ZM10.49 2.42A0.79 0.79 0 1 0 12.06 2.42A0.79 0.79 0 1 0 10.49 2.42ZM10.49 7.24A0.79 0.79 0 1 0 12.06 7.24A0.79 0.79 0 1 0 10.49 7.24ZM9.04 11.66L9.26 11.16L9.56 10.71L9.92 10.3L10.34 9.95L10.81 9.68L11.32 9.47L11.85 9.35L12.39 9.31L12.94 9.35L13.47 9.47L13.97 9.68L14.44 9.95L14.86 10.3L15.23 10.71L15.52 11.16L15.75 11.66L17.29 11.1L16.96 10.37L16.53 9.71L16 9.12L15.38 8.61L14.7 8.21L13.96 7.91L13.18 7.73L12.39 7.67L11.6 7.73L10.82 7.91L10.09 8.21L9.4 8.61L8.79 9.12L8.26 9.71L7.82 10.37L7.49 11.1ZM7.45 11.38A0.82 0.82 0 1 0 9.08 11.38A0.82 0.82 0 1 0 7.45 11.38ZM15.7 11.38A0.82 0.82 0 1 0 17.34 11.38A0.82 0.82 0 1 0 15.7 11.38Z"
|
|
1232
|
-
},
|
|
1233
1224
|
"viewport-camera": {
|
|
1234
1225
|
"path": "M4 12L4 10.5C4 10.22 4.22 10 4.5 10L10.5 10C10.78 10 11 10.22 11 10.5L11 15.5C11 15.78 10.78 16 10.5 16L5.71 16C5.57 16 5.45 15.95 5.35 15.85L4.15 14.65C4.05 14.55 4 14.43 4 14.29L4 13L3 13L3 13.5C3 13.78 2.78 14 2.5 14C2.22 14 2 13.78 2 13.5L2 11.5C2 11.22 2.22 11 2.5 11C2.78 11 3 11.22 3 11.5L3 12ZM5.91 15L5 14.09L5 11L10 11L10 15ZM13.22 13.5L15 14.59L15 12.41ZM15.15 15.85C15.24 15.94 15.36 16 15.5 16C15.78 16 16 15.78 16 15.5L16 11.5C16 11.22 15.78 11 15.5 11C15.36 11 15.24 11.06 15.15 11.15L12.04 13.04C11.87 13.12 11.75 13.3 11.75 13.5C11.75 13.7 11.87 13.88 12.04 13.96ZM6.38 4.22C6.02 4.3 5.65 4.17 5.41 3.89C4.95 3.34 4.27 3 3.5 3C2.12 3 1 4.12 1 5.5C1 6.88 2.12 8 3.5 8L10.5 8C12.43 8 14 6.43 14 4.5C14 2.57 12.43 1 10.5 1C8.91 1 7.56 2.06 7.14 3.52C7.04 3.88 6.75 4.15 6.38 4.22ZM6.61 2.24C6.42 2.56 6.28 2.89 6.18 3.25C5.94 2.97 5.67 2.73 5.36 2.54C4.82 2.2 4.18 2 3.5 2C1.57 2 0 3.57 0 5.5C0 7.43 1.57 9 3.5 9L10.5 9C12.99 9 15 6.99 15 4.5C15 2.01 12.99 0 10.5 0C8.84 0 7.38 0.9 6.61 2.24ZM10.5 5C10.78 5 11 4.78 11 4.5C11 4.22 10.78 4 10.5 4C10.22 4 10 4.22 10 4.5C10 4.78 10.22 5 10.5 5ZM10.5 6C11.33 6 12 5.33 12 4.5C12 3.67 11.33 3 10.5 3C9.67 3 9 3.67 9 4.5C9 5.33 9.67 6 10.5 6ZM4.5 5.5C4.5 6.05 4.05 6.5 3.5 6.5C2.95 6.5 2.5 6.05 2.5 5.5C2.5 4.95 2.95 4.5 3.5 4.5C4.05 4.5 4.5 4.95 4.5 5.5Z"
|
|
1235
1226
|
},
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The BLENDER keymap (`@volter/sdk/looks`, a `workspace.keymap`
|
|
3
|
-
* contribution).
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
3
|
+
* contribution). Its chords are this package's `contributes.keybindings`,
|
|
4
|
+
* gated on `volter.keymap == 'blender'`: only where they differ from the
|
|
5
|
+
* editor's own, each Blender's in `scripts/presets/keyconfig/keymap_data/
|
|
6
|
+
* blender_default.py` at the engine's pin (v5.2.0), the file that IS the
|
|
7
|
+
* "Blender" keyconfig. A Volter chord Blender leaves free is bound there to
|
|
8
|
+
* `volter.unboundKey`, so it does nothing here as it does in Blender. W arms
|
|
9
|
+
* `transform.select`, the tool that draws no transform gizmo: Blender's Select
|
|
10
|
+
* Box, which the 3D View keymap puts on W (`blender_default.py:1950-1955`,
|
|
11
|
+
* `op_tool_cycle("builtin.select_box", …)`).
|
|
9
12
|
*/
|
|
10
13
|
import type { KeymapContribution } from '@volter/sdk/looks';
|
|
11
14
|
|
|
@@ -15,51 +18,6 @@ export const keymap: KeymapContribution = {
|
|
|
15
18
|
title: 'Blender',
|
|
16
19
|
description:
|
|
17
20
|
'Blender bindings: W select, G/R/S gizmo modes, X delete, Shift+D duplicate, A select all, Alt+A deselect, . frames.',
|
|
18
|
-
bindings: {
|
|
19
|
-
// `km_view3d`, `:1950-1955` — `op_tool_cycle("builtin.select_box", {"type": 'W'})`,
|
|
20
|
-
// bound whenever select is on the left mouse button, which is the default.
|
|
21
|
-
'transform.select': [{ key: 'w' }],
|
|
22
|
-
'transform.translate': [{ key: 'g' }],
|
|
23
|
-
'transform.rotate': [{ key: 'r' }],
|
|
24
|
-
'transform.scale': [{ key: 's' }],
|
|
25
|
-
// `km_object_mode`, `:4552` — `object.delete` with `use_global: False`.
|
|
26
|
-
// Blender's X raises a confirmation popup and ours does not; the deletion
|
|
27
|
-
// itself is `bpy.ops.object.delete()` either way.
|
|
28
|
-
'edit.delete': [{ key: 'x' }],
|
|
29
|
-
// `km_object_mode`, `:4563` — `object.duplicate_move` on Shift+D. Blender
|
|
30
|
-
// then runs a modal move on the copy; ours leaves it on the original's
|
|
31
|
-
// pose for the gizmo to move, which is what the Outliner and the `.blend`
|
|
32
|
-
// both read back.
|
|
33
|
-
'edit.duplicate': [{ key: 'd', shift: true }],
|
|
34
|
-
'edit.selectAll': [{ key: 'a' }, { key: 'a', mod: true }],
|
|
35
|
-
'edit.deselectAll': [{ key: 'a', alt: true }],
|
|
36
|
-
'viewport.frameSelection': [{ key: '.' }, { key: '', code: 'NumpadDecimal' }],
|
|
37
|
-
'viewport.cyclePivot': [{ key: ',' }],
|
|
38
|
-
// Blender's G is Grab (`transform.translate` above); a 2D view's modes keep their buttons.
|
|
39
|
-
'canvas.panMode': [],
|
|
40
|
-
'canvas.rulerMode': [],
|
|
41
|
-
'canvas.toggleGrid': [],
|
|
42
|
-
// `km_view3d` — `view3d.view_persportho` on NUMPAD_5; Blender has no isometric preset key.
|
|
43
|
-
'view.perspective': [],
|
|
44
|
-
'view.projection': [{ key: '', code: 'Numpad5' }],
|
|
45
|
-
// `km_view3d` — `view3d.view_axis` with Ctrl for the opposite side, `view3d.view_orbit` on
|
|
46
|
-
// NUMPAD_2/4/6/8 (15°, `pad_rot_angle`) and NUMPAD_9 (π), `view3d.view_roll` on Shift+4/6.
|
|
47
|
-
// Blender's Ctrl is the Control key on every platform, macOS included (`ctrl`, not `mod`).
|
|
48
|
-
'view.back': [{ key: '', code: 'Numpad1', ctrl: true }],
|
|
49
|
-
'view.left': [{ key: '', code: 'Numpad3', ctrl: true }],
|
|
50
|
-
'view.bottom': [{ key: '', code: 'Numpad7', ctrl: true }],
|
|
51
|
-
'view.orbitLeft': [{ key: '', code: 'Numpad4' }],
|
|
52
|
-
'view.orbitRight': [{ key: '', code: 'Numpad6' }],
|
|
53
|
-
'view.orbitUp': [{ key: '', code: 'Numpad8' }],
|
|
54
|
-
'view.orbitDown': [{ key: '', code: 'Numpad2' }],
|
|
55
|
-
'view.opposite': [{ key: '', code: 'Numpad9' }],
|
|
56
|
-
'view.rollLeft': [{ key: '', code: 'Numpad4', shift: true }],
|
|
57
|
-
'view.rollRight': [{ key: '', code: 'Numpad6', shift: true }],
|
|
58
|
-
// `view3d.view_all` on HOME; `view3d.zoom` on NUMPAD_PLUS/NUMPAD_MINUS and Ctrl+=/Ctrl+-.
|
|
59
|
-
'view.all': [{ key: '', code: 'Home' }],
|
|
60
|
-
'view.zoomIn': [{ key: '', code: 'NumpadAdd' }, { key: '', code: 'Equal', ctrl: true }],
|
|
61
|
-
'view.zoomOut': [{ key: '', code: 'NumpadSubtract' }, { key: '', code: 'Minus', ctrl: true }],
|
|
62
|
-
},
|
|
63
21
|
// `km_view3d` — `view3d.rotate` on MIDDLEMOUSE and `view3d.move` on Shift+MIDDLEMOUSE; the
|
|
64
22
|
// right button is the context menu's, and Shift+Right places the 3D cursor. The factory
|
|
65
23
|
// rotation is Turntable at 0.4° a pixel (`view_rotate_method`,
|
|
@@ -1462,7 +1462,6 @@
|
|
|
1462
1462
|
},
|
|
1463
1463
|
"GeometryNodeForeachGeometryElementOutput": {
|
|
1464
1464
|
"customSocketOrder": true,
|
|
1465
|
-
"conditional": true,
|
|
1466
1465
|
"items": [
|
|
1467
1466
|
{
|
|
1468
1467
|
"kind": "output",
|
|
@@ -1546,7 +1545,6 @@
|
|
|
1546
1545
|
},
|
|
1547
1546
|
"GeometryNodeGridToPoints": {
|
|
1548
1547
|
"customSocketOrder": true,
|
|
1549
|
-
"conditional": true,
|
|
1550
1548
|
"items": [
|
|
1551
1549
|
{
|
|
1552
1550
|
"kind": "layout"
|
|
@@ -1860,7 +1858,6 @@
|
|
|
1860
1858
|
},
|
|
1861
1859
|
"GeometryNodeTransferAttributes": {
|
|
1862
1860
|
"customSocketOrder": true,
|
|
1863
|
-
"conditional": true,
|
|
1864
1861
|
"items": [
|
|
1865
1862
|
{
|
|
1866
1863
|
"kind": "input",
|
|
@@ -1967,7 +1964,6 @@
|
|
|
1967
1964
|
},
|
|
1968
1965
|
"NodeEvaluateClosure": {
|
|
1969
1966
|
"customSocketOrder": true,
|
|
1970
|
-
"conditional": true,
|
|
1971
1967
|
"items": [
|
|
1972
1968
|
{
|
|
1973
1969
|
"kind": "input",
|
|
@@ -2019,8 +2015,7 @@
|
|
|
2019
2015
|
},
|
|
2020
2016
|
{
|
|
2021
2017
|
"kind": "input",
|
|
2022
|
-
"name": "Weight"
|
|
2023
|
-
"conditional": true
|
|
2018
|
+
"name": "Weight"
|
|
2024
2019
|
},
|
|
2025
2020
|
{
|
|
2026
2021
|
"kind": "panel",
|
|
@@ -2087,8 +2082,7 @@
|
|
|
2087
2082
|
},
|
|
2088
2083
|
{
|
|
2089
2084
|
"kind": "input",
|
|
2090
|
-
"name": "Weight"
|
|
2091
|
-
"conditional": true
|
|
2085
|
+
"name": "Weight"
|
|
2092
2086
|
},
|
|
2093
2087
|
{
|
|
2094
2088
|
"kind": "panel",
|
|
@@ -2144,8 +2138,7 @@
|
|
|
2144
2138
|
},
|
|
2145
2139
|
{
|
|
2146
2140
|
"kind": "input",
|
|
2147
|
-
"name": "Weight"
|
|
2148
|
-
"conditional": true
|
|
2141
|
+
"name": "Weight"
|
|
2149
2142
|
},
|
|
2150
2143
|
{
|
|
2151
2144
|
"kind": "panel",
|
|
@@ -2309,7 +2302,6 @@
|
|
|
2309
2302
|
},
|
|
2310
2303
|
"ShaderNodeRaycast": {
|
|
2311
2304
|
"customSocketOrder": true,
|
|
2312
|
-
"conditional": true,
|
|
2313
2305
|
"items": [
|
|
2314
2306
|
{
|
|
2315
2307
|
"kind": "layout"
|
|
@@ -2375,8 +2367,7 @@
|
|
|
2375
2367
|
},
|
|
2376
2368
|
{
|
|
2377
2369
|
"kind": "input",
|
|
2378
|
-
"name": "Weight"
|
|
2379
|
-
"conditional": true
|
|
2370
|
+
"name": "Weight"
|
|
2380
2371
|
},
|
|
2381
2372
|
{
|
|
2382
2373
|
"kind": "panel",
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EDITOR-BLENDER'S PAGE HALF (`contributes.volter.page`, `docs/adr/plugin-apis.md`): what its
|
|
3
|
+
* `activate` returns is its API, which a package reads with
|
|
4
|
+
* `context.extensions.getExtension('volter.@volter/editor-blender')`. It is the model Play seam
|
|
5
|
+
* this package owns, the parts of the Blender document's bottom area that play's Game panel
|
|
6
|
+
* draws with, and two generic doors onto the document's Blender: its saves, each running a script on the
|
|
7
|
+
* scene it wrote whose files reach the project (and a save asked for where it changed), and an asset library in its preferences. Nothing more. Its type is `@volter/editor-blender/api`'s `BlenderPageApi`. It also
|
|
8
|
+
* registers the package's commands: its session verbs and its views' verbs, and starts Blender as a project that
|
|
9
|
+
* holds a model opens.
|
|
10
|
+
*/
|
|
11
|
+
import { registerVerbCommands, registerViewCommands } from '@volter/sdk/commands';
|
|
12
|
+
import { registerHierarchyKinds } from '@volter/sdk/hierarchy-kind-icon';
|
|
13
|
+
import type { PluginContext } from '@volter/sdk/plugins';
|
|
14
|
+
import { flushModelDocument, heldModelDocument, NODE_VIEW_VERBS, setBlenderAssetLibrary, setModelDocumentSaveScript, startBlenderWithProject, subscribeModelDocumentSaved } from '../host/blender-runtime-host';
|
|
15
|
+
import { registerPlay } from '../src/play-tool';
|
|
16
|
+
import {
|
|
17
|
+
modelPlayMode,
|
|
18
|
+
playModeOf,
|
|
19
|
+
playModeVersion,
|
|
20
|
+
servedModelDocument,
|
|
21
|
+
subscribePlayMode,
|
|
22
|
+
} from '../src/play-mode';
|
|
23
|
+
import { PlayAreaButton, PlayModeSwitch, switchPlayMode } from './blender-play-area';
|
|
24
|
+
import { HEADER_HEIGHT, TIMELINE_CHROME, TIMELINE_THEME } from './blender-timeline-geometry';
|
|
25
|
+
import { TIMELINE_VIEW_VERBS } from './blender-timeline.document';
|
|
26
|
+
import { UV_VIEW_VERBS } from './blender-uv-editor.document';
|
|
27
|
+
import { blenderVerbs } from './blender.verbs';
|
|
28
|
+
import { exportVerbs } from './blender-export.verbs';
|
|
29
|
+
import { gltfExportVerbs } from './blender-gltf-export.verbs';
|
|
30
|
+
|
|
31
|
+
export function activate(context: PluginContext) {
|
|
32
|
+
// The Model documents send Blender's kinds: `blender-*`, each its own Outliner glyph, among them the DATA marks for
|
|
33
|
+
// an object's data, its materials and its shape keys (`tree_element_get_icon_from_id`, `outliner_draw.cc:2479-2610`)
|
|
34
|
+
// and the value-keyed lamp and probe marks and `OUTLINER_DATA_*` family (`:2508-2521`, `:2578-2589`). Only a Model
|
|
35
|
+
// document sends them, and its editor is this package's, so they are registered before any of its modules mount.
|
|
36
|
+
registerHierarchyKinds({
|
|
37
|
+
namespace: 'blender-',
|
|
38
|
+
datablocks: ['blender-material-data', 'blender-shapekey-data', 'blender-texture-data', 'blender-image-data'],
|
|
39
|
+
datablockPrefixes: ['blender-outliner-data-', 'blender-light-', 'blender-lightprobe-'],
|
|
40
|
+
});
|
|
41
|
+
// The session verbs and the views' verbs, as the Code-OSS commands package.json declares.
|
|
42
|
+
registerVerbCommands(context, { ...blenderVerbs, ...exportVerbs, ...gltfExportVerbs });
|
|
43
|
+
registerViewCommands(context, 'blender-node-view', NODE_VIEW_VERBS);
|
|
44
|
+
registerViewCommands(context, 'timeline', TIMELINE_VIEW_VERBS);
|
|
45
|
+
registerViewCommands(context, 'blender-uv-view', UV_VIEW_VERBS);
|
|
46
|
+
// Blender starts as a project with a model opens, so opening a model costs its own file's load.
|
|
47
|
+
context.subscriptions.push({ dispose: startBlenderWithProject() });
|
|
48
|
+
return {
|
|
49
|
+
registerPlay,
|
|
50
|
+
/** The bottom area's mode and the model document it serves. */
|
|
51
|
+
playMode: { modelPlayMode, playModeOf, playModeVersion, servedModelDocument, subscribePlayMode },
|
|
52
|
+
PlayAreaButton,
|
|
53
|
+
PlayModeSwitch,
|
|
54
|
+
switchPlayMode,
|
|
55
|
+
/** The Timeline's chrome, which a panel in the same area draws with. */
|
|
56
|
+
timelineGeometry: { HEADER_HEIGHT, TIMELINE_CHROME, TIMELINE_THEME },
|
|
57
|
+
/** The model document's `.blend` as a file: a script every save runs in its own turn on the scene it wrote, each
|
|
58
|
+
* save that landed it in the project, with what that script did and the carry of its files, a save of the
|
|
59
|
+
* project's `.blend` where Blender holds it changed (`flush`), and the one it holds (`held`). */
|
|
60
|
+
modelDocument: { setSaveScript: setModelDocumentSaveScript, subscribeSaved: subscribeModelDocumentSaved, flush: flushModelDocument, held: heldModelDocument },
|
|
61
|
+
/** An asset library every Blender session has, until its removal. */
|
|
62
|
+
setAssetLibrary: setBlenderAssetLibrary,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -1,140 +1,23 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Blender's MOUSE HINTS, at the left of the status bar (`workspace.status`).
|
|
3
|
-
*
|
|
4
|
-
* Blender's bottom row opens with three tiny mouse glyphs, each with one
|
|
5
|
-
* button lit and the action it performs beside it — `Select`, `Rotate View`,
|
|
6
|
-
* `Options` in the reference (`modeling.png`). It is the one piece of chrome
|
|
7
|
-
* that tells a newcomer what the three buttons DO without a menu.
|
|
8
|
-
*
|
|
9
|
-
* THEY ARE HOVER-DRIVEN THERE, AND ALWAYS-ON HERE. Measured across the whole
|
|
10
|
-
* reference set: `modeling.png` is the ONE frame with any ink left of centre
|
|
11
|
-
* in the status band; `layout`, `modeling-edit-none`, `modeling-edit-all`,
|
|
12
|
-
* `modeling-object-selected`, `sculpting` and `texture-paint` all have ZERO
|
|
13
|
-
* ink before device x 3325 — Blender publishes the keymap for whatever the
|
|
14
|
-
* cursor is over, so a still with the pointer outside the 3D View shows
|
|
15
|
-
* nothing. A still therefore cannot say whether Blender's band is empty by
|
|
16
|
-
* design; it can only say what the hints look like WHEN they are drawn, which
|
|
17
|
-
* is what the geometry below is measured from. Ours are unconditional
|
|
18
|
-
* because the editor has no hover-scoped keymap publisher and inventing one
|
|
19
|
-
* to match a screenshot would be a mechanism with no other reader.
|
|
20
|
-
*
|
|
21
|
-
* THE ACTION NAMES ARE OURS, NOT BLENDER'S. What each button does is a fact
|
|
22
|
-
* about THIS editor, so the labels are read from this editor's own contract,
|
|
23
|
-
* never transcribed from the reference:
|
|
24
|
-
*
|
|
25
|
-
* - the KEYMAP (`keymap-presets.ts`, `@volter/sdk/looks`) binds no
|
|
26
|
-
* mouse buttons and structurally cannot: a `KeyChord` is
|
|
27
|
-
* `{ key, code?, mod?, shift?, alt? }` (`sdk/src/looks.ts:76-82`)
|
|
28
|
-
* with no button field, so no keymap — the Blender one included — can
|
|
29
|
-
* move a mouse binding. Nothing to read there.
|
|
30
|
-
* - so the source is the VIEWPORT's own mouse contract:
|
|
31
|
-
* `editor-viewport.ts:682-686` sets `orbitControls.mouseButtons` to
|
|
32
|
-
* `{ LEFT: -1, MIDDLE: PAN, RIGHT: ROTATE }`, and the left button is left
|
|
33
|
-
* free for the selection raycast (`editor-viewport.ts:3782`,
|
|
34
|
-
* `if (e.button === 0 && !e.altKey)`).
|
|
35
|
-
*
|
|
36
|
-
* That contract is a constant of the host, not of any document, so the table
|
|
37
|
-
* below is a constant too — with the citations above as its provenance. When
|
|
38
|
-
* a surface ever makes a button mean something else, it publishes that and
|
|
39
|
-
* this reads it; it does not get hardcoded here.
|
|
40
|
-
*
|
|
41
|
-
* The inline styles spell the theme tokens directly (`var(--volter-space-5)`,
|
|
42
|
-
* `var(--volter-font-md)`) instead of importing the host's `spaceVar` /
|
|
43
|
-
* `fontSizeVar` handles: `@volter/editor-blender` is pinned at ZERO host imports
|
|
44
|
-
* (`validate-editor-closure.mjs`). See `blender-version.status.tsx`.
|
|
45
|
-
*/
|
|
46
|
-
|
|
47
|
-
export const point = 'workspace.status';
|
|
48
|
-
export const title = 'Mouse hints';
|
|
49
|
-
export const align = 'left';
|
|
50
|
-
/** Front of the contributed band — see the note at the bottom of this file
|
|
51
|
-
* for why that is not the front of the bar. */
|
|
52
|
-
export const order = 0;
|
|
1
|
+
/** Blender-style status hints describe this editor’s viewport mouse bindings: left selects, middle pans, right rotates. They are always visible because there is no hover-scoped keymap publisher. Use SDK theme variables without importing host implementation. The item `mouseHints`, whose name, side and priority are this package's `contributes.volter.views.statusbar` entry. */
|
|
53
2
|
|
|
54
3
|
/** Which button the glyph lights. */
|
|
55
4
|
type MouseButton = 'left' | 'middle' | 'right';
|
|
56
5
|
|
|
57
|
-
/**
|
|
58
|
-
* The viewport's three buttons, in Blender's order. Sourced from
|
|
59
|
-
* `editor-viewport.ts:682-686` + `:3782` (see the header) — the editor's
|
|
60
|
-
* contract, which is why the middle/right labels are Pan/Orbit and not
|
|
61
|
-
* Blender's own Rotate View/Options.
|
|
62
|
-
*/
|
|
6
|
+
/** Viewport bindings in Blender's button order: left selects, middle pans and right orbits. */
|
|
63
7
|
const HINTS: readonly { readonly button: MouseButton; readonly label: string }[] = [
|
|
64
8
|
{ button: 'left', label: 'Select' },
|
|
65
9
|
{ button: 'middle', label: 'Pan View' },
|
|
66
10
|
{ button: 'right', label: 'Orbit View' },
|
|
67
11
|
];
|
|
68
12
|
|
|
69
|
-
/**
|
|
70
|
-
* One mouse glyph in Blender's idiom, TRANSCRIBED FROM THE FRAME rather than
|
|
71
|
-
* estimated: `modeling.png` at native 2x (the only reference frame whose
|
|
72
|
-
* status bar carries the hints — see the note under {@link HINTS}), glyph 1 at
|
|
73
|
-
* device x 12..35, y 2065..2096. Halved, that is a 12 x 16 CSS ink box, and
|
|
74
|
-
* the viewBox IS that box so the SVG needs no scale factor to reason about:
|
|
75
|
-
* one unit is one CSS pixel at the size below.
|
|
76
|
-
*
|
|
77
|
-
* MEASURED BY COVERAGE, NOT BY A THRESHOLD. Every number below is the 50%
|
|
78
|
-
* contour of `(value - 23) / (135 - 23)` — an ink threshold reads a glyph's
|
|
79
|
-
* antialiasing as geometry and got two of these badly wrong on the first cut.
|
|
80
|
-
* Device reading -> CSS (the ink box's left edge is device -0.5, so
|
|
81
|
-
* `css = (device + 0.5) / 2`):
|
|
82
|
-
*
|
|
83
|
-
* shell stroke 1.98 device, flat on every side row -> {@link STROKE} 1
|
|
84
|
-
* button row height 16 of 32 device -> {@link BUTTON_H} 8, exactly half
|
|
85
|
-
* lit button width left fill ends 9.5 / right fill starts 13.5 device
|
|
86
|
-
* -> {@link BUTTON_W} 5, a button at each end
|
|
87
|
-
* divider the shell's top edge stops at 11.98 device in the
|
|
88
|
-
* left-lit glyph and starts there in the right-lit one
|
|
89
|
-
* -> {@link DIVIDER_X} 6.25, ONE boundary, not a gap
|
|
90
|
-
* centred on the middle
|
|
91
|
-
* gap under button 2 device of nothing (fill ends row 15, stroke resumes
|
|
92
|
-
* row 18) -> {@link UNDER_GAP} 1
|
|
93
|
-
* wheel 8 x 14 device at x 8, y 4, ends rounded at r 2.5
|
|
94
|
-
* -> 4 x 7 at 4, 2, rx 1.25
|
|
95
|
-
*
|
|
96
|
-
* THE LIT BUTTON IS NOT AN INSET NUB AND NOT A CLIPPED OVERLAY: in the frame
|
|
97
|
-
* the fill REPLACES the shell over its own quadrant (a mid row reads ten solid
|
|
98
|
-
* device px from the very left edge) and the shell stroke is absent for two
|
|
99
|
-
* device rows beneath it and across the divider on the top edge. So the fill
|
|
100
|
-
* is the outer silhouette intersected with the button rect, and the stroke is
|
|
101
|
-
* MASKED OUT over the button's side of the divider down to the hairline —
|
|
102
|
-
* which is why there is a mask here and not the clip the earlier cut used.
|
|
103
|
-
*
|
|
104
|
-
* (Superseded readings, kept so the next round does not re-derive them: a
|
|
105
|
-
* 13x17 box with a 1.7 stroke and a 0.45 hairline, sized to the text's own
|
|
106
|
-
* font size. Both came from a DPR-1 capture against a downscaled reference,
|
|
107
|
-
* which is the instrument WORK.md records as unable to settle stroke weights;
|
|
108
|
-
* at native 2x the glyph measures 16 CSS tall against 9 CSS of cap height,
|
|
109
|
-
* not the ~1:1 that reading reported.)
|
|
110
|
-
*
|
|
111
|
-
* RESIDUE, measured and not fitted away: Blender's TOP edge integrates 3.0
|
|
112
|
-
* device px of coverage where its sides integrate 1.98. One uniform stroke
|
|
113
|
-
* cannot draw both, and a second stroke for the top would be a constant with
|
|
114
|
-
* no reason behind it; the sides are the ones that set the glyph's weight.
|
|
115
|
-
*/
|
|
13
|
+
/** The mouse glyph’s viewBox uses CSS pixels. Its lit quadrant replaces the shell fill; mask that quadrant’s shell stroke and preserve the divider and gap beneath it. */
|
|
116
14
|
const STROKE = 1;
|
|
117
15
|
const BUTTON_H = 8;
|
|
118
16
|
const BUTTON_W = 5;
|
|
119
17
|
const UNDER_GAP = 1;
|
|
120
|
-
/**
|
|
121
|
-
* Where the shell's top edge stops for a lit LEFT button and starts again for
|
|
122
|
-
* a lit RIGHT one — ONE boundary, at device 11.98 in both glyphs, not a gap
|
|
123
|
-
* straddling the centre. The button fills stop short of it by 1.25 on the left
|
|
124
|
-
* and start 0.75 past it on the right, which is why modelling this as a
|
|
125
|
-
* symmetric "gap" beside a button could not reproduce either glyph.
|
|
126
|
-
*/
|
|
18
|
+
/** Shared shell-stroke boundary; button fills end 1.25 units before it on the left and start 0.75 after it on the right. */
|
|
127
19
|
const DIVIDER_X = 6.25;
|
|
128
|
-
/**
|
|
129
|
-
* The shell's OUTER corner radius, and the number two cuts got wrong — first
|
|
130
|
-
* as a capsule (half the width), then as 2 from an ink threshold. SOLVED on
|
|
131
|
-
* the 50% contour of the middle glyph's bottom-left and top-right corners
|
|
132
|
-
* (`modeling.png`, device x 122..145, y 2065..2096): the contour runs 4.04,
|
|
133
|
-
* 2.39, 1.33, 0.55, 0.05, -0.28 device from the straight edge over successive
|
|
134
|
-
* rows, and a circle of radius 7.16 device reproduces all six to within 0.11.
|
|
135
|
-
* The opposite corner independently gives 7.2. Halved, 3.6 — and both wrong
|
|
136
|
-
* answers were visible at full scale as the wrong SHAPE, not the wrong size.
|
|
137
|
-
*/
|
|
20
|
+
/** The mouse shell’s outer corner radius is 3.6 CSS px. */
|
|
138
21
|
const RADIUS = 3.6;
|
|
139
22
|
/** The ink box, in CSS px, and the viewBox. */
|
|
140
23
|
const BOX = { width: 12, height: 16 } as const;
|
|
@@ -153,9 +36,7 @@ function MouseGlyph({ button }: { button: MouseButton }) {
|
|
|
153
36
|
role="presentation"
|
|
154
37
|
aria-hidden="true"
|
|
155
38
|
focusable="false"
|
|
156
|
-
//
|
|
157
|
-
// exactly 16 CSS tall in the frame. `width: auto` then resolves to 12
|
|
158
|
-
// from the viewBox, the measured width, with no second number to drift.
|
|
39
|
+
// Use the 16-px icon-lg height; the viewBox supplies the 12-px width.
|
|
159
40
|
style={{ height: 'var(--volter-icon-lg)', width: 'auto', display: 'block', flex: '0 0 auto' }}
|
|
160
41
|
>
|
|
161
42
|
<title>{button} mouse button</title>
|
|
@@ -211,23 +92,14 @@ export default function BlenderMouseHintsStatus() {
|
|
|
211
92
|
<span
|
|
212
93
|
className="volter-status-copy"
|
|
213
94
|
data-testid="status-blender-mouse-hints"
|
|
214
|
-
//
|
|
215
|
-
// starts at 61; the second gap repeats it to a tenth). `space-5` is 10.
|
|
95
|
+
// The 10px space-5 token approximates Blender's 9.5px hint gap.
|
|
216
96
|
style={{ display: 'inline-flex', alignItems: 'center', gap: 'var(--volter-space-5)' }}
|
|
217
97
|
>
|
|
218
98
|
{HINTS.map((hint) => (
|
|
219
99
|
<span
|
|
220
100
|
key={hint.button}
|
|
221
101
|
data-mouse-button={hint.button}
|
|
222
|
-
//
|
|
223
|
-
// bar is the DIMMEST text in its window — `#878787` (135) flat, on
|
|
224
|
-
// every frame, against the 216 its top-bar menus ink. A contribution
|
|
225
|
-
// that pins `content.muted` (194) makes itself the loudest thing in
|
|
226
|
-
// the quietest band; inheriting is what keeps the band one voice.
|
|
227
|
-
//
|
|
228
|
-
// The SIZE is measured: Blender's status text caps at 9.0 CSS (18
|
|
229
|
-
// device), which is the `md`/`base` rung (11px -> 8.8), not `sm`
|
|
230
|
-
// (10px -> 8.0).
|
|
102
|
+
// Inherit the status band’s colour and base font size.
|
|
231
103
|
style={{
|
|
232
104
|
display: 'inline-flex',
|
|
233
105
|
alignItems: 'center',
|
|
@@ -243,17 +115,3 @@ export default function BlenderMouseHintsStatus() {
|
|
|
243
115
|
</span>
|
|
244
116
|
);
|
|
245
117
|
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* ORDER, and the one thing this cannot have. Blender's hints are the
|
|
249
|
-
* LEFTMOST thing in its bottom row. A contributed status item cannot be:
|
|
250
|
-
* `tool-loader.ts:1145` registers it at `contributedSectionOrder(order)`,
|
|
251
|
-
* which is `CONTRIBUTED_SECTION_ORDER + (order ?? 999)`
|
|
252
|
-
* (`tool-loader.ts:359-361`) with `CONTRIBUTED_SECTION_ORDER = 5000`
|
|
253
|
-
* (`inspection/model.ts:475`), while the host's own left items register at
|
|
254
|
-
* -90..40 (`components/status-contributions.tsx`). Every contributed item
|
|
255
|
-
* therefore lands BEHIND every core item, by construction — the band exists
|
|
256
|
-
* precisely so a contribution cannot outrank a built-in. `order = 0` is the
|
|
257
|
-
* front of what the contract allows: first among contributed left items,
|
|
258
|
-
* after the host's save/console/play cluster.
|
|
259
|
-
*/
|