@nice2dev/ui-3d 1.1.10 → 1.2.1
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 +173 -0
- package/README.md +11 -15
- package/dist/cjs/core/editorAsk.js +48 -0
- package/dist/cjs/core/i18n.js +7 -3
- package/dist/cjs/core/threeCompat.js +52 -0
- package/dist/cjs/dance/DanceScoreEngine.js +7 -0
- package/dist/cjs/dance/PoseDetector.js +116 -64
- package/dist/cjs/index.js +0 -3
- package/dist/cjs/material/NiceMaterialEditor.js +7 -3
- package/dist/cjs/material/materialEditorTypes.js +1 -1
- package/dist/cjs/material/materialNodeDefinitions.js +14 -11
- package/dist/cjs/model/ModelEditor.js +3 -5
- package/dist/cjs/model/ModelEditorLeftPanel.js +10 -4
- package/dist/cjs/model/ModelEditorMenuBar.js +7 -5
- package/dist/cjs/model/ModelEditorRightPanel.js +18 -16
- package/dist/cjs/model/ModelEditorSubComponents.js +8 -6
- package/dist/cjs/model/ModelEditorTimeline.js +6 -4
- package/dist/cjs/model/ModelEditorToolbar.js +6 -4
- package/dist/cjs/model/ModelEditorViewport.js +16 -6
- package/dist/cjs/model/ModelViewer.js +6 -7
- package/dist/cjs/model/NiceArmatureEditor.js +8 -6
- package/dist/cjs/model/NiceMorphTargetEditor.js +5 -3
- package/dist/cjs/model/NiceOctree.js +3 -1
- package/dist/cjs/model/NicePhysicsSimulation.js +7 -5
- package/dist/cjs/model/NiceProceduralGeometry.js +5 -3
- package/dist/cjs/model/NiceTerrainEditor.js +10 -8
- package/dist/cjs/model/NiceWeightPainter.js +6 -4
- package/dist/cjs/model/NiceXRPreview.js +7 -5
- package/dist/cjs/model/useModelEditor.js +30 -18
- package/dist/cjs/model/useModelViewer.js +13 -9
- package/dist/cjs/nice2dev-ui-3d.css +1 -1
- package/dist/cjs/particle/NiceParticleEditor.js +14 -9
- package/dist/cjs/rendering/NiceCascadedShadows.js +3 -1
- package/dist/cjs/rendering/NiceRenderExport.js +11 -5
- package/dist/cjs/rendering/NiceSSAO.js +3 -1
- package/dist/cjs/rendering/NiceSSR.js +3 -1
- package/dist/cjs/rendering/NiceWebGPURenderer.js +7 -4
- package/dist/cjs/uv/NiceUVEditor.js +16 -12
- package/dist/cjs/uv/uvEditorTypes.js +1 -0
- package/dist/esm/core/editorAsk.js +45 -0
- package/dist/esm/core/i18n.js +8 -4
- package/dist/esm/core/threeCompat.js +30 -0
- package/dist/esm/dance/DanceScoreEngine.js +7 -0
- package/dist/esm/dance/PoseDetector.js +118 -65
- package/dist/esm/index.js +3 -3
- package/dist/esm/material/NiceMaterialEditor.js +7 -3
- package/dist/esm/material/materialEditorTypes.js +1 -1
- package/dist/esm/material/materialNodeDefinitions.js +14 -11
- package/dist/esm/model/ModelEditor.js +4 -5
- package/dist/esm/model/ModelEditorLeftPanel.js +10 -4
- package/dist/esm/model/ModelEditorMenuBar.js +7 -5
- package/dist/esm/model/ModelEditorRightPanel.js +18 -16
- package/dist/esm/model/ModelEditorSubComponents.js +8 -6
- package/dist/esm/model/ModelEditorTimeline.js +6 -4
- package/dist/esm/model/ModelEditorToolbar.js +6 -4
- package/dist/esm/model/ModelEditorViewport.js +16 -6
- package/dist/esm/model/ModelViewer.js +7 -7
- package/dist/esm/model/NiceArmatureEditor.js +9 -7
- package/dist/esm/model/NiceMorphTargetEditor.js +5 -3
- package/dist/esm/model/NiceOctree.js +3 -1
- package/dist/esm/model/NicePhysicsSimulation.js +7 -5
- package/dist/esm/model/NiceProceduralGeometry.js +5 -3
- package/dist/esm/model/NiceTerrainEditor.js +10 -8
- package/dist/esm/model/NiceWeightPainter.js +6 -4
- package/dist/esm/model/NiceXRPreview.js +7 -5
- package/dist/esm/model/useModelEditor.js +30 -18
- package/dist/esm/model/useModelViewer.js +13 -9
- package/dist/esm/nice2dev-ui-3d.css +1 -1
- package/dist/esm/particle/NiceParticleEditor.js +14 -9
- package/dist/esm/rendering/NiceCascadedShadows.js +3 -1
- package/dist/esm/rendering/NiceRenderExport.js +11 -5
- package/dist/esm/rendering/NiceSSAO.js +3 -1
- package/dist/esm/rendering/NiceSSR.js +3 -1
- package/dist/esm/rendering/NiceWebGPURenderer.js +7 -4
- package/dist/esm/uv/NiceUVEditor.js +16 -12
- package/dist/esm/uv/uvEditorTypes.js +1 -0
- package/dist/types/core/editorAsk.d.ts +26 -0
- package/dist/types/core/i18n.d.ts +1 -17
- package/dist/types/core/threeCompat.d.ts +14 -0
- package/dist/types/dance/DanceScoreEngine.d.ts +0 -6
- 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/dist/types/model/useModelEditor.d.ts +3 -0
- package/package.json +3 -3
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
var jsxRuntime = require('react/jsx-runtime');
|
|
4
4
|
var ui = require('@nice2dev/ui');
|
|
5
5
|
var React = require('react');
|
|
6
|
+
var i18n = require('../core/i18n.js');
|
|
6
7
|
|
|
7
8
|
// ── Standard keypoint definitions (COCO 17-keypoint model) ──────
|
|
8
9
|
const COCO_KEYPOINTS = [
|
|
@@ -42,19 +43,15 @@ const SKELETON_CONNECTIONS = [
|
|
|
42
43
|
[12, 14],
|
|
43
44
|
[14, 16], // right leg
|
|
44
45
|
];
|
|
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
|
-
}
|
|
46
|
+
/** A keypoint the detector reported without a score counts as found. */
|
|
47
|
+
const confidenceOf = (kp) => { var _a; return (_a = kp.confidence) !== null && _a !== void 0 ? _a : 1; };
|
|
55
48
|
// ── Hook: usePoseDetection ──────────────────────────────────────
|
|
56
49
|
function usePoseDetection(config = {}) {
|
|
57
|
-
|
|
50
|
+
// The detection loop outlives renders: it reads the latest settings from here.
|
|
51
|
+
const configRef = React.useRef(config);
|
|
52
|
+
React.useEffect(() => {
|
|
53
|
+
configRef.current = config;
|
|
54
|
+
});
|
|
58
55
|
const [isDetecting, setIsDetecting] = React.useState(false);
|
|
59
56
|
const latestFrameRef = React.useRef(null);
|
|
60
57
|
const frameIndexRef = React.useRef(0);
|
|
@@ -62,33 +59,67 @@ function usePoseDetection(config = {}) {
|
|
|
62
59
|
const streamRef = React.useRef(null);
|
|
63
60
|
const videoRef = React.useRef(null);
|
|
64
61
|
const callbackRef = React.useRef(null);
|
|
65
|
-
const
|
|
62
|
+
const errorRef = React.useRef(null);
|
|
63
|
+
const startedAtRef = React.useRef(0);
|
|
64
|
+
const lastRunRef = React.useRef(-Infinity);
|
|
65
|
+
const busyRef = React.useRef(false);
|
|
66
|
+
// Bumped by start() and stop(): a detection that resolves after them is dropped.
|
|
67
|
+
const runRef = React.useRef(0);
|
|
68
|
+
const stop = React.useCallback(() => {
|
|
66
69
|
var _a;
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
runRef.current += 1;
|
|
71
|
+
cancelAnimationFrame(rafRef.current);
|
|
72
|
+
(_a = streamRef.current) === null || _a === void 0 ? void 0 : _a.getTracks().forEach((t) => t.stop());
|
|
73
|
+
streamRef.current = null;
|
|
74
|
+
if (videoRef.current) {
|
|
75
|
+
videoRef.current.srcObject = null;
|
|
76
|
+
}
|
|
77
|
+
setIsDetecting(false);
|
|
78
|
+
}, []);
|
|
79
|
+
const processFrame = React.useCallback(() => {
|
|
80
|
+
rafRef.current = requestAnimationFrame(processFrame);
|
|
81
|
+
const video = videoRef.current;
|
|
82
|
+
const { detector, maxPersons = 1, minConfidence = 0.5, targetFps = 30 } = configRef.current;
|
|
83
|
+
if (!detector || !video || video.readyState < 2 || busyRef.current) {
|
|
69
84
|
return;
|
|
70
85
|
}
|
|
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
|
-
}
|
|
86
|
+
const now = performance.now();
|
|
87
|
+
if (now - lastRunRef.current < 1000 / targetFps) {
|
|
88
|
+
return;
|
|
81
89
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
lastRunRef.current = now;
|
|
91
|
+
busyRef.current = true;
|
|
92
|
+
const run = runRef.current;
|
|
93
|
+
Promise.resolve()
|
|
94
|
+
.then(() => detector(video, now))
|
|
95
|
+
.then((found) => {
|
|
96
|
+
var _a;
|
|
97
|
+
if (run !== runRef.current) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const frame = {
|
|
101
|
+
frame_index: frameIndexRef.current++,
|
|
102
|
+
timestamp_sec: (now - startedAtRef.current) / 1000,
|
|
103
|
+
persons: found
|
|
104
|
+
.filter((person) => person.keypoints.some((kp) => confidenceOf(kp) >= minConfidence))
|
|
105
|
+
.slice(0, maxPersons),
|
|
106
|
+
};
|
|
107
|
+
latestFrameRef.current = frame;
|
|
108
|
+
(_a = callbackRef.current) === null || _a === void 0 ? void 0 : _a.call(callbackRef, frame);
|
|
109
|
+
})
|
|
110
|
+
.catch((err) => {
|
|
111
|
+
var _a;
|
|
112
|
+
if (run !== runRef.current) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
stop();
|
|
116
|
+
(_a = errorRef.current) === null || _a === void 0 ? void 0 : _a.call(errorRef, err instanceof Error ? err : new Error(String(err)));
|
|
117
|
+
})
|
|
118
|
+
.finally(() => {
|
|
119
|
+
busyRef.current = false;
|
|
120
|
+
});
|
|
121
|
+
}, [stop]);
|
|
122
|
+
const start = React.useCallback(async (video, onFrame, onError) => {
|
|
92
123
|
try {
|
|
93
124
|
const stream = await navigator.mediaDevices.getUserMedia({
|
|
94
125
|
video: { facingMode: 'user', width: { ideal: 640 }, height: { ideal: 480 } },
|
|
@@ -98,7 +129,14 @@ function usePoseDetection(config = {}) {
|
|
|
98
129
|
video.srcObject = stream;
|
|
99
130
|
await video.play();
|
|
100
131
|
callbackRef.current = onFrame !== null && onFrame !== void 0 ? onFrame : null;
|
|
132
|
+
errorRef.current = onError !== null && onError !== void 0 ? onError : null;
|
|
101
133
|
frameIndexRef.current = 0;
|
|
134
|
+
startedAtRef.current = performance.now();
|
|
135
|
+
lastRunRef.current = -Infinity;
|
|
136
|
+
runRef.current += 1;
|
|
137
|
+
if (!configRef.current.detector) {
|
|
138
|
+
console.warn('[NicePoseDetector] started without a `detector`: the camera runs, but no poses are detected.');
|
|
139
|
+
}
|
|
102
140
|
setIsDetecting(true);
|
|
103
141
|
rafRef.current = requestAnimationFrame(processFrame);
|
|
104
142
|
}
|
|
@@ -106,23 +144,14 @@ function usePoseDetection(config = {}) {
|
|
|
106
144
|
throw new Error(`Camera access failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
107
145
|
}
|
|
108
146
|
}, [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
147
|
React.useEffect(() => () => {
|
|
120
148
|
stop();
|
|
121
149
|
}, [stop]);
|
|
122
150
|
return { start, stop, isDetecting, getLatestFrame: () => latestFrameRef.current };
|
|
123
151
|
}
|
|
124
152
|
// ── Component: PoseDetector ─────────────────────────────────────
|
|
125
|
-
const NicePoseDetector = React.forwardRef(function NicePoseDetector(props, ref) {
|
|
153
|
+
const NicePoseDetector = /* @__PURE__ */ React.forwardRef(function NicePoseDetector(props, ref) {
|
|
154
|
+
const { t } = i18n.useNiceTranslation();
|
|
126
155
|
const { onFrame, onStart, onStop, onError, className, style, width = 640, height = 480, mirror = true, showSkeleton = true, showConfidence = false, ...config } = props;
|
|
127
156
|
const videoRef = React.useRef(null);
|
|
128
157
|
const canvasRef = React.useRef(null);
|
|
@@ -136,64 +165,90 @@ const NicePoseDetector = React.forwardRef(function NicePoseDetector(props, ref)
|
|
|
136
165
|
},
|
|
137
166
|
}));
|
|
138
167
|
const drawSkeleton = React.useCallback((frame) => {
|
|
139
|
-
var _a
|
|
168
|
+
var _a;
|
|
140
169
|
const ctx = (_a = canvasRef.current) === null || _a === void 0 ? void 0 : _a.getContext('2d');
|
|
141
170
|
if (!ctx || !canvasRef.current) {
|
|
142
171
|
return;
|
|
143
172
|
}
|
|
144
173
|
ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
|
|
174
|
+
// Keypoints are in video pixels; the video fills the box with object-fit: cover.
|
|
175
|
+
const video = videoRef.current;
|
|
176
|
+
const videoW = (video === null || video === void 0 ? void 0 : video.videoWidth) || width;
|
|
177
|
+
const videoH = (video === null || video === void 0 ? void 0 : video.videoHeight) || height;
|
|
178
|
+
const scale = Math.max(width / videoW, height / videoH);
|
|
179
|
+
const offsetX = (width - videoW * scale) / 2;
|
|
180
|
+
const offsetY = (height - videoH * scale) / 2;
|
|
181
|
+
const at = (kp) => [
|
|
182
|
+
kp.x * scale + offsetX,
|
|
183
|
+
kp.y * scale + offsetY,
|
|
184
|
+
];
|
|
145
185
|
for (const person of frame.persons) {
|
|
146
186
|
const kps = person.keypoints;
|
|
187
|
+
const byName = new Map(kps.map((kp) => [kp.name, kp]));
|
|
147
188
|
// Draw connections
|
|
148
189
|
if (showSkeleton) {
|
|
149
190
|
ctx.strokeStyle = ui.resolveCssColor('var(--nice-success, #00ff88)', ctx.canvas);
|
|
150
191
|
ctx.lineWidth = 2;
|
|
151
192
|
for (const [a, b] of SKELETON_CONNECTIONS) {
|
|
152
|
-
const ka =
|
|
193
|
+
const ka = byName.get(COCO_KEYPOINTS[a]);
|
|
194
|
+
const kb = byName.get(COCO_KEYPOINTS[b]);
|
|
153
195
|
if (!ka || !kb) {
|
|
154
196
|
continue;
|
|
155
197
|
}
|
|
156
|
-
if ((
|
|
198
|
+
if (confidenceOf(ka) < 0.3 || confidenceOf(kb) < 0.3) {
|
|
157
199
|
continue;
|
|
158
200
|
}
|
|
159
201
|
ctx.beginPath();
|
|
160
|
-
ctx.moveTo(ka
|
|
161
|
-
ctx.lineTo(kb
|
|
202
|
+
ctx.moveTo(...at(ka));
|
|
203
|
+
ctx.lineTo(...at(kb));
|
|
162
204
|
ctx.stroke();
|
|
163
205
|
}
|
|
164
206
|
}
|
|
165
207
|
// Draw keypoints
|
|
166
208
|
for (const kp of kps) {
|
|
167
|
-
if ((
|
|
209
|
+
if (confidenceOf(kp) < 0.3) {
|
|
168
210
|
continue;
|
|
169
211
|
}
|
|
170
|
-
const radius = showConfidence ? 3 + (
|
|
212
|
+
const radius = showConfidence ? 3 + confidenceOf(kp) * 5 : 4;
|
|
171
213
|
ctx.fillStyle = ui.resolveCssColor(showConfidence
|
|
172
|
-
? `rgba(0, 255, 136, ${(
|
|
214
|
+
? `rgba(0, 255, 136, ${confidenceOf(kp)})`
|
|
173
215
|
: 'var(--nice-success, #00ff88)', ctx.canvas);
|
|
174
216
|
ctx.beginPath();
|
|
175
|
-
ctx.arc(kp
|
|
217
|
+
ctx.arc(...at(kp), radius, 0, 2 * Math.PI);
|
|
176
218
|
ctx.fill();
|
|
177
219
|
}
|
|
178
220
|
}
|
|
179
|
-
}, [showSkeleton, showConfidence]);
|
|
221
|
+
}, [showSkeleton, showConfidence, width, height]);
|
|
222
|
+
// The detection loop is started once; it reaches this render's drawing and callbacks here.
|
|
223
|
+
const latestRef = React.useRef({ drawSkeleton, onFrame, onError });
|
|
224
|
+
React.useEffect(() => {
|
|
225
|
+
latestRef.current = { drawSkeleton, onFrame, onError };
|
|
226
|
+
});
|
|
180
227
|
const handleFrame = React.useCallback((frame) => {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
228
|
+
var _a, _b;
|
|
229
|
+
latestRef.current.drawSkeleton(frame);
|
|
230
|
+
(_b = (_a = latestRef.current).onFrame) === null || _b === void 0 ? void 0 : _b.call(_a, frame);
|
|
231
|
+
}, []);
|
|
232
|
+
const handleDetectorError = React.useCallback((error) => {
|
|
233
|
+
var _a, _b;
|
|
234
|
+
(_b = (_a = latestRef.current).onError) === null || _b === void 0 ? void 0 : _b.call(_a, error);
|
|
235
|
+
}, []);
|
|
184
236
|
const handleStart = React.useCallback(async () => {
|
|
185
237
|
try {
|
|
186
238
|
if (videoRef.current) {
|
|
187
|
-
await detection.start(videoRef.current, handleFrame);
|
|
239
|
+
await detection.start(videoRef.current, handleFrame, handleDetectorError);
|
|
188
240
|
onStart === null || onStart === void 0 ? void 0 : onStart();
|
|
189
241
|
}
|
|
190
242
|
}
|
|
191
243
|
catch (err) {
|
|
192
244
|
onError === null || onError === void 0 ? void 0 : onError(err instanceof Error ? err : new Error(String(err)));
|
|
193
245
|
}
|
|
194
|
-
}, [detection, handleFrame, onStart, onError]);
|
|
246
|
+
}, [detection, handleFrame, handleDetectorError, onStart, onError]);
|
|
195
247
|
const handleStop = React.useCallback(() => {
|
|
248
|
+
var _a;
|
|
196
249
|
detection.stop();
|
|
250
|
+
const canvas = canvasRef.current;
|
|
251
|
+
(_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
252
|
onStop === null || onStop === void 0 ? void 0 : onStop();
|
|
198
253
|
}, [detection, onStop]);
|
|
199
254
|
const containerStyle = {
|
|
@@ -232,11 +287,8 @@ const NicePoseDetector = React.forwardRef(function NicePoseDetector(props, ref)
|
|
|
232
287
|
cursor: 'pointer',
|
|
233
288
|
fontSize: 14,
|
|
234
289
|
fontWeight: 600,
|
|
235
|
-
}, children:
|
|
290
|
+
}, children: t('poseDetector.startCamera', 'Start Camera') }))] }));
|
|
236
291
|
});
|
|
237
|
-
/** @deprecated Use NicePoseDetector */
|
|
238
|
-
const PoseDetector = NicePoseDetector;
|
|
239
292
|
|
|
240
293
|
exports.NicePoseDetector = NicePoseDetector;
|
|
241
|
-
exports.PoseDetector = PoseDetector;
|
|
242
294
|
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;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
var jsxRuntime = require('react/jsx-runtime');
|
|
4
4
|
var ui = require('@nice2dev/ui');
|
|
5
5
|
var React = require('react');
|
|
6
|
+
var i18n = require('../core/i18n.js');
|
|
6
7
|
var MaterialEditor_module = require('./MaterialEditor.module.css.js');
|
|
7
8
|
var materialEditorTypes = require('./materialEditorTypes.js');
|
|
8
9
|
var materialEditorUtils = require('./materialEditorUtils.js');
|
|
@@ -367,6 +368,7 @@ function useMaterialEditor(initialGraph) {
|
|
|
367
368
|
return { state, dispatch };
|
|
368
369
|
}
|
|
369
370
|
function NodePalette({ searchQuery, expandedCategories, onSearch, onToggleCategory, onAddNode, onDragStart, }) {
|
|
371
|
+
const { t } = i18n.useNiceTranslation();
|
|
370
372
|
const filteredNodes = searchQuery ? materialNodeDefinitions.searchNodes(searchQuery) : materialNodeDefinitions.MATERIAL_NODE_DEFINITIONS;
|
|
371
373
|
const categories = React.useMemo(() => {
|
|
372
374
|
const cats = new Map();
|
|
@@ -378,7 +380,7 @@ function NodePalette({ searchQuery, expandedCategories, onSearch, onToggleCatego
|
|
|
378
380
|
}
|
|
379
381
|
return cats;
|
|
380
382
|
}, [filteredNodes]);
|
|
381
|
-
return (jsxRuntime.jsxs("div", { className: MaterialEditor_module.palette, children: [jsxRuntime.jsx("div", { className: MaterialEditor_module.paletteSearch, children: jsxRuntime.jsx("input", { type: "text", placeholder:
|
|
383
|
+
return (jsxRuntime.jsxs("div", { className: MaterialEditor_module.palette, children: [jsxRuntime.jsx("div", { className: MaterialEditor_module.paletteSearch, children: jsxRuntime.jsx("input", { type: "text", placeholder: t('materialEditor.searchNodes', 'Search nodes...'), value: searchQuery, onChange: (e) => onSearch(e.target.value), className: MaterialEditor_module.searchInput }) }), jsxRuntime.jsx("div", { className: MaterialEditor_module.paletteCategories, children: Array.from(categories.entries()).map(([category, nodes]) => (jsxRuntime.jsxs("div", { className: MaterialEditor_module.paletteCategory, children: [jsxRuntime.jsxs("button", { className: MaterialEditor_module.categoryHeader, onClick: () => onToggleCategory(category), style: { borderLeftColor: materialEditorTypes.NODE_CATEGORY_COLORS[category] }, children: [jsxRuntime.jsx("span", { className: MaterialEditor_module.categoryIcon, children: jsxRuntime.jsx(ui.NiceIcon, { name: expandedCategories.includes(category) ? 'chevron-down' : 'chevron-right', size: 14 }) }), jsxRuntime.jsx("span", { children: materialEditorTypes.NODE_CATEGORY_NAMES[category] }), jsxRuntime.jsx("span", { className: MaterialEditor_module.categoryCount, children: nodes.length })] }), expandedCategories.includes(category) && (jsxRuntime.jsx("div", { className: MaterialEditor_module.categoryNodes, children: nodes.map((nodeDef) => (jsxRuntime.jsxs("div", { className: MaterialEditor_module.paletteNode, draggable: true, onDragStart: (e) => onDragStart(nodeDef.type, e), onDoubleClick: () => onAddNode(nodeDef.type), title: nodeDef.description, children: [jsxRuntime.jsx("span", { className: MaterialEditor_module.nodeColor, style: {
|
|
382
384
|
backgroundColor: nodeDef.color || materialEditorTypes.NODE_CATEGORY_COLORS[nodeDef.category],
|
|
383
385
|
} }), jsxRuntime.jsx("span", { className: MaterialEditor_module.nodeName, children: nodeDef.name })] }, nodeDef.type))) }))] }, category))) })] }));
|
|
384
386
|
}
|
|
@@ -454,9 +456,11 @@ function GraphNode({ node, definition, selected, viewTransform, connections, onS
|
|
|
454
456
|
}) })] })), node.comment && jsxRuntime.jsx("div", { className: MaterialEditor_module.nodeComment, children: node.comment })] }));
|
|
455
457
|
}
|
|
456
458
|
function Toolbar({ canUndo, canRedo, canPaste, hasSelection, autoCompile, compiling, showGrid, snapToGrid, onUndo, onRedo, onCopy, onCut, onPaste, onDuplicate, onDelete, onSelectAll, onZoomToFit, onToggleGrid, onToggleSnap, onToggleAutoCompile, onCompile, onNew, onExport, onImport, }) {
|
|
457
|
-
|
|
459
|
+
const { t } = i18n.useNiceTranslation();
|
|
460
|
+
return (jsxRuntime.jsxs("div", { className: MaterialEditor_module.toolbar, children: [jsxRuntime.jsxs("div", { className: MaterialEditor_module.toolbarGroup, children: [jsxRuntime.jsx("button", { onClick: onNew, title: t('materialEditor.newMaterialCtrlN', 'New Material (Ctrl+N)'), "aria-label": t('materialEditor.newMaterial', 'New Material'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "file", size: 16 }) }), jsxRuntime.jsx("button", { onClick: onImport, title: t('materialEditor.importCtrlO', 'Import (Ctrl+O)'), "aria-label": t('materialEditor.import', 'Import'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "folder", size: 16 }) }), jsxRuntime.jsx("button", { onClick: onExport, title: t('materialEditor.exportCtrlS', 'Export (Ctrl+S)'), "aria-label": t('materialEditor.export', 'Export'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "save", size: 16 }) })] }), jsxRuntime.jsx("div", { className: MaterialEditor_module.toolbarSeparator }), jsxRuntime.jsxs("div", { className: MaterialEditor_module.toolbarGroup, children: [jsxRuntime.jsx("button", { onClick: onUndo, disabled: !canUndo, title: t('materialEditor.undoCtrlZ', 'Undo (Ctrl+Z)'), "aria-label": t('materialEditor.undo', 'Undo'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "arrow-left", size: 16 }) }), jsxRuntime.jsx("button", { onClick: onRedo, disabled: !canRedo, title: t('materialEditor.redoCtrlY', 'Redo (Ctrl+Y)'), "aria-label": t('materialEditor.redo', 'Redo'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "arrow-right", size: 16 }) })] }), jsxRuntime.jsx("div", { className: MaterialEditor_module.toolbarSeparator }), jsxRuntime.jsxs("div", { className: MaterialEditor_module.toolbarGroup, children: [jsxRuntime.jsx("button", { onClick: onCut, disabled: !hasSelection, title: t('materialEditor.cutCtrlX', 'Cut (Ctrl+X)'), "aria-label": t('materialEditor.cut', 'Cut'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "scissors", size: 16 }) }), jsxRuntime.jsx("button", { onClick: onCopy, disabled: !hasSelection, title: t('materialEditor.copyCtrlC', 'Copy (Ctrl+C)'), "aria-label": t('materialEditor.copy', 'Copy'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "clipboard", size: 16 }) }), jsxRuntime.jsx("button", { onClick: onPaste, disabled: !canPaste, title: t('materialEditor.pasteCtrlV', 'Paste (Ctrl+V)'), "aria-label": t('materialEditor.paste', 'Paste'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "download", size: 16 }) }), jsxRuntime.jsx("button", { onClick: onDuplicate, disabled: !hasSelection, title: t('materialEditor.duplicateCtrlD', 'Duplicate (Ctrl+D)'), "aria-label": t('materialEditor.duplicate', 'Duplicate'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "refresh", size: 16 }) }), jsxRuntime.jsx("button", { onClick: onDelete, disabled: !hasSelection, title: t('materialEditor.deleteDel', 'Delete (Del)'), "aria-label": t('materialEditor.delete', 'Delete'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "trash", size: 16 }) })] }), jsxRuntime.jsx("div", { className: MaterialEditor_module.toolbarSeparator }), jsxRuntime.jsxs("div", { className: MaterialEditor_module.toolbarGroup, children: [jsxRuntime.jsx("button", { onClick: onSelectAll, title: t('materialEditor.selectAllCtrlA', 'Select All (Ctrl+A)'), "aria-label": t('materialEditor.selectAll', 'Select All'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "square", size: 16 }) }), jsxRuntime.jsx("button", { onClick: onZoomToFit, title: t('materialEditor.zoomToFitF', 'Zoom to Fit (F)'), "aria-label": t('materialEditor.zoomToFit', 'Zoom to Fit'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "search", size: 16 }) }), jsxRuntime.jsx("button", { onClick: onToggleGrid, className: showGrid ? MaterialEditor_module.active : '', title: t('materialEditor.toggleGridG', 'Toggle Grid (G)'), "aria-label": t('materialEditor.toggleGrid', 'Toggle Grid'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "hash", size: 16 }) }), jsxRuntime.jsx("button", { onClick: onToggleSnap, className: snapToGrid ? MaterialEditor_module.active : '', title: t('materialEditor.toggleSnapS', 'Toggle Snap (S)'), "aria-label": t('materialEditor.toggleSnap', 'Toggle Snap'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "target", size: 16 }) })] }), jsxRuntime.jsx("div", { className: MaterialEditor_module.toolbarSeparator }), jsxRuntime.jsxs("div", { className: MaterialEditor_module.toolbarGroup, children: [jsxRuntime.jsx("button", { onClick: onToggleAutoCompile, className: autoCompile ? MaterialEditor_module.active : '', title: t('materialEditor.autoCompile', 'Auto Compile'), "aria-label": t('materialEditor.autoCompile', 'Auto Compile'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "zap", size: 16 }) }), jsxRuntime.jsx("button", { onClick: onCompile, disabled: compiling, title: t('materialEditor.compileF5', 'Compile (F5)'), "aria-label": t('materialEditor.compile', 'Compile'), children: jsxRuntime.jsx(ui.NiceIcon, { name: compiling ? 'clock' : 'play', size: 16 }) })] })] }));
|
|
458
461
|
}
|
|
459
462
|
function PreviewPanel({ shader, mode, compiling, onModeChange }) {
|
|
463
|
+
const { t } = i18n.useNiceTranslation();
|
|
460
464
|
const canvasRef = React.useRef(null);
|
|
461
465
|
const previewModes = ['sphere', 'cube', 'plane', 'cylinder', 'torus'];
|
|
462
466
|
// Simple preview rendering (in real implementation would use Three.js)
|
|
@@ -486,7 +490,7 @@ function PreviewPanel({ shader, mode, compiling, onModeChange }) {
|
|
|
486
490
|
ctx.arc(100, 100, 80, 0, Math.PI * 2);
|
|
487
491
|
ctx.fill();
|
|
488
492
|
}, [shader, mode]);
|
|
489
|
-
return (jsxRuntime.jsxs("div", { className: MaterialEditor_module.previewPanel, children: [jsxRuntime.jsxs("div", { className: MaterialEditor_module.previewHeader, children: [jsxRuntime.jsx("span", { children:
|
|
493
|
+
return (jsxRuntime.jsxs("div", { className: MaterialEditor_module.previewPanel, children: [jsxRuntime.jsxs("div", { className: MaterialEditor_module.previewHeader, children: [jsxRuntime.jsx("span", { children: t('materialEditor.preview', 'Preview') }), jsxRuntime.jsx("select", { value: mode, onChange: (e) => onModeChange(e.target.value), className: MaterialEditor_module.previewModeSelect, children: previewModes.map((m) => (jsxRuntime.jsx("option", { value: m, children: m.charAt(0).toUpperCase() + m.slice(1) }, m))) })] }), jsxRuntime.jsxs("div", { className: MaterialEditor_module.previewCanvas, children: [compiling && (jsxRuntime.jsx("div", { className: MaterialEditor_module.previewCompiling, children: t('materialEditor.compiling', 'Compiling...') })), jsxRuntime.jsx("canvas", { ref: canvasRef, width: 200, height: 200 })] }), (shader === null || shader === void 0 ? void 0 : shader.errors.length) ? (jsxRuntime.jsx("div", { className: MaterialEditor_module.previewErrors, children: shader.errors.slice(0, 3).map((err, i) => (jsxRuntime.jsx("div", { className: MaterialEditor_module.previewError, children: err.message }, i))) })) : null] }));
|
|
490
494
|
}
|
|
491
495
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
492
496
|
Main Component
|
|
@@ -44,7 +44,7 @@ const PORT_TYPE_COLORS = {
|
|
|
44
44
|
vec4: 'var(--nice-warning, #ffc107)',
|
|
45
45
|
color: 'var(--nice-danger, #ff5722)',
|
|
46
46
|
texture2d: 'var(--nice-info, #00bcd4)',
|
|
47
|
-
textureCube: '#03A9F4',
|
|
47
|
+
textureCube: 'var(--nice-color-primary, #03A9F4)',
|
|
48
48
|
sampler: 'var(--nice-accent, #3f51b5)',
|
|
49
49
|
matrix3: 'var(--nice-accent, #673ab7)',
|
|
50
50
|
matrix4: 'var(--nice-accent, #9c27b0)',
|
|
@@ -1452,17 +1452,20 @@ const logicNodes = [
|
|
|
1452
1452
|
EXPORTS
|
|
1453
1453
|
═══════════════════════════════════════════════════════════════════════════ */
|
|
1454
1454
|
/** All built-in node definitions */
|
|
1455
|
-
const MATERIAL_NODE_DEFINITIONS =
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1455
|
+
const MATERIAL_NODE_DEFINITIONS =
|
|
1456
|
+
/* @__PURE__ */ (function () {
|
|
1457
|
+
return [
|
|
1458
|
+
...inputNodes,
|
|
1459
|
+
...outputNodes,
|
|
1460
|
+
...mathNodes,
|
|
1461
|
+
...vectorNodes,
|
|
1462
|
+
...colorNodes,
|
|
1463
|
+
...textureNodes,
|
|
1464
|
+
...proceduralNodes,
|
|
1465
|
+
...utilityNodes,
|
|
1466
|
+
...logicNodes,
|
|
1467
|
+
];
|
|
1468
|
+
})();
|
|
1466
1469
|
/** Node definitions map by type */
|
|
1467
1470
|
const MATERIAL_NODE_MAP = new Map(MATERIAL_NODE_DEFINITIONS.map((def) => [def.type, def]));
|
|
1468
1471
|
/** Get nodes by category */
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var jsxRuntime = require('react/jsx-runtime');
|
|
4
4
|
var ui = require('@nice2dev/ui');
|
|
5
|
+
var editorAsk = require('../core/editorAsk.js');
|
|
5
6
|
var ModelEditor_module = require('./ModelEditor.module.css.js');
|
|
6
7
|
var ModelEditorLeftPanel = require('./ModelEditorLeftPanel.js');
|
|
7
8
|
var ModelEditorMenuBar = require('./ModelEditorMenuBar.js');
|
|
@@ -12,7 +13,7 @@ var modelEditorTypes = require('./modelEditorTypes.js');
|
|
|
12
13
|
var ModelEditorViewport = require('./ModelEditorViewport.js');
|
|
13
14
|
var useModelEditor = require('./useModelEditor.js');
|
|
14
15
|
|
|
15
|
-
const ACCEPT = modelEditorTypes.SUPPORTED_EXTENSIONS.map((e) => `.${e}`).join(',');
|
|
16
|
+
const ACCEPT = /* @__PURE__ */ modelEditorTypes.SUPPORTED_EXTENSIONS.map((e) => `.${e}`).join(',');
|
|
16
17
|
const NiceModelEditor = ({ className, onSaveToLibrary, onAIPoseVideo, }) => {
|
|
17
18
|
const api = useModelEditor.useModelEditor({ onSaveToLibrary, onAIPoseVideo });
|
|
18
19
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("input", { ref: api.fileInputRef, type: "file", accept: ACCEPT, style: { display: 'none' }, onChange: (e) => {
|
|
@@ -29,10 +30,7 @@ const NiceModelEditor = ({ className, onSaveToLibrary, onAIPoseVideo, }) => {
|
|
|
29
30
|
api.mergeAnimationFBX(f);
|
|
30
31
|
}
|
|
31
32
|
e.target.value = '';
|
|
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
|
+
} }), jsxRuntime.jsx("input", { ref: api.videoInputRef, type: "file", accept: "video/*", style: { display: 'none' }, onChange: api.handleAIVideo }), jsxRuntime.jsx(editorAsk.EditorAskDialog, { ask: api.pendingAsk }), 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
34
|
};
|
|
34
|
-
/** @deprecated Use NiceModelEditor */
|
|
35
|
-
const ModelEditor = NiceModelEditor;
|
|
36
35
|
|
|
37
|
-
exports.ModelEditor = ModelEditor;
|
|
38
36
|
exports.NiceModelEditor = NiceModelEditor;
|
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
var jsxRuntime = require('react/jsx-runtime');
|
|
4
4
|
var ui = require('@nice2dev/ui');
|
|
5
5
|
var React = require('react');
|
|
6
|
+
var i18n = require('../core/i18n.js');
|
|
6
7
|
var ModelEditor_module = require('./ModelEditor.module.css.js');
|
|
7
8
|
|
|
8
|
-
const ModelEditorLeftPanel =
|
|
9
|
+
const ModelEditorLeftPanel = /* @__PURE__ */ Object.assign(
|
|
10
|
+
/* @__PURE__ */ React.memo(({ api }) => {
|
|
11
|
+
const { t } = i18n.useNiceTranslation();
|
|
9
12
|
const { sceneTree, selectedId, outlinerSearch, setOutlinerSearch, selectObject, rebuildTree, toggleNodeExpanded, toggleNodeVisibility, } = api;
|
|
10
13
|
const renderTreeNode = (node, depth) => {
|
|
11
14
|
// Outliner search filter
|
|
@@ -39,8 +42,11 @@ const ModelEditorLeftPanel = React.memo(({ api }) => {
|
|
|
39
42
|
toggleNodeVisibility(node);
|
|
40
43
|
}, children: jsxRuntime.jsx(ui.NiceIcon, { name: node.visible ? 'eye' : 'eye-off', size: 14 }) })] }), node.expanded && node.children.map((c) => renderTreeNode(c, depth + 1))] }, node.id));
|
|
41
44
|
};
|
|
42
|
-
return (jsxRuntime.jsx("div", { className: ModelEditor_module.leftPanel, children: jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "clipboard", size: 14 }), "
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
return (jsxRuntime.jsx("div", { className: ModelEditor_module.leftPanel, children: jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "clipboard", size: 14 }), " ", t('modelEditorLeftPanel.outliner', 'Outliner'), jsxRuntime.jsx(ui.NiceButton, { variant: "ghost", size: "sm", onClick: rebuildTree, title: t('modelEditorLeftPanel.refresh', 'Refresh'), "aria-label": t('modelEditorLeftPanel.refreshOutliner', 'Refresh outliner'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "refresh", size: 14 }) })] }), jsxRuntime.jsx("div", { className: ModelEditor_module.outlinerSearch, children: jsxRuntime.jsx(ui.NiceTextInput, { className: ModelEditor_module.propInput, placeholder: t('modelEditorLeftPanel.filter', 'Filter...'), value: outlinerSearch, onChange: (val) => setOutlinerSearch(val), style: { width: '100%', marginBottom: 4 } }) }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelContent, style: { overflowY: 'auto', flex: 1 }, children: [sceneTree.length === 0 && (jsxRuntime.jsx("div", { style: {
|
|
46
|
+
color: 'var(--nice-text-secondary, #666)',
|
|
47
|
+
fontSize: 10,
|
|
48
|
+
padding: '8px 0',
|
|
49
|
+
}, children: t('modelEditorLeftPanel.dropA3dFileTo', 'Drop a 3D file to begin') })), sceneTree.map((n) => renderTreeNode(n, 0))] })] }) }));
|
|
50
|
+
}), { displayName: 'ModelEditorLeftPanel' });
|
|
45
51
|
|
|
46
52
|
module.exports = ModelEditorLeftPanel;
|
|
@@ -3,15 +3,18 @@
|
|
|
3
3
|
var jsxRuntime = require('react/jsx-runtime');
|
|
4
4
|
var ui = require('@nice2dev/ui');
|
|
5
5
|
var React = require('react');
|
|
6
|
+
var i18n = require('../core/i18n.js');
|
|
6
7
|
var ModelEditor_module = require('./ModelEditor.module.css.js');
|
|
7
8
|
|
|
8
|
-
const ModelEditorMenuBar =
|
|
9
|
+
const ModelEditorMenuBar = /* @__PURE__ */ Object.assign(
|
|
10
|
+
/* @__PURE__ */ React.memo(({ api, hasOnSaveToLibrary }) => {
|
|
11
|
+
const { t } = i18n.useNiceTranslation();
|
|
9
12
|
const { editorMode, setEditorMode, fileInputRef, mergeInputRef, addMenuOpen, setAddMenuOpen, addPrimitive, addSceneLight, addCameraObject, addEmpty, selectedNode, duplicateSelected, deleteSelected, exportGLTF, exportOBJ, exportSTL, exportPLY, exportUSDZ, saveToLibrary, clearScene, showGrid, setShowGrid, showAxes, setShowAxes, wireframe, setWireframe, showSkeleton, setShowSkeleton, showLightHelpers, setShowLightHelpers, bgColor, setBgColor, } = api;
|
|
10
13
|
return (jsxRuntime.jsxs("div", { className: ModelEditor_module.menuBar, children: [jsxRuntime.jsx(ui.NiceSelect, { className: ModelEditor_module.modeSelect, value: editorMode, onChange: (val) => setEditorMode(val), options: [
|
|
11
14
|
{ value: 'object', label: 'Object Mode' },
|
|
12
15
|
{ value: 'edit', label: 'Edit Mode' },
|
|
13
16
|
{ value: 'pose', label: 'Pose Mode' },
|
|
14
|
-
] }), jsxRuntime.jsx("div", { className: ModelEditor_module.menuSep }), jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "folder", size: 14 }), " Open
|
|
17
|
+
] }), jsxRuntime.jsx("div", { className: ModelEditor_module.menuSep }), jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "folder", size: 14 }), " ", t('modelEditorMenuBar.open', 'Open')] }), jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: () => { var _a; return (_a = mergeInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, title: t('modelEditorMenuBar.mergeAnimationsFromFbxGltf', 'Merge animations from FBX/GLTF onto current model'), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "plus", size: 14 }), " ", t('modelEditorMenuBar.mergeAnim', 'Merge Anim')] }), jsxRuntime.jsx("div", { className: ModelEditor_module.menuSep }), jsxRuntime.jsxs("div", { className: ModelEditor_module.menuDropdownWrap, children: [jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: () => setAddMenuOpen(addMenuOpen ? null : 'mesh'), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "plus", size: 14 }), " Add ", jsxRuntime.jsx(ui.NiceIcon, { name: "chevron-down", size: 14 })] }), addMenuOpen && (jsxRuntime.jsxs("div", { className: ModelEditor_module.dropdownMenu, children: [jsxRuntime.jsx("div", { className: ModelEditor_module.dropdownTitle, children: t('modelEditorMenuBar.mesh', 'Mesh') }), [
|
|
15
18
|
'cube',
|
|
16
19
|
'sphere',
|
|
17
20
|
'cylinder',
|
|
@@ -25,8 +28,7 @@ const ModelEditorMenuBar = React.memo(({ api, hasOnSaveToLibrary }) => {
|
|
|
25
28
|
'octahedron',
|
|
26
29
|
'tetrahedron',
|
|
27
30
|
'torusKnot',
|
|
28
|
-
].map((t) => (jsxRuntime.jsx("div", { className: ModelEditor_module.dropdownItem, onClick: () => addPrimitive(t), children: t.charAt(0).toUpperCase() + t.slice(1) }, t))), jsxRuntime.jsx("div", { className: ModelEditor_module.dropdownSep }), jsxRuntime.jsx("div", { className: ModelEditor_module.dropdownTitle, children:
|
|
29
|
-
});
|
|
30
|
-
ModelEditorMenuBar.displayName = 'ModelEditorMenuBar';
|
|
31
|
+
].map((t) => (jsxRuntime.jsx("div", { className: ModelEditor_module.dropdownItem, onClick: () => addPrimitive(t), children: t.charAt(0).toUpperCase() + t.slice(1) }, t))), jsxRuntime.jsx("div", { className: ModelEditor_module.dropdownSep }), jsxRuntime.jsx("div", { className: ModelEditor_module.dropdownTitle, children: t('modelEditorMenuBar.light', 'Light') }), ['point', 'spot', 'directional', 'hemisphere'].map((t) => (jsxRuntime.jsxs("div", { className: ModelEditor_module.dropdownItem, onClick: () => addSceneLight(t), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "lightbulb", size: 14 }), " ", t.charAt(0).toUpperCase() + t.slice(1)] }, t))), jsxRuntime.jsx("div", { className: ModelEditor_module.dropdownSep }), jsxRuntime.jsx("div", { className: ModelEditor_module.dropdownTitle, children: t('modelEditorMenuBar.other', 'Other') }), jsxRuntime.jsxs("div", { className: ModelEditor_module.dropdownItem, onClick: addCameraObject, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "camera", size: 14 }), " ", t('modelEditorMenuBar.camera', 'Camera')] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.dropdownItem, onClick: () => addEmpty('arrows'), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "move", size: 14 }), " Empty (Arrows)"] })] }))] }), jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: duplicateSelected, disabled: !selectedNode, title: t('modelEditorMenuBar.duplicateSelectedShiftD', 'Duplicate selected (Shift+D)'), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "clipboard", size: 14 }), " ", t('modelEditorMenuBar.duplicate', 'Duplicate')] }), jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: deleteSelected, disabled: !selectedNode, title: t('modelEditorMenuBar.deleteSelectedDelete', 'Delete selected (Delete)'), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "close", size: 14 }), " ", t('modelEditorMenuBar.delete', 'Delete')] }), jsxRuntime.jsx("div", { className: ModelEditor_module.menuSep }), jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: () => exportGLTF(true), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "save", size: 14 }), " ", t('modelEditorMenuBar.exportGlb', 'Export GLB')] }), jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: () => exportGLTF(false), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "file", size: 14 }), " ", t('modelEditorMenuBar.exportGltf', 'Export GLTF')] }), jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: exportOBJ, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "file", size: 14 }), " ", t('modelEditorMenuBar.exportObj', 'Export OBJ')] }), jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: () => exportSTL(true), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "file", size: 14 }), " ", t('modelEditorMenuBar.exportStl', 'Export STL')] }), jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: () => exportPLY(true), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "file", size: 14 }), " ", t('modelEditorMenuBar.exportPly', 'Export PLY')] }), jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: exportUSDZ, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "file", size: 14 }), " ", t('modelEditorMenuBar.exportUsdz', 'Export USDZ')] }), hasOnSaveToLibrary && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: ModelEditor_module.menuSep }), jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: saveToLibrary, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "box", size: 14 }), ' ', t('modelEditorMenuBar.saveToLibrary', 'Save to Library')] })] })), jsxRuntime.jsx("div", { className: ModelEditor_module.menuSep }), jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: clearScene, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "trash", size: 14 }), " ", t('modelEditorMenuBar.clear', 'Clear')] }), jsxRuntime.jsx("div", { className: ModelEditor_module.menuSep }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showGrid, onChange: () => setShowGrid((v) => !v), label: t('modelEditorMenuBar.grid', 'Grid') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showAxes, onChange: () => setShowAxes((v) => !v), label: t('modelEditorMenuBar.axes', 'Axes') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: wireframe, onChange: () => setWireframe((v) => !v), label: t('modelEditorMenuBar.wire', 'Wire') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showSkeleton, onChange: () => setShowSkeleton((v) => !v), label: t('modelEditorMenuBar.skel', 'Skel') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showLightHelpers, onChange: () => setShowLightHelpers((v) => !v), label: t('modelEditorMenuBar.helpers', 'Helpers') }), jsxRuntime.jsxs("div", { className: ModelEditor_module.menuRight, children: [jsxRuntime.jsx("label", { className: ModelEditor_module.menuLabel, children: "BG" }), jsxRuntime.jsx(ui.NiceColorPicker, { value: bgColor, onChange: (c) => setBgColor(c) })] })] }));
|
|
32
|
+
}), { displayName: 'ModelEditorMenuBar' });
|
|
31
33
|
|
|
32
34
|
module.exports = ModelEditorMenuBar;
|