@volter/editor-blender 0.1.30 → 0.1.32
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.
|
@@ -1511,6 +1511,8 @@ export function blenderOutlinerAuthoringFor(
|
|
|
1511
1511
|
// `matrixWorld`, would sit still with them.
|
|
1512
1512
|
object.updateMatrix();
|
|
1513
1513
|
object.updateMatrixWorld(true);
|
|
1514
|
+
// And every other area drawing the model moves with it, as Blender redraws them all.
|
|
1515
|
+
presented()?.mirrorPose?.(object);
|
|
1514
1516
|
},
|
|
1515
1517
|
endEdit: async (id): Promise<WriteAck | undefined> => {
|
|
1516
1518
|
const object = gestureObjects.get(id) ?? objectFor(id);
|
|
@@ -43,6 +43,9 @@ export interface PresentedView {
|
|
|
43
43
|
blenderSelection(): { readonly selected: readonly string[]; readonly active: string | null };
|
|
44
44
|
/** Where `volter.stage.mode` comes from — see `BlenderRuntimeView.stageMode`. */
|
|
45
45
|
setStageMode(mode: string | null): void;
|
|
46
|
+
/** A pose moved mid-gesture, drawn by the other views of the model — see
|
|
47
|
+
* `BlenderRuntimeView.mirrorPose`. */
|
|
48
|
+
mirrorPose?(object: THREE.Object3D): void;
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
const isPresentedView = (value: unknown): value is PresentedView =>
|
|
@@ -54,6 +54,7 @@ import type {
|
|
|
54
54
|
BlenderRnaView,
|
|
55
55
|
} from '@volter/blender-engine/browser/rna';
|
|
56
56
|
import { useEffect, useState, useSyncExternalStore } from 'react';
|
|
57
|
+
import { ColorPicker, hexToRgb, parseAlpha, toHex } from '@volter/editor-sdk/widgets';
|
|
57
58
|
import {
|
|
58
59
|
type BlenderSubject,
|
|
59
60
|
blenderPropertiesState,
|
|
@@ -414,10 +415,27 @@ function fieldName(row: BlenderRnaRow, index?: number): string {
|
|
|
414
415
|
return index === undefined ? name : `${name} ${arrayItemChar(row.subtype, index) ?? index}`;
|
|
415
416
|
}
|
|
416
417
|
|
|
417
|
-
|
|
418
|
+
/** A colour property's display bytes: a `COLOR` is scene-linear and is shown through the display
|
|
419
|
+
* curve, a `COLOR_GAMMA` already holds display values. */
|
|
420
|
+
function colourBytes(values: readonly number[], gamma: boolean): [number, number, number] {
|
|
418
421
|
const byte = (channel: number) =>
|
|
419
|
-
Math.max(0, Math.min(255, Math.round((values[channel] ?? 0) ** (1 / 2.2) * 255)));
|
|
420
|
-
return
|
|
422
|
+
Math.max(0, Math.min(255, Math.round(Math.max(0, values[channel] ?? 0) ** (gamma ? 1 : 1 / 2.2) * 255)));
|
|
423
|
+
return [byte(0), byte(1), byte(2)];
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function colourOf(values: readonly number[], gamma = false): string {
|
|
427
|
+
const [r, g, b] = colourBytes(values, gamma);
|
|
428
|
+
return `rgb(${r}, ${g}, ${b})`;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/** The picker's colour back as the property's values, alpha kept where the picker has none. */
|
|
432
|
+
function colourValues(color: string, previous: readonly number[], gamma: boolean): number[] {
|
|
433
|
+
const channel = (byte: number) => (gamma ? byte / 255 : (byte / 255) ** 2.2);
|
|
434
|
+
const [r, g, b] = hexToRgb(toHex(color)).map(channel) as [number, number, number];
|
|
435
|
+
const next = [r, g, b];
|
|
436
|
+
const carriesAlpha = /^(rgba|hsla)\(/i.test(color) || /^#[0-9a-f]{8}$/i.test(color);
|
|
437
|
+
if (previous.length > 3) next.push(carriesAlpha ? parseAlpha(color) : (previous[3] ?? 1));
|
|
438
|
+
return next;
|
|
421
439
|
}
|
|
422
440
|
|
|
423
441
|
/** A property's widget, by RNA TYPE — the dispatcher is one `switch` and each
|
|
@@ -484,13 +502,44 @@ function StringWidget({ row, write }: WidgetProps) {
|
|
|
484
502
|
|
|
485
503
|
/** A COLOR subtype is a swatch, not three numbers — `PropertyRNA.subtype` is
|
|
486
504
|
* exactly what says so, and it is why the door carries `subtype` at all. */
|
|
487
|
-
|
|
505
|
+
/** BLENDER'S COLOUR FIELD: the whole-width swatch, and a click opens a picker under it
|
|
506
|
+
* (`ui_block_colorpicker`); the colour is written when the pick ends, one step, as Blender's
|
|
507
|
+
* popup pushes one undo when it closes. */
|
|
508
|
+
function ColourWidget({ row, write }: WidgetProps) {
|
|
488
509
|
const values = (row.value as readonly number[] | null) ?? [];
|
|
510
|
+
const gamma = row.subtype === 'COLOR_GAMMA';
|
|
511
|
+
const [open, setOpen] = useState(false);
|
|
512
|
+
const [r, g, b] = colourBytes(values, gamma);
|
|
513
|
+
const alpha = values.length > 3 ? (values[3] ?? 1) : 1;
|
|
514
|
+
const shown = alpha < 1 ? `rgba(${r}, ${g}, ${b}, ${alpha.toFixed(2)})` : colourOf(values, gamma);
|
|
489
515
|
return (
|
|
490
|
-
<span
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
516
|
+
<span style={{ position: 'relative', display: 'block' }}>
|
|
517
|
+
<button
|
|
518
|
+
type="button"
|
|
519
|
+
aria-label={row.name}
|
|
520
|
+
aria-expanded={open}
|
|
521
|
+
disabled={row.readonly}
|
|
522
|
+
onClick={() => setOpen(!open)}
|
|
523
|
+
style={{
|
|
524
|
+
...fieldStyle,
|
|
525
|
+
background: colourOf(values, gamma),
|
|
526
|
+
display: 'block',
|
|
527
|
+
width: '100%',
|
|
528
|
+
cursor: row.readonly ? 'default' : 'pointer',
|
|
529
|
+
}}
|
|
530
|
+
title={values.map((value) => value.toFixed(3)).join(', ')}
|
|
531
|
+
/>
|
|
532
|
+
{open ? (
|
|
533
|
+
<div style={{ position: 'absolute', zIndex: 'var(--volter-z-dropdown)', top: '100%', right: 0, marginTop: 4 }}>
|
|
534
|
+
<ColorPicker
|
|
535
|
+
value={shown}
|
|
536
|
+
onChange={() => {}}
|
|
537
|
+
onChangeEnd={(color) => write(colourValues(color, values, gamma))}
|
|
538
|
+
onClose={() => setOpen(false)}
|
|
539
|
+
/>
|
|
540
|
+
</div>
|
|
541
|
+
) : null}
|
|
542
|
+
</span>
|
|
494
543
|
);
|
|
495
544
|
}
|
|
496
545
|
|
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.32",
|
|
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.30",
|
|
74
|
+
"@volter/editor-threejs": "0.5.92",
|
|
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.92",
|
|
79
|
+
"@volter/editor-project": "0.5.92",
|
|
80
|
+
"@volter/editor-sdk": "0.5.92",
|
|
81
81
|
"react": "^19.0.0",
|
|
82
82
|
"react-dom": "^19.0.0",
|
|
83
83
|
"three": "^0.180.0",
|