@nice2dev/ui-3d 1.1.9 → 1.2.0
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/CHANGELOG.md +76 -0
- package/README.md +11 -15
- package/dist/cjs/core/threeCompat.js +52 -0
- package/dist/cjs/dance/PoseDetector.js +112 -62
- package/dist/cjs/index.js +0 -3
- package/dist/cjs/model/ModelEditor.js +0 -3
- package/dist/cjs/model/ModelViewer.js +0 -3
- package/dist/cjs/model/NiceArmatureEditor.js +2 -2
- package/dist/cjs/model/NicePhysicsSimulation.js +1 -1
- package/dist/cjs/model/NiceTerrainEditor.js +2 -2
- package/dist/cjs/model/NiceWeightPainter.js +2 -2
- package/dist/cjs/model/NiceXRPreview.js +3 -3
- package/dist/cjs/model/useModelEditor.js +8 -3
- package/dist/cjs/model/useModelViewer.js +13 -9
- package/dist/cjs/rendering/NiceRenderExport.js +2 -2
- package/dist/cjs/rendering/NiceWebGPURenderer.js +3 -2
- package/dist/esm/core/threeCompat.js +30 -0
- package/dist/esm/dance/PoseDetector.js +114 -63
- package/dist/esm/index.js +3 -3
- package/dist/esm/model/ModelEditor.js +1 -3
- package/dist/esm/model/ModelViewer.js +1 -3
- package/dist/esm/model/NiceArmatureEditor.js +3 -3
- package/dist/esm/model/NicePhysicsSimulation.js +1 -1
- package/dist/esm/model/NiceTerrainEditor.js +2 -2
- package/dist/esm/model/NiceWeightPainter.js +2 -2
- package/dist/esm/model/NiceXRPreview.js +3 -3
- package/dist/esm/model/useModelEditor.js +8 -3
- package/dist/esm/model/useModelViewer.js +13 -9
- package/dist/esm/rendering/NiceRenderExport.js +2 -2
- package/dist/esm/rendering/NiceWebGPURenderer.js +3 -2
- package/dist/types/core/threeCompat.d.ts +14 -0
- package/dist/types/dance/PoseDetector.d.ts +14 -13
- package/dist/types/index.d.ts +4 -4
- package/dist/types/model/ModelEditor.d.ts +0 -4
- package/dist/types/model/ModelViewer.d.ts +0 -4
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,81 @@
|
|
|
1
1
|
# @nice2dev/ui-3d — Changelog
|
|
2
2
|
|
|
3
|
+
## 1.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 3a4939d: 1.2.0 — the API cleanup announced for this version, dictionaries per language by default, and
|
|
8
|
+
three new controls. **Breaking:** every removal has a replacement, listed name by name in
|
|
9
|
+
`documentation/omniverk/OMNIVERK_UPGRADE_1.2.0.md`.
|
|
10
|
+
|
|
11
|
+
**Removed (breaking).** All 118 `@deprecated` names: the aliases without the `Nice` prefix in
|
|
12
|
+
`ui-audio`, `ui-graphics` and `ui-3d`; `NiceBarcodeGenerator` (→ `NiceBarcode`), `NiceFloorPlanCanvas`
|
|
13
|
+
(→ `NiceSpatialEditor` in `@nice2dev/ui-buildings`), the v2 props and types of `NiceTopNav`,
|
|
14
|
+
`NiceProgress.showLabel`, `NiceCountryPicker.showDialCode` / `showFlag`, `type` on
|
|
15
|
+
`NiceDateTimePicker` / `NiceDateRangeBox` (→ `dateFormatPreset`); `ZoneOccupancy` in
|
|
16
|
+
`ui-buildings` and `events`, the `SmartHome*` types; the icon variant `flat` (→ `cutout`) and
|
|
17
|
+
`NiceIconEditor` (→ `NiceVectorEditor`); `NiceSizeLegacy`; the `snapGrid` module of `ui-diagrams` (→
|
|
18
|
+
`@nice2dev/spatial-core`); the opt-in stylesheet `@nice2dev/ui/css/legacy-bg-text-aliases.css`, which
|
|
19
|
+
formed an alias cycle with `style.css`. Props that were accepted and never read — 19 on
|
|
20
|
+
`NiceModuleGrid`, and more on `NiceAutocomplete`, `NiceTaskInbox`, `NiceConsentBanner`, the
|
|
21
|
+
building and communication controls, four `ui-audio` panels and `NiceStreamDashboard` — are gone
|
|
22
|
+
from the types. `NiceResourceAllocation` in `ui-erp` is `NiceCapacityPlanner` (the one in `ui`
|
|
23
|
+
keeps its name); `NiceGenericPlayerStageVisualizer` (→ `NiceStageVisualizer`) and
|
|
24
|
+
`NiceCubeBrowserProps` are removed. The `@nice2dev/ui/overlays` entry calls its modal, drawer,
|
|
25
|
+
bottom sheet and context menu `NiceOverlayModal`, `NiceOverlayDrawer`, `NiceOverlayBottomSheet` and
|
|
26
|
+
`NiceOverlayContextMenu`: the old names belonged to different components of `@nice2dev/ui`.
|
|
27
|
+
|
|
28
|
+
**Dictionaries per language.** `NiceI18nProvider` loads only the language in use, with `import()`
|
|
29
|
+
(it was `NiceI18nLazyProvider` in 1.1.10, which stays as the same component); the controls show their
|
|
30
|
+
built-in English until it arrives — `await loadNiceDictionary(lang)` before the first render to start
|
|
31
|
+
in the language. Nothing on the main entry of `@nice2dev/i18n` imports a dictionary any more:
|
|
32
|
+
`NICE_I18N_DICTIONARIES`, `createTranslator`, `NICE_I18N_VARIANTS` / `resolvePlVariant` and the
|
|
33
|
+
translation tooling moved to the new entry `@nice2dev/i18n/all`, and `@nice2dev/ui-core` no longer
|
|
34
|
+
re-exports the dictionary map. `@nice2dev/ui` with what it bundles: 1283 kB → 952 kB gzip loaded up
|
|
35
|
+
front. The `ui-audio` controls read the app's `NiceI18nProvider` now; their own provider overrides it.
|
|
36
|
+
|
|
37
|
+
**New controls.** `NiceRecurrenceEditor`, `NiceStopwatch`, `NiceCountdownTimer` (`@nice2dev/ui`) and
|
|
38
|
+
`NicePasswordGenerator` (`@nice2dev/auth`), plus `recurrenceDates()` and `generatePassword()`.
|
|
39
|
+
`NiceScheduler` honours a weekly rule's `byDay`, which it ignored.
|
|
40
|
+
|
|
41
|
+
**Visible changes.** The palette meets WCAG AA for text on every fill: darker fills in the light
|
|
42
|
+
theme, dark text on the fills of the dark theme (new `--nice-text-on-success` / `-error` / `-info` /
|
|
43
|
+
`-accent` / `-warning`; `NiceThemeText` takes `onSuccess`, `onError`, `onInfo`, `onWarning`). Mobile
|
|
44
|
+
and platform panels follow the app theme instead of `prefers-color-scheme`. Swipes in
|
|
45
|
+
`NiceSwipeActions`, `NiceTouchTable` and `NiceMobileCalendar` work with a mouse and a pen. The
|
|
46
|
+
`ui-forms` editors have the intended spacing (it was four times too large). `NiceMAM`,
|
|
47
|
+
`NiceTranscoder`, `NiceVideoAnnotator` and `NiceStreamDashboard` use `nice-`-prefixed BEM classes.
|
|
48
|
+
|
|
49
|
+
**Fixes.** Controls that invented results ask the host instead: `NiceIntegrationHub`,
|
|
50
|
+
`NiceMLStudio` (`onExperimentRun` may return metrics), `NiceRemoteManagement`
|
|
51
|
+
(`onCaptureScreenshot`), `NicePoseDetector` (`detector`), `NiceBatchScannerProvider`,
|
|
52
|
+
`NiceCacheStrategies`; `NiceLowCodeStudio` accepts dropped components. Controls that did nothing
|
|
53
|
+
work or say why they are off: `NiceStreamDashboard` adds sources, sends chat and runs the chosen
|
|
54
|
+
transition (new `onAddPlatform`, `onSendChatMessage`, `onTransition`, `onScreenshot`,
|
|
55
|
+
`onOpenAudioMixer`, `onOpenSettings`; `receiveChatMessage` feeds incoming chat), `NiceMLStudio`
|
|
56
|
+
downloads through `onModelDownload`, `NiceETLBuilder` saves through `onSave` (its Save button had
|
|
57
|
+
no handler), `NiceTranscoder` makes jobs of picked and dropped files, `NiceAutomationEditor` offers
|
|
58
|
+
the trigger types its model has. Texts of about fifty components go
|
|
59
|
+
through `t()` in six languages, among them the AI data grid, chart and form builders, the ML
|
|
60
|
+
studio, the report designer, the song picker, `NiceProtocolConfig`, `NiceETLBuilder` and
|
|
61
|
+
`NiceInlineHelp` — every key the controls pass to `t()` now has a dictionary entry, so none of
|
|
62
|
+
them stays English under another language; `NiceDataGridAI` sorts on a query in any language, not
|
|
63
|
+
only in English. `NiceWindow`, `NiceConsole`, `NiceFileManager` and `NiceGitManager` no longer
|
|
64
|
+
crash when `accessMode` switches to or from `'hidden'`. Ids come from `createId()` (`@nice2dev/ui-core`), so nothing throws
|
|
65
|
+
outside a secure context as `crypto.randomUUID()` did on plain http. `ui-3d` runs on three r183
|
|
66
|
+
without its deprecation warnings. `NiceRemoteManagement` calls the handlers of the latest render.
|
|
67
|
+
The AI hint popover and the `ui-platform` panels follow the app theme, not the system's or a
|
|
68
|
+
`.dark` class on the host. `NiceCommandPalette` has styles and its search filters;
|
|
69
|
+
`NiceTimeoutFeedback` starts its clock; `NiceOfflineBanner` takes `online`; Monaco's workers in
|
|
70
|
+
`NiceCodeEditor` load; the `ui-audio` panels that used Bootstrap classes use Nice controls; colours
|
|
71
|
+
that reached a canvas as `var(…)` (karaoke lanes and notes) are resolved first.
|
|
72
|
+
|
|
73
|
+
## 1.1.10
|
|
74
|
+
|
|
75
|
+
### Patch Changes
|
|
76
|
+
|
|
77
|
+
- The package description on npm shows a dash instead of the mis-encoded "—".
|
|
78
|
+
|
|
3
79
|
## 1.1.9
|
|
4
80
|
|
|
5
81
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -27,22 +27,18 @@ npm install @nice2dev/ui-3d three
|
|
|
27
27
|
## Quick start
|
|
28
28
|
|
|
29
29
|
```tsx
|
|
30
|
-
import {
|
|
30
|
+
import { NiceModelEditor } from '@nice2dev/ui-3d';
|
|
31
31
|
import '@nice2dev/ui-3d/style.css';
|
|
32
32
|
|
|
33
33
|
function App() {
|
|
34
|
-
return (
|
|
35
|
-
<ModelEditor
|
|
36
|
-
onSaveToLibrary={(dataUrl, name, mimeType) => console.log('save', name)}
|
|
37
|
-
/>
|
|
38
|
-
);
|
|
34
|
+
return <NiceModelEditor onSaveToLibrary={(dataUrl, name, mimeType) => console.log('save', name)} />;
|
|
39
35
|
}
|
|
40
36
|
```
|
|
41
37
|
|
|
42
38
|
## With AI pose-from-video
|
|
43
39
|
|
|
44
40
|
```tsx
|
|
45
|
-
import {
|
|
41
|
+
import { NiceModelEditor, type Pose3DSequenceResult } from '@nice2dev/ui-3d';
|
|
46
42
|
|
|
47
43
|
async function myAiPose(file: File): Promise<Pose3DSequenceResult> {
|
|
48
44
|
const form = new FormData();
|
|
@@ -51,20 +47,20 @@ async function myAiPose(file: File): Promise<Pose3DSequenceResult> {
|
|
|
51
47
|
return res.json();
|
|
52
48
|
}
|
|
53
49
|
|
|
54
|
-
<
|
|
50
|
+
<NiceModelEditor onAIPoseVideo={myAiPose} />;
|
|
55
51
|
```
|
|
56
52
|
|
|
57
53
|
## With i18n
|
|
58
54
|
|
|
59
55
|
```tsx
|
|
60
|
-
import {
|
|
56
|
+
import { NiceModelEditor, NiceI18nProvider } from '@nice2dev/ui-3d';
|
|
61
57
|
import { useTranslation } from 'react-i18next';
|
|
62
58
|
|
|
63
59
|
function App() {
|
|
64
60
|
const { t } = useTranslation();
|
|
65
61
|
return (
|
|
66
62
|
<NiceI18nProvider t={(key, fallback) => t(key, fallback)}>
|
|
67
|
-
<
|
|
63
|
+
<NiceModelEditor />
|
|
68
64
|
</NiceI18nProvider>
|
|
69
65
|
);
|
|
70
66
|
}
|
|
@@ -72,11 +68,11 @@ function App() {
|
|
|
72
68
|
|
|
73
69
|
## Props
|
|
74
70
|
|
|
75
|
-
| Prop
|
|
76
|
-
|
|
77
|
-
| `className`
|
|
78
|
-
| `onSaveToLibrary` | `(dataUrl, name, mimeType) => void`
|
|
79
|
-
| `onAIPoseVideo`
|
|
71
|
+
| Prop | Type | Default | Description |
|
|
72
|
+
| ----------------- | ----------------------------------------------- | ------- | ------------------------------------------ |
|
|
73
|
+
| `className` | `string` | — | CSS class added to root element |
|
|
74
|
+
| `onSaveToLibrary` | `(dataUrl, name, mimeType) => void` | — | Called when user saves render |
|
|
75
|
+
| `onAIPoseVideo` | `(file: File) => Promise<Pose3DSequenceResult>` | — | AI pose callback; button hidden if omitted |
|
|
80
76
|
|
|
81
77
|
## Build
|
|
82
78
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var THREE = require('three');
|
|
4
|
+
|
|
5
|
+
function _interopNamespaceDefault(e) {
|
|
6
|
+
var n = Object.create(null);
|
|
7
|
+
if (e) {
|
|
8
|
+
Object.keys(e).forEach(function (k) {
|
|
9
|
+
if (k !== 'default') {
|
|
10
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
11
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
get: function () { return e[k]; }
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
n.default = e;
|
|
19
|
+
return Object.freeze(n);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
var THREE__namespace = /*#__PURE__*/_interopNamespaceDefault(THREE);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* three.js r183 deprecated `Clock` (for `Timer`) and folds `PCFSoftShadowMap` into
|
|
26
|
+
* `PCFShadowMap`; both log a console warning there. The peer range starts at r150, which has
|
|
27
|
+
* neither `Timer` in its core nor the softer `PCFShadowMap`, so the choice follows the revision.
|
|
28
|
+
*/
|
|
29
|
+
const REVISION = Number.parseInt(THREE__namespace.REVISION, 10);
|
|
30
|
+
const MODERN = REVISION >= 183;
|
|
31
|
+
/** Soft shadows without the r183 deprecation warning; older three keeps `PCFSoftShadowMap`. */
|
|
32
|
+
const SOFT_SHADOW_MAP = MODERN
|
|
33
|
+
? THREE__namespace.PCFShadowMap
|
|
34
|
+
: THREE__namespace.PCFSoftShadowMap;
|
|
35
|
+
/** `Timer` where three has deprecated `Clock`, `Clock` before. */
|
|
36
|
+
function createFrameClock() {
|
|
37
|
+
const TimerCtor = THREE__namespace.Timer;
|
|
38
|
+
if (MODERN && TimerCtor) {
|
|
39
|
+
const timer = new TimerCtor();
|
|
40
|
+
return {
|
|
41
|
+
getDelta: () => {
|
|
42
|
+
timer.update();
|
|
43
|
+
return timer.getDelta();
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const clock = new THREE__namespace.Clock();
|
|
48
|
+
return { getDelta: () => clock.getDelta() };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
exports.SOFT_SHADOW_MAP = SOFT_SHADOW_MAP;
|
|
52
|
+
exports.createFrameClock = createFrameClock;
|
|
@@ -42,19 +42,15 @@ const SKELETON_CONNECTIONS = [
|
|
|
42
42
|
[12, 14],
|
|
43
43
|
[14, 16], // right leg
|
|
44
44
|
];
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return COCO_KEYPOINTS.map((name, _i) => ({
|
|
48
|
-
name,
|
|
49
|
-
x: 0,
|
|
50
|
-
y: 0,
|
|
51
|
-
z: 0,
|
|
52
|
-
confidence: 0,
|
|
53
|
-
}));
|
|
54
|
-
}
|
|
45
|
+
/** A keypoint the detector reported without a score counts as found. */
|
|
46
|
+
const confidenceOf = (kp) => { var _a; return (_a = kp.confidence) !== null && _a !== void 0 ? _a : 1; };
|
|
55
47
|
// ── Hook: usePoseDetection ──────────────────────────────────────
|
|
56
48
|
function usePoseDetection(config = {}) {
|
|
57
|
-
|
|
49
|
+
// The detection loop outlives renders: it reads the latest settings from here.
|
|
50
|
+
const configRef = React.useRef(config);
|
|
51
|
+
React.useEffect(() => {
|
|
52
|
+
configRef.current = config;
|
|
53
|
+
});
|
|
58
54
|
const [isDetecting, setIsDetecting] = React.useState(false);
|
|
59
55
|
const latestFrameRef = React.useRef(null);
|
|
60
56
|
const frameIndexRef = React.useRef(0);
|
|
@@ -62,33 +58,67 @@ function usePoseDetection(config = {}) {
|
|
|
62
58
|
const streamRef = React.useRef(null);
|
|
63
59
|
const videoRef = React.useRef(null);
|
|
64
60
|
const callbackRef = React.useRef(null);
|
|
65
|
-
const
|
|
61
|
+
const errorRef = React.useRef(null);
|
|
62
|
+
const startedAtRef = React.useRef(0);
|
|
63
|
+
const lastRunRef = React.useRef(-Infinity);
|
|
64
|
+
const busyRef = React.useRef(false);
|
|
65
|
+
// Bumped by start() and stop(): a detection that resolves after them is dropped.
|
|
66
|
+
const runRef = React.useRef(0);
|
|
67
|
+
const stop = React.useCallback(() => {
|
|
66
68
|
var _a;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
runRef.current += 1;
|
|
70
|
+
cancelAnimationFrame(rafRef.current);
|
|
71
|
+
(_a = streamRef.current) === null || _a === void 0 ? void 0 : _a.getTracks().forEach((t) => t.stop());
|
|
72
|
+
streamRef.current = null;
|
|
73
|
+
if (videoRef.current) {
|
|
74
|
+
videoRef.current.srcObject = null;
|
|
75
|
+
}
|
|
76
|
+
setIsDetecting(false);
|
|
77
|
+
}, []);
|
|
78
|
+
const processFrame = React.useCallback(() => {
|
|
79
|
+
rafRef.current = requestAnimationFrame(processFrame);
|
|
80
|
+
const video = videoRef.current;
|
|
81
|
+
const { detector, maxPersons = 1, minConfidence = 0.5, targetFps = 30 } = configRef.current;
|
|
82
|
+
if (!detector || !video || video.readyState < 2 || busyRef.current) {
|
|
69
83
|
return;
|
|
70
84
|
}
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
// In production, this calls MediaPipe/TensorFlow.js pose estimation
|
|
75
|
-
const persons = [];
|
|
76
|
-
for (let p = 0; p < maxPersons; p++) {
|
|
77
|
-
const keypoints = createSimulatedKeypoints();
|
|
78
|
-
if (keypoints.some((kp) => { var _a; return ((_a = kp.confidence) !== null && _a !== void 0 ? _a : 0) >= minConfidence; })) {
|
|
79
|
-
persons.push({ id: p, keypoints });
|
|
80
|
-
}
|
|
85
|
+
const now = performance.now();
|
|
86
|
+
if (now - lastRunRef.current < 1000 / targetFps) {
|
|
87
|
+
return;
|
|
81
88
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
lastRunRef.current = now;
|
|
90
|
+
busyRef.current = true;
|
|
91
|
+
const run = runRef.current;
|
|
92
|
+
Promise.resolve()
|
|
93
|
+
.then(() => detector(video, now))
|
|
94
|
+
.then((found) => {
|
|
95
|
+
var _a;
|
|
96
|
+
if (run !== runRef.current) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const frame = {
|
|
100
|
+
frame_index: frameIndexRef.current++,
|
|
101
|
+
timestamp_sec: (now - startedAtRef.current) / 1000,
|
|
102
|
+
persons: found
|
|
103
|
+
.filter((person) => person.keypoints.some((kp) => confidenceOf(kp) >= minConfidence))
|
|
104
|
+
.slice(0, maxPersons),
|
|
105
|
+
};
|
|
106
|
+
latestFrameRef.current = frame;
|
|
107
|
+
(_a = callbackRef.current) === null || _a === void 0 ? void 0 : _a.call(callbackRef, frame);
|
|
108
|
+
})
|
|
109
|
+
.catch((err) => {
|
|
110
|
+
var _a;
|
|
111
|
+
if (run !== runRef.current) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
stop();
|
|
115
|
+
(_a = errorRef.current) === null || _a === void 0 ? void 0 : _a.call(errorRef, err instanceof Error ? err : new Error(String(err)));
|
|
116
|
+
})
|
|
117
|
+
.finally(() => {
|
|
118
|
+
busyRef.current = false;
|
|
119
|
+
});
|
|
120
|
+
}, [stop]);
|
|
121
|
+
const start = React.useCallback(async (video, onFrame, onError) => {
|
|
92
122
|
try {
|
|
93
123
|
const stream = await navigator.mediaDevices.getUserMedia({
|
|
94
124
|
video: { facingMode: 'user', width: { ideal: 640 }, height: { ideal: 480 } },
|
|
@@ -98,7 +128,14 @@ function usePoseDetection(config = {}) {
|
|
|
98
128
|
video.srcObject = stream;
|
|
99
129
|
await video.play();
|
|
100
130
|
callbackRef.current = onFrame !== null && onFrame !== void 0 ? onFrame : null;
|
|
131
|
+
errorRef.current = onError !== null && onError !== void 0 ? onError : null;
|
|
101
132
|
frameIndexRef.current = 0;
|
|
133
|
+
startedAtRef.current = performance.now();
|
|
134
|
+
lastRunRef.current = -Infinity;
|
|
135
|
+
runRef.current += 1;
|
|
136
|
+
if (!configRef.current.detector) {
|
|
137
|
+
console.warn('[NicePoseDetector] started without a `detector`: the camera runs, but no poses are detected.');
|
|
138
|
+
}
|
|
102
139
|
setIsDetecting(true);
|
|
103
140
|
rafRef.current = requestAnimationFrame(processFrame);
|
|
104
141
|
}
|
|
@@ -106,16 +143,6 @@ function usePoseDetection(config = {}) {
|
|
|
106
143
|
throw new Error(`Camera access failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
107
144
|
}
|
|
108
145
|
}, [processFrame]);
|
|
109
|
-
const stop = React.useCallback(() => {
|
|
110
|
-
var _a;
|
|
111
|
-
cancelAnimationFrame(rafRef.current);
|
|
112
|
-
(_a = streamRef.current) === null || _a === void 0 ? void 0 : _a.getTracks().forEach((t) => t.stop());
|
|
113
|
-
streamRef.current = null;
|
|
114
|
-
if (videoRef.current) {
|
|
115
|
-
videoRef.current.srcObject = null;
|
|
116
|
-
}
|
|
117
|
-
setIsDetecting(false);
|
|
118
|
-
}, []);
|
|
119
146
|
React.useEffect(() => () => {
|
|
120
147
|
stop();
|
|
121
148
|
}, [stop]);
|
|
@@ -136,64 +163,90 @@ const NicePoseDetector = React.forwardRef(function NicePoseDetector(props, ref)
|
|
|
136
163
|
},
|
|
137
164
|
}));
|
|
138
165
|
const drawSkeleton = React.useCallback((frame) => {
|
|
139
|
-
var _a
|
|
166
|
+
var _a;
|
|
140
167
|
const ctx = (_a = canvasRef.current) === null || _a === void 0 ? void 0 : _a.getContext('2d');
|
|
141
168
|
if (!ctx || !canvasRef.current) {
|
|
142
169
|
return;
|
|
143
170
|
}
|
|
144
171
|
ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
|
|
172
|
+
// Keypoints are in video pixels; the video fills the box with object-fit: cover.
|
|
173
|
+
const video = videoRef.current;
|
|
174
|
+
const videoW = (video === null || video === void 0 ? void 0 : video.videoWidth) || width;
|
|
175
|
+
const videoH = (video === null || video === void 0 ? void 0 : video.videoHeight) || height;
|
|
176
|
+
const scale = Math.max(width / videoW, height / videoH);
|
|
177
|
+
const offsetX = (width - videoW * scale) / 2;
|
|
178
|
+
const offsetY = (height - videoH * scale) / 2;
|
|
179
|
+
const at = (kp) => [
|
|
180
|
+
kp.x * scale + offsetX,
|
|
181
|
+
kp.y * scale + offsetY,
|
|
182
|
+
];
|
|
145
183
|
for (const person of frame.persons) {
|
|
146
184
|
const kps = person.keypoints;
|
|
185
|
+
const byName = new Map(kps.map((kp) => [kp.name, kp]));
|
|
147
186
|
// Draw connections
|
|
148
187
|
if (showSkeleton) {
|
|
149
188
|
ctx.strokeStyle = ui.resolveCssColor('var(--nice-success, #00ff88)', ctx.canvas);
|
|
150
189
|
ctx.lineWidth = 2;
|
|
151
190
|
for (const [a, b] of SKELETON_CONNECTIONS) {
|
|
152
|
-
const ka =
|
|
191
|
+
const ka = byName.get(COCO_KEYPOINTS[a]);
|
|
192
|
+
const kb = byName.get(COCO_KEYPOINTS[b]);
|
|
153
193
|
if (!ka || !kb) {
|
|
154
194
|
continue;
|
|
155
195
|
}
|
|
156
|
-
if ((
|
|
196
|
+
if (confidenceOf(ka) < 0.3 || confidenceOf(kb) < 0.3) {
|
|
157
197
|
continue;
|
|
158
198
|
}
|
|
159
199
|
ctx.beginPath();
|
|
160
|
-
ctx.moveTo(ka
|
|
161
|
-
ctx.lineTo(kb
|
|
200
|
+
ctx.moveTo(...at(ka));
|
|
201
|
+
ctx.lineTo(...at(kb));
|
|
162
202
|
ctx.stroke();
|
|
163
203
|
}
|
|
164
204
|
}
|
|
165
205
|
// Draw keypoints
|
|
166
206
|
for (const kp of kps) {
|
|
167
|
-
if ((
|
|
207
|
+
if (confidenceOf(kp) < 0.3) {
|
|
168
208
|
continue;
|
|
169
209
|
}
|
|
170
|
-
const radius = showConfidence ? 3 + (
|
|
210
|
+
const radius = showConfidence ? 3 + confidenceOf(kp) * 5 : 4;
|
|
171
211
|
ctx.fillStyle = ui.resolveCssColor(showConfidence
|
|
172
|
-
? `rgba(0, 255, 136, ${(
|
|
212
|
+
? `rgba(0, 255, 136, ${confidenceOf(kp)})`
|
|
173
213
|
: 'var(--nice-success, #00ff88)', ctx.canvas);
|
|
174
214
|
ctx.beginPath();
|
|
175
|
-
ctx.arc(kp
|
|
215
|
+
ctx.arc(...at(kp), radius, 0, 2 * Math.PI);
|
|
176
216
|
ctx.fill();
|
|
177
217
|
}
|
|
178
218
|
}
|
|
179
|
-
}, [showSkeleton, showConfidence]);
|
|
219
|
+
}, [showSkeleton, showConfidence, width, height]);
|
|
220
|
+
// The detection loop is started once; it reaches this render's drawing and callbacks here.
|
|
221
|
+
const latestRef = React.useRef({ drawSkeleton, onFrame, onError });
|
|
222
|
+
React.useEffect(() => {
|
|
223
|
+
latestRef.current = { drawSkeleton, onFrame, onError };
|
|
224
|
+
});
|
|
180
225
|
const handleFrame = React.useCallback((frame) => {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
226
|
+
var _a, _b;
|
|
227
|
+
latestRef.current.drawSkeleton(frame);
|
|
228
|
+
(_b = (_a = latestRef.current).onFrame) === null || _b === void 0 ? void 0 : _b.call(_a, frame);
|
|
229
|
+
}, []);
|
|
230
|
+
const handleDetectorError = React.useCallback((error) => {
|
|
231
|
+
var _a, _b;
|
|
232
|
+
(_b = (_a = latestRef.current).onError) === null || _b === void 0 ? void 0 : _b.call(_a, error);
|
|
233
|
+
}, []);
|
|
184
234
|
const handleStart = React.useCallback(async () => {
|
|
185
235
|
try {
|
|
186
236
|
if (videoRef.current) {
|
|
187
|
-
await detection.start(videoRef.current, handleFrame);
|
|
237
|
+
await detection.start(videoRef.current, handleFrame, handleDetectorError);
|
|
188
238
|
onStart === null || onStart === void 0 ? void 0 : onStart();
|
|
189
239
|
}
|
|
190
240
|
}
|
|
191
241
|
catch (err) {
|
|
192
242
|
onError === null || onError === void 0 ? void 0 : onError(err instanceof Error ? err : new Error(String(err)));
|
|
193
243
|
}
|
|
194
|
-
}, [detection, handleFrame, onStart, onError]);
|
|
244
|
+
}, [detection, handleFrame, handleDetectorError, onStart, onError]);
|
|
195
245
|
const handleStop = React.useCallback(() => {
|
|
246
|
+
var _a;
|
|
196
247
|
detection.stop();
|
|
248
|
+
const canvas = canvasRef.current;
|
|
249
|
+
(_a = canvas === null || canvas === void 0 ? void 0 : canvas.getContext('2d')) === null || _a === void 0 ? void 0 : _a.clearRect(0, 0, canvas.width, canvas.height);
|
|
197
250
|
onStop === null || onStop === void 0 ? void 0 : onStop();
|
|
198
251
|
}, [detection, onStop]);
|
|
199
252
|
const containerStyle = {
|
|
@@ -234,9 +287,6 @@ const NicePoseDetector = React.forwardRef(function NicePoseDetector(props, ref)
|
|
|
234
287
|
fontWeight: 600,
|
|
235
288
|
}, children: "Start Camera" }))] }));
|
|
236
289
|
});
|
|
237
|
-
/** @deprecated Use NicePoseDetector */
|
|
238
|
-
const PoseDetector = NicePoseDetector;
|
|
239
290
|
|
|
240
291
|
exports.NicePoseDetector = NicePoseDetector;
|
|
241
|
-
exports.PoseDetector = PoseDetector;
|
|
242
292
|
exports.usePoseDetection = usePoseDetection;
|
package/dist/cjs/index.js
CHANGED
|
@@ -39,9 +39,7 @@ var NiceRenderExport = require('./rendering/NiceRenderExport.js');
|
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
exports.ModelEditor = ModelEditor.ModelEditor;
|
|
43
42
|
exports.NiceModelEditor = ModelEditor.NiceModelEditor;
|
|
44
|
-
exports.ModelViewer = ModelViewer.ModelViewer;
|
|
45
43
|
exports.NiceModelViewer = ModelViewer.NiceModelViewer;
|
|
46
44
|
exports.DEFAULT_CAD_SETTINGS = cadModeUtils.DEFAULT_CAD_SETTINGS;
|
|
47
45
|
exports.FLOOR_MATERIALS = cadModeUtils.FLOOR_MATERIALS;
|
|
@@ -101,7 +99,6 @@ exports.subclip = modelEditorUtils.subclip;
|
|
|
101
99
|
exports.toThreeKeyframeTrack = modelEditorUtils.toThreeKeyframeTrack;
|
|
102
100
|
exports.EditorShortcutManager = editorShortcuts.EditorShortcutManager;
|
|
103
101
|
exports.NicePoseDetector = PoseDetector.NicePoseDetector;
|
|
104
|
-
exports.PoseDetector = PoseDetector.PoseDetector;
|
|
105
102
|
exports.usePoseDetection = PoseDetector.usePoseDetection;
|
|
106
103
|
exports.DanceScoreEngine = DanceScoreEngine.DanceScoreEngine;
|
|
107
104
|
exports.useDanceScore = DanceScoreEngine.useDanceScore;
|
|
@@ -31,8 +31,5 @@ const NiceModelEditor = ({ className, onSaveToLibrary, onAIPoseVideo, }) => {
|
|
|
31
31
|
e.target.value = '';
|
|
32
32
|
} }), jsxRuntime.jsx("input", { ref: api.videoInputRef, type: "file", accept: "video/*", style: { display: 'none' }, onChange: api.handleAIVideo }), jsxRuntime.jsx(ui.NiceAppShell, { className: `${ModelEditor_module.root} ${className !== null && className !== void 0 ? className : ''}`, flush: true, menubar: jsxRuntime.jsx(ModelEditorMenuBar, { api: api, hasOnSaveToLibrary: !!onSaveToLibrary }), sidebar: jsxRuntime.jsxs("div", { style: { display: 'flex', minHeight: 0 }, children: [jsxRuntime.jsx(ModelEditorLeftPanel, { api: api }), jsxRuntime.jsx(ModelEditorToolbar, { api: api })] }), rightSidebar: jsxRuntime.jsx(ModelEditorRightPanel, { api: api }), bottomPanel: jsxRuntime.jsx(ModelEditorTimeline, { api: api }), statusBar: jsxRuntime.jsxs("div", { className: ModelEditor_module.statusBar, children: [jsxRuntime.jsx("span", { children: api.statusText }), jsxRuntime.jsxs("span", { style: { marginLeft: 'auto' }, children: [api.polyCount.toLocaleString(), " triangles \u00B7 ", api.meshCount, " meshes \u00B7 ", api.boneCount, ' ', "bones \u00B7 ", api.animations.length, " anim(s)"] })] }), children: jsxRuntime.jsx(ModelEditorViewport, { api: api }) })] }));
|
|
33
33
|
};
|
|
34
|
-
/** @deprecated Use NiceModelEditor */
|
|
35
|
-
const ModelEditor = NiceModelEditor;
|
|
36
34
|
|
|
37
|
-
exports.ModelEditor = ModelEditor;
|
|
38
35
|
exports.NiceModelEditor = NiceModelEditor;
|
|
@@ -62,8 +62,5 @@ const NiceModelViewer = ({ className, showToolbar = true, showStatusBar = true,
|
|
|
62
62
|
a.click();
|
|
63
63
|
}, title: "Screenshot", "aria-label": "Screenshot", children: jsxRuntime.jsx(ui.NiceIcon, { name: "camera", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: "ghost", size: "sm", onClick: api.toggleFullscreen, title: "Fullscreen", "aria-label": "Fullscreen", children: jsxRuntime.jsx(ui.NiceIcon, { name: api.isFullscreen ? 'collapse' : 'expand', size: 16 }) }), toolbarExtra, showInfoPanel && (jsxRuntime.jsx("div", { className: ModelViewer_module.toolRight, children: jsxRuntime.jsx(ui.NiceButton, { className: `${ModelViewer_module.toolBtn} ${panelOpen ? ModelViewer_module.active : ''}`, variant: panelOpen ? 'primary' : 'ghost', size: "sm", onClick: () => setPanelOpen(!panelOpen), title: "Info panel", "aria-label": "Info panel", children: jsxRuntime.jsx(ui.NiceIcon, { name: "info", size: 16 }) }) }))] })), jsxRuntime.jsxs("div", { className: ModelViewer_module.viewport, ref: api.mountRef, tabIndex: 0, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, children: [api.loading && (jsxRuntime.jsxs("div", { className: ModelViewer_module.loadingOverlay, children: [jsxRuntime.jsx("span", { className: ModelViewer_module.loadingText, children: "Loading model\u2026" }), jsxRuntime.jsx("div", { className: ModelViewer_module.loadingBar, children: jsxRuntime.jsx("div", { className: ModelViewer_module.loadingBarFill, style: { width: `${(api.loadProgress * 100).toFixed(0)}%` } }) })] })), api.error && (jsxRuntime.jsxs("div", { className: ModelViewer_module.errorOverlay, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "warning", size: 28 }), jsxRuntime.jsx("span", { style: { marginTop: 8 }, children: api.error })] })), dragOver && jsxRuntime.jsx("div", { className: ModelViewer_module.dropZone, children: "Drop 3D model file here" })] }), jsxRuntime.jsxs("div", { className: `${ModelViewer_module.sidePanel} ${!panelOpen ? ModelViewer_module.hidden : ''}`, children: [jsxRuntime.jsxs("div", { className: ModelViewer_module.panelSection, children: [jsxRuntime.jsx("div", { className: ModelViewer_module.panelTitle, children: "Model Info" }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: "Triangles" }), jsxRuntime.jsx("span", { children: api.polyCount.toLocaleString() })] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: "Meshes" }), jsxRuntime.jsx("span", { children: api.meshCount })] }), api.dimensions && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: "Width" }), jsxRuntime.jsx("span", { children: api.dimensions.width })] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: "Height" }), jsxRuntime.jsx("span", { children: api.dimensions.height })] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: "Depth" }), jsxRuntime.jsx("span", { children: api.dimensions.depth })] })] }))] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelSection, children: [jsxRuntime.jsx("div", { className: ModelViewer_module.panelTitle, children: "Navigation" }), navModes.map((m) => (jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: api.navigationMode === m ? 'primary' : 'ghost', size: "sm", onClick: () => api.setNavigationMode(m), children: jsxRuntime.jsx(NavLabel, { mode: m }) }, m)))] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelSection, children: [jsxRuntime.jsx("div", { className: ModelViewer_module.panelTitle, children: "Auto Rotate" }), jsxRuntime.jsx(ui.NiceSlider, { min: 0, max: 10, step: 0.5, value: api.autoRotateSpeed, onChange: (val) => api.setAutoRotateSpeed(val), style: { width: '100%' } })] }), api.animations.length > 0 && (jsxRuntime.jsxs("div", { className: ModelViewer_module.panelSection, children: [jsxRuntime.jsx("div", { className: ModelViewer_module.panelTitle, children: "Animations" }), api.animations.map((name, i) => (jsxRuntime.jsxs(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: api.activeAnimIdx === i ? 'primary' : 'ghost', size: "sm", onClick: () => api.playAnimation(i), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "play", size: 14 }), " ", name] }, i)))] }))] }), jsxRuntime.jsxs("div", { className: `${ModelViewer_module.animBar} ${api.animations.length === 0 ? ModelViewer_module.hidden : ''}`, children: [jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: "ghost", size: "sm", onClick: api.togglePlay, "aria-label": "Play/Pause", children: jsxRuntime.jsx(ui.NiceIcon, { name: api.isPlaying ? 'pause' : 'play', size: 16 }) }), jsxRuntime.jsx(ui.NiceSelect, { className: ModelViewer_module.animSelect, value: String(api.activeAnimIdx), onChange: (val) => api.playAnimation(parseInt(val)), options: api.animations.map((n, i) => ({ value: String(i), label: n })) }), jsxRuntime.jsx(ui.NiceSlider, { className: ModelViewer_module.animSlider, min: 0, max: api.animDuration || 1, step: 0.01, value: api.animTime, onChange: (val) => api.seekAnim(val) }), jsxRuntime.jsxs("span", { className: ModelViewer_module.toolLabel, children: [api.animTime.toFixed(1), "s / ", api.animDuration.toFixed(1), "s"] })] }), showStatusBar && (jsxRuntime.jsxs("div", { className: ModelViewer_module.statusBar, children: [jsxRuntime.jsx("span", { children: api.statusText }), jsxRuntime.jsxs("div", { className: ModelViewer_module.statusRight, children: [jsxRuntime.jsx("span", { children: jsxRuntime.jsx(NavLabel, { mode: api.navigationMode }) }), api.dimensions && (jsxRuntime.jsxs("span", { children: [api.dimensions.width, "\u00D7", api.dimensions.height, "\u00D7", api.dimensions.depth] }))] })] })), children === null || children === void 0 ? void 0 : children(api)] }));
|
|
64
64
|
};
|
|
65
|
-
/** @deprecated Use NiceModelViewer */
|
|
66
|
-
const ModelViewer = NiceModelViewer;
|
|
67
65
|
|
|
68
|
-
exports.ModelViewer = ModelViewer;
|
|
69
66
|
exports.NiceModelViewer = NiceModelViewer;
|
|
@@ -166,7 +166,7 @@ function useArmatureEditor(skeleton, scene, onBoneSelect) {
|
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
const pose = {
|
|
169
|
-
id:
|
|
169
|
+
id: ui.createId(),
|
|
170
170
|
name,
|
|
171
171
|
timestamp: Date.now(),
|
|
172
172
|
boneTransforms: transforms,
|
|
@@ -243,7 +243,7 @@ const NiceArmatureEditor = ({ skeleton, skinnedMesh: _skinnedMesh, scene, camera
|
|
|
243
243
|
padding: '4px 8px',
|
|
244
244
|
paddingLeft: depth * 16 + 8,
|
|
245
245
|
background: isSelected ? 'var(--nice-color-accent, #6366f1)' : 'transparent',
|
|
246
|
-
color: isSelected ? 'var(--nice-
|
|
246
|
+
color: isSelected ? 'var(--nice-color-white, #fff)' : 'inherit',
|
|
247
247
|
cursor: 'pointer',
|
|
248
248
|
fontSize: 13,
|
|
249
249
|
borderRadius: 4,
|
|
@@ -289,7 +289,7 @@ const NicePhysicsSimulation = ({ scene, selectedObject, onSimulationStep, classN
|
|
|
289
289
|
return (jsxRuntime.jsxs("div", { className: `nice-physics-simulation ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' }, children: [jsxRuntime.jsxs("strong", { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "zap", size: 14 }), " Physics"] }), jsxRuntime.jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 4, fontSize: 11 }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: debugDraw, onChange: (e) => setDebugDraw(e.target.checked) }), "Debug"] })] }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4 }, children: [jsxRuntime.jsx("button", { type: "button", onClick: isRunning ? pause : play, style: {
|
|
290
290
|
flex: 1,
|
|
291
291
|
background: isRunning ? 'var(--nice-danger, #ef4444)' : 'var(--nice-success, #22c55e)',
|
|
292
|
-
color: 'var(--nice-
|
|
292
|
+
color: 'var(--nice-color-white, #fff)',
|
|
293
293
|
border: 'none',
|
|
294
294
|
borderRadius: 4,
|
|
295
295
|
padding: '5px 8px',
|
|
@@ -364,12 +364,12 @@ const NiceTerrainEditor = ({ scene, camera, canvas, config: configProp, onTerrai
|
|
|
364
364
|
borderRadius: 4,
|
|
365
365
|
border: '1px solid var(--nice-border, #e2e8f0)',
|
|
366
366
|
background: brush.mode === m ? 'var(--nice-color-accent, #6366f1)' : 'transparent',
|
|
367
|
-
color: brush.mode === m ? 'var(--nice-
|
|
367
|
+
color: brush.mode === m ? 'var(--nice-color-white, #fff)' : 'inherit',
|
|
368
368
|
}, children: m }, m)))] }), jsxRuntime.jsxs("label", { children: ["Radius: ", brush.radius.toFixed(1), jsxRuntime.jsx("input", { type: "range", min: 0.5, max: 20, step: 0.5, value: brush.radius, onChange: (e) => setBrush({ radius: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Strength: ", brush.strength.toFixed(2), jsxRuntime.jsx("input", { type: "range", min: 0.01, max: 1, step: 0.01, value: brush.strength, onChange: (e) => setBrush({ strength: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsx("button", { type: "button", onClick: isPainting ? stopPainting : startPainting, style: {
|
|
369
369
|
background: isPainting
|
|
370
370
|
? 'var(--nice-danger, #ef4444)'
|
|
371
371
|
: 'var(--nice-color-accent, #6366f1)',
|
|
372
|
-
color: 'var(--nice-
|
|
372
|
+
color: 'var(--nice-color-white, #fff)',
|
|
373
373
|
border: 'none',
|
|
374
374
|
borderRadius: 4,
|
|
375
375
|
padding: '6px 12px',
|
|
@@ -260,7 +260,7 @@ const NiceWeightPainter = ({ skinnedMesh, activeBoneIndex: initialBoneIndex, sce
|
|
|
260
260
|
flex: 1,
|
|
261
261
|
padding: '4px 6px',
|
|
262
262
|
background: brush.mode === mode ? 'var(--nice-color-accent, #6366f1)' : 'transparent',
|
|
263
|
-
color: brush.mode === mode ? 'var(--nice-
|
|
263
|
+
color: brush.mode === mode ? 'var(--nice-color-white, #fff)' : 'inherit',
|
|
264
264
|
border: '1px solid var(--nice-border, #e2e8f0)',
|
|
265
265
|
borderRadius: 4,
|
|
266
266
|
cursor: 'pointer',
|
|
@@ -270,7 +270,7 @@ const NiceWeightPainter = ({ skinnedMesh, activeBoneIndex: initialBoneIndex, sce
|
|
|
270
270
|
background: isPainting
|
|
271
271
|
? 'var(--nice-danger, #ef4444)'
|
|
272
272
|
: 'var(--nice-color-accent, #6366f1)',
|
|
273
|
-
color: 'var(--nice-
|
|
273
|
+
color: 'var(--nice-color-white, #fff)',
|
|
274
274
|
border: 'none',
|
|
275
275
|
borderRadius: 4,
|
|
276
276
|
padding: '6px 12px',
|
|
@@ -292,7 +292,7 @@ const NiceXRPreview = ({ renderer, scene, camera, mode: initialMode, onSessionSt
|
|
|
292
292
|
borderRadius: 4,
|
|
293
293
|
cursor: 'pointer',
|
|
294
294
|
background: mode === 'vr' ? 'var(--nice-color-accent, #6366f1)' : 'transparent',
|
|
295
|
-
color: mode === 'vr' ? 'var(--nice-
|
|
295
|
+
color: mode === 'vr' ? 'var(--nice-color-white, #fff)' : 'inherit',
|
|
296
296
|
border: '1px solid var(--nice-border, #e2e8f0)',
|
|
297
297
|
}, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "eye", size: 14 }), " VR"] }), jsxRuntime.jsxs("button", { type: "button", onClick: () => setMode('ar'), style: {
|
|
298
298
|
flex: 1,
|
|
@@ -300,13 +300,13 @@ const NiceXRPreview = ({ renderer, scene, camera, mode: initialMode, onSessionSt
|
|
|
300
300
|
borderRadius: 4,
|
|
301
301
|
cursor: 'pointer',
|
|
302
302
|
background: mode === 'ar' ? 'var(--nice-color-accent, #6366f1)' : 'transparent',
|
|
303
|
-
color: mode === 'ar' ? 'var(--nice-
|
|
303
|
+
color: mode === 'ar' ? 'var(--nice-color-white, #fff)' : 'inherit',
|
|
304
304
|
border: '1px solid var(--nice-border, #e2e8f0)',
|
|
305
305
|
}, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "window", size: 14 }), " AR"] })] }), jsxRuntime.jsx("button", { type: "button", onClick: isSessionActive ? endSession : startSession, style: {
|
|
306
306
|
background: isSessionActive
|
|
307
307
|
? 'var(--nice-danger, #ef4444)'
|
|
308
308
|
: 'var(--nice-success, #22c55e)',
|
|
309
|
-
color: 'var(--nice-
|
|
309
|
+
color: 'var(--nice-color-white, #fff)',
|
|
310
310
|
border: 'none',
|
|
311
311
|
borderRadius: 4,
|
|
312
312
|
padding: '6px 12px',
|