@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.
Files changed (86) hide show
  1. package/CHANGELOG.md +173 -0
  2. package/README.md +11 -15
  3. package/dist/cjs/core/editorAsk.js +48 -0
  4. package/dist/cjs/core/i18n.js +7 -3
  5. package/dist/cjs/core/threeCompat.js +52 -0
  6. package/dist/cjs/dance/DanceScoreEngine.js +7 -0
  7. package/dist/cjs/dance/PoseDetector.js +116 -64
  8. package/dist/cjs/index.js +0 -3
  9. package/dist/cjs/material/NiceMaterialEditor.js +7 -3
  10. package/dist/cjs/material/materialEditorTypes.js +1 -1
  11. package/dist/cjs/material/materialNodeDefinitions.js +14 -11
  12. package/dist/cjs/model/ModelEditor.js +3 -5
  13. package/dist/cjs/model/ModelEditorLeftPanel.js +10 -4
  14. package/dist/cjs/model/ModelEditorMenuBar.js +7 -5
  15. package/dist/cjs/model/ModelEditorRightPanel.js +18 -16
  16. package/dist/cjs/model/ModelEditorSubComponents.js +8 -6
  17. package/dist/cjs/model/ModelEditorTimeline.js +6 -4
  18. package/dist/cjs/model/ModelEditorToolbar.js +6 -4
  19. package/dist/cjs/model/ModelEditorViewport.js +16 -6
  20. package/dist/cjs/model/ModelViewer.js +6 -7
  21. package/dist/cjs/model/NiceArmatureEditor.js +8 -6
  22. package/dist/cjs/model/NiceMorphTargetEditor.js +5 -3
  23. package/dist/cjs/model/NiceOctree.js +3 -1
  24. package/dist/cjs/model/NicePhysicsSimulation.js +7 -5
  25. package/dist/cjs/model/NiceProceduralGeometry.js +5 -3
  26. package/dist/cjs/model/NiceTerrainEditor.js +10 -8
  27. package/dist/cjs/model/NiceWeightPainter.js +6 -4
  28. package/dist/cjs/model/NiceXRPreview.js +7 -5
  29. package/dist/cjs/model/useModelEditor.js +30 -18
  30. package/dist/cjs/model/useModelViewer.js +13 -9
  31. package/dist/cjs/nice2dev-ui-3d.css +1 -1
  32. package/dist/cjs/particle/NiceParticleEditor.js +14 -9
  33. package/dist/cjs/rendering/NiceCascadedShadows.js +3 -1
  34. package/dist/cjs/rendering/NiceRenderExport.js +11 -5
  35. package/dist/cjs/rendering/NiceSSAO.js +3 -1
  36. package/dist/cjs/rendering/NiceSSR.js +3 -1
  37. package/dist/cjs/rendering/NiceWebGPURenderer.js +7 -4
  38. package/dist/cjs/uv/NiceUVEditor.js +16 -12
  39. package/dist/cjs/uv/uvEditorTypes.js +1 -0
  40. package/dist/esm/core/editorAsk.js +45 -0
  41. package/dist/esm/core/i18n.js +8 -4
  42. package/dist/esm/core/threeCompat.js +30 -0
  43. package/dist/esm/dance/DanceScoreEngine.js +7 -0
  44. package/dist/esm/dance/PoseDetector.js +118 -65
  45. package/dist/esm/index.js +3 -3
  46. package/dist/esm/material/NiceMaterialEditor.js +7 -3
  47. package/dist/esm/material/materialEditorTypes.js +1 -1
  48. package/dist/esm/material/materialNodeDefinitions.js +14 -11
  49. package/dist/esm/model/ModelEditor.js +4 -5
  50. package/dist/esm/model/ModelEditorLeftPanel.js +10 -4
  51. package/dist/esm/model/ModelEditorMenuBar.js +7 -5
  52. package/dist/esm/model/ModelEditorRightPanel.js +18 -16
  53. package/dist/esm/model/ModelEditorSubComponents.js +8 -6
  54. package/dist/esm/model/ModelEditorTimeline.js +6 -4
  55. package/dist/esm/model/ModelEditorToolbar.js +6 -4
  56. package/dist/esm/model/ModelEditorViewport.js +16 -6
  57. package/dist/esm/model/ModelViewer.js +7 -7
  58. package/dist/esm/model/NiceArmatureEditor.js +9 -7
  59. package/dist/esm/model/NiceMorphTargetEditor.js +5 -3
  60. package/dist/esm/model/NiceOctree.js +3 -1
  61. package/dist/esm/model/NicePhysicsSimulation.js +7 -5
  62. package/dist/esm/model/NiceProceduralGeometry.js +5 -3
  63. package/dist/esm/model/NiceTerrainEditor.js +10 -8
  64. package/dist/esm/model/NiceWeightPainter.js +6 -4
  65. package/dist/esm/model/NiceXRPreview.js +7 -5
  66. package/dist/esm/model/useModelEditor.js +30 -18
  67. package/dist/esm/model/useModelViewer.js +13 -9
  68. package/dist/esm/nice2dev-ui-3d.css +1 -1
  69. package/dist/esm/particle/NiceParticleEditor.js +14 -9
  70. package/dist/esm/rendering/NiceCascadedShadows.js +3 -1
  71. package/dist/esm/rendering/NiceRenderExport.js +11 -5
  72. package/dist/esm/rendering/NiceSSAO.js +3 -1
  73. package/dist/esm/rendering/NiceSSR.js +3 -1
  74. package/dist/esm/rendering/NiceWebGPURenderer.js +7 -4
  75. package/dist/esm/uv/NiceUVEditor.js +16 -12
  76. package/dist/esm/uv/uvEditorTypes.js +1 -0
  77. package/dist/types/core/editorAsk.d.ts +26 -0
  78. package/dist/types/core/i18n.d.ts +1 -17
  79. package/dist/types/core/threeCompat.d.ts +14 -0
  80. package/dist/types/dance/DanceScoreEngine.d.ts +0 -6
  81. package/dist/types/dance/PoseDetector.d.ts +14 -13
  82. package/dist/types/index.d.ts +4 -4
  83. package/dist/types/model/ModelEditor.d.ts +0 -4
  84. package/dist/types/model/ModelViewer.d.ts +0 -4
  85. package/dist/types/model/useModelEditor.d.ts +3 -0
  86. 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
- // ── Simulated pose detection (no external dependency) ───────────
46
- function createSimulatedKeypoints() {
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
- const { maxPersons = 1, minConfidence = 0.5, targetFps = 30 } = config;
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 processFrame = React.useCallback(() => {
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
- if (!videoRef.current || videoRef.current.readyState < 2) {
68
- rafRef.current = requestAnimationFrame(processFrame);
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 idx = frameIndexRef.current++;
72
- const timestamp = idx / targetFps;
73
- // Build frame from video analysis (placeholder for actual ML model)
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
- const frame = {
83
- frame_index: idx,
84
- timestamp_sec: timestamp,
85
- persons,
86
- };
87
- latestFrameRef.current = frame;
88
- (_a = callbackRef.current) === null || _a === void 0 ? void 0 : _a.call(callbackRef, frame);
89
- rafRef.current = requestAnimationFrame(processFrame);
90
- }, [maxPersons, minConfidence, targetFps]);
91
- const start = React.useCallback(async (video, onFrame) => {
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, _b, _c, _d, _e, _f;
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 = kps[a], kb = kps[b];
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 (((_b = ka.confidence) !== null && _b !== void 0 ? _b : 0) < 0.3 || ((_c = kb.confidence) !== null && _c !== void 0 ? _c : 0) < 0.3) {
198
+ if (confidenceOf(ka) < 0.3 || confidenceOf(kb) < 0.3) {
157
199
  continue;
158
200
  }
159
201
  ctx.beginPath();
160
- ctx.moveTo(ka.x, ka.y);
161
- ctx.lineTo(kb.x, kb.y);
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 (((_d = kp.confidence) !== null && _d !== void 0 ? _d : 0) < 0.3) {
209
+ if (confidenceOf(kp) < 0.3) {
168
210
  continue;
169
211
  }
170
- const radius = showConfidence ? 3 + ((_e = kp.confidence) !== null && _e !== void 0 ? _e : 0) * 5 : 4;
212
+ const radius = showConfidence ? 3 + confidenceOf(kp) * 5 : 4;
171
213
  ctx.fillStyle = ui.resolveCssColor(showConfidence
172
- ? `rgba(0, 255, 136, ${(_f = kp.confidence) !== null && _f !== void 0 ? _f : 0.5})`
214
+ ? `rgba(0, 255, 136, ${confidenceOf(kp)})`
173
215
  : 'var(--nice-success, #00ff88)', ctx.canvas);
174
216
  ctx.beginPath();
175
- ctx.arc(kp.x, kp.y, radius, 0, 2 * Math.PI);
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
- drawSkeleton(frame);
182
- onFrame === null || onFrame === void 0 ? void 0 : onFrame(frame);
183
- }, [drawSkeleton, onFrame]);
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: "Start Camera" }))] }));
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: "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: {
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
- return (jsxRuntime.jsxs("div", { className: MaterialEditor_module.toolbar, children: [jsxRuntime.jsxs("div", { className: MaterialEditor_module.toolbarGroup, children: [jsxRuntime.jsx("button", { onClick: onNew, title: "New Material (Ctrl+N)", "aria-label": "New Material", children: jsxRuntime.jsx(ui.NiceIcon, { name: "file", size: 16 }) }), jsxRuntime.jsx("button", { onClick: onImport, title: "Import (Ctrl+O)", "aria-label": "Import", children: jsxRuntime.jsx(ui.NiceIcon, { name: "folder", size: 16 }) }), jsxRuntime.jsx("button", { onClick: onExport, title: "Export (Ctrl+S)", "aria-label": "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: "Undo (Ctrl+Z)", "aria-label": "Undo", children: jsxRuntime.jsx(ui.NiceIcon, { name: "arrow-left", size: 16 }) }), jsxRuntime.jsx("button", { onClick: onRedo, disabled: !canRedo, title: "Redo (Ctrl+Y)", "aria-label": "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: "Cut (Ctrl+X)", "aria-label": "Cut", children: jsxRuntime.jsx(ui.NiceIcon, { name: "scissors", size: 16 }) }), jsxRuntime.jsx("button", { onClick: onCopy, disabled: !hasSelection, title: "Copy (Ctrl+C)", "aria-label": "Copy", children: jsxRuntime.jsx(ui.NiceIcon, { name: "clipboard", size: 16 }) }), jsxRuntime.jsx("button", { onClick: onPaste, disabled: !canPaste, title: "Paste (Ctrl+V)", "aria-label": "Paste", children: jsxRuntime.jsx(ui.NiceIcon, { name: "download", size: 16 }) }), jsxRuntime.jsx("button", { onClick: onDuplicate, disabled: !hasSelection, title: "Duplicate (Ctrl+D)", "aria-label": "Duplicate", children: jsxRuntime.jsx(ui.NiceIcon, { name: "refresh", size: 16 }) }), jsxRuntime.jsx("button", { onClick: onDelete, disabled: !hasSelection, title: "Delete (Del)", "aria-label": "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: "Select All (Ctrl+A)", "aria-label": "Select All", children: jsxRuntime.jsx(ui.NiceIcon, { name: "square", size: 16 }) }), jsxRuntime.jsx("button", { onClick: onZoomToFit, title: "Zoom to Fit (F)", "aria-label": "Zoom to Fit", children: jsxRuntime.jsx(ui.NiceIcon, { name: "search", size: 16 }) }), jsxRuntime.jsx("button", { onClick: onToggleGrid, className: showGrid ? MaterialEditor_module.active : '', title: "Toggle Grid (G)", "aria-label": "Toggle Grid", children: jsxRuntime.jsx(ui.NiceIcon, { name: "hash", size: 16 }) }), jsxRuntime.jsx("button", { onClick: onToggleSnap, className: snapToGrid ? MaterialEditor_module.active : '', title: "Toggle Snap (S)", "aria-label": "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: "Auto Compile", "aria-label": "Auto Compile", children: jsxRuntime.jsx(ui.NiceIcon, { name: "zap", size: 16 }) }), jsxRuntime.jsx("button", { onClick: onCompile, disabled: compiling, title: "Compile (F5)", "aria-label": "Compile", children: jsxRuntime.jsx(ui.NiceIcon, { name: compiling ? 'clock' : 'play', size: 16 }) })] })] }));
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: "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: "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] }));
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
- ...inputNodes,
1457
- ...outputNodes,
1458
- ...mathNodes,
1459
- ...vectorNodes,
1460
- ...colorNodes,
1461
- ...textureNodes,
1462
- ...proceduralNodes,
1463
- ...utilityNodes,
1464
- ...logicNodes,
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 = React.memo(({ api }) => {
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 }), " Outliner", jsxRuntime.jsx(ui.NiceButton, { variant: "ghost", size: "sm", onClick: rebuildTree, title: "Refresh", "aria-label": "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: "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: { color: 'var(--nice-text-secondary, #666)', fontSize: 10, padding: '8px 0' }, children: "Drop a 3D file to begin" })), sceneTree.map((n) => renderTreeNode(n, 0))] })] }) }));
43
- });
44
- ModelEditorLeftPanel.displayName = 'ModelEditorLeftPanel';
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 = React.memo(({ api, hasOnSaveToLibrary }) => {
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"] }), 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: "Merge animations from FBX/GLTF onto current model", children: [jsxRuntime.jsx(ui.NiceIcon, { name: "plus", size: 14 }), " 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: "Mesh" }), [
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: "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: "Other" }), jsxRuntime.jsxs("div", { className: ModelEditor_module.dropdownItem, onClick: addCameraObject, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "camera", size: 14 }), " 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: "Duplicate selected (Shift+D)", children: [jsxRuntime.jsx(ui.NiceIcon, { name: "clipboard", size: 14 }), " Duplicate"] }), jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: deleteSelected, disabled: !selectedNode, title: "Delete selected (Delete)", children: [jsxRuntime.jsx(ui.NiceIcon, { name: "close", size: 14 }), " 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 }), " 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 }), " 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 }), " 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 }), " 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 }), " 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 }), " 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 }), " 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 }), " Clear"] }), jsxRuntime.jsx("div", { className: ModelEditor_module.menuSep }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showGrid, onChange: () => setShowGrid((v) => !v), label: "Grid" }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showAxes, onChange: () => setShowAxes((v) => !v), label: "Axes" }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: wireframe, onChange: () => setWireframe((v) => !v), label: "Wire" }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showSkeleton, onChange: () => setShowSkeleton((v) => !v), label: "Skel" }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showLightHelpers, onChange: () => setShowLightHelpers((v) => !v), label: "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) })] })] }));
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;