@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
@@ -1,6 +1,7 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import { NiceTutorialButton, resolveTutorial, NiceAppShell, NiceIcon, resolveCssColor } from '@nice2dev/ui';
3
3
  import { useRef, useState, useMemo, useEffect, useCallback, useReducer } from 'react';
4
+ import { useNiceTranslation } from '../core/i18n.js';
4
5
  import styles from './MaterialEditor.module.css.js';
5
6
  import { MAX_ZOOM, MIN_ZOOM, NODE_WIDTH, NODE_HEADER_HEIGHT, PORT_HEIGHT, PORT_TYPE_COLORS, NODE_CATEGORY_COLORS, NODE_CATEGORY_NAMES, DEFAULT_GRID_SIZE } from './materialEditorTypes.js';
6
7
  import { compileShader, screenToGraph, importGraphFromJSON, exportGraphToJSON, updateNodeInGraph, calculateZoomToFit, cloneNode, generateNodeId, removeNodesFromGraph, removeConnectionFromGraph, getNodeDefinition, canConnect, createConnection, addConnectionToGraph, moveNodesInGraph, createNode, addNodeToGraph, createEmptyGraph } from './materialEditorUtils.js';
@@ -365,6 +366,7 @@ function useMaterialEditor(initialGraph) {
365
366
  return { state, dispatch };
366
367
  }
367
368
  function NodePalette({ searchQuery, expandedCategories, onSearch, onToggleCategory, onAddNode, onDragStart, }) {
369
+ const { t } = useNiceTranslation();
368
370
  const filteredNodes = searchQuery ? searchNodes(searchQuery) : MATERIAL_NODE_DEFINITIONS;
369
371
  const categories = useMemo(() => {
370
372
  const cats = new Map();
@@ -376,7 +378,7 @@ function NodePalette({ searchQuery, expandedCategories, onSearch, onToggleCatego
376
378
  }
377
379
  return cats;
378
380
  }, [filteredNodes]);
379
- return (jsxs("div", { className: styles.palette, children: [jsx("div", { className: styles.paletteSearch, children: jsx("input", { type: "text", placeholder: "Search nodes...", value: searchQuery, onChange: (e) => onSearch(e.target.value), className: styles.searchInput }) }), jsx("div", { className: styles.paletteCategories, children: Array.from(categories.entries()).map(([category, nodes]) => (jsxs("div", { className: styles.paletteCategory, children: [jsxs("button", { className: styles.categoryHeader, onClick: () => onToggleCategory(category), style: { borderLeftColor: NODE_CATEGORY_COLORS[category] }, children: [jsx("span", { className: styles.categoryIcon, children: jsx(NiceIcon, { name: expandedCategories.includes(category) ? 'chevron-down' : 'chevron-right', size: 14 }) }), jsx("span", { children: NODE_CATEGORY_NAMES[category] }), jsx("span", { className: styles.categoryCount, children: nodes.length })] }), expandedCategories.includes(category) && (jsx("div", { className: styles.categoryNodes, children: nodes.map((nodeDef) => (jsxs("div", { className: styles.paletteNode, draggable: true, onDragStart: (e) => onDragStart(nodeDef.type, e), onDoubleClick: () => onAddNode(nodeDef.type), title: nodeDef.description, children: [jsx("span", { className: styles.nodeColor, style: {
381
+ return (jsxs("div", { className: styles.palette, children: [jsx("div", { className: styles.paletteSearch, children: jsx("input", { type: "text", placeholder: t('materialEditor.searchNodes', 'Search nodes...'), value: searchQuery, onChange: (e) => onSearch(e.target.value), className: styles.searchInput }) }), jsx("div", { className: styles.paletteCategories, children: Array.from(categories.entries()).map(([category, nodes]) => (jsxs("div", { className: styles.paletteCategory, children: [jsxs("button", { className: styles.categoryHeader, onClick: () => onToggleCategory(category), style: { borderLeftColor: NODE_CATEGORY_COLORS[category] }, children: [jsx("span", { className: styles.categoryIcon, children: jsx(NiceIcon, { name: expandedCategories.includes(category) ? 'chevron-down' : 'chevron-right', size: 14 }) }), jsx("span", { children: NODE_CATEGORY_NAMES[category] }), jsx("span", { className: styles.categoryCount, children: nodes.length })] }), expandedCategories.includes(category) && (jsx("div", { className: styles.categoryNodes, children: nodes.map((nodeDef) => (jsxs("div", { className: styles.paletteNode, draggable: true, onDragStart: (e) => onDragStart(nodeDef.type, e), onDoubleClick: () => onAddNode(nodeDef.type), title: nodeDef.description, children: [jsx("span", { className: styles.nodeColor, style: {
380
382
  backgroundColor: nodeDef.color || NODE_CATEGORY_COLORS[nodeDef.category],
381
383
  } }), jsx("span", { className: styles.nodeName, children: nodeDef.name })] }, nodeDef.type))) }))] }, category))) })] }));
382
384
  }
@@ -452,9 +454,11 @@ function GraphNode({ node, definition, selected, viewTransform, connections, onS
452
454
  }) })] })), node.comment && jsx("div", { className: styles.nodeComment, children: node.comment })] }));
453
455
  }
454
456
  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, }) {
455
- return (jsxs("div", { className: styles.toolbar, children: [jsxs("div", { className: styles.toolbarGroup, children: [jsx("button", { onClick: onNew, title: "New Material (Ctrl+N)", "aria-label": "New Material", children: jsx(NiceIcon, { name: "file", size: 16 }) }), jsx("button", { onClick: onImport, title: "Import (Ctrl+O)", "aria-label": "Import", children: jsx(NiceIcon, { name: "folder", size: 16 }) }), jsx("button", { onClick: onExport, title: "Export (Ctrl+S)", "aria-label": "Export", children: jsx(NiceIcon, { name: "save", size: 16 }) })] }), jsx("div", { className: styles.toolbarSeparator }), jsxs("div", { className: styles.toolbarGroup, children: [jsx("button", { onClick: onUndo, disabled: !canUndo, title: "Undo (Ctrl+Z)", "aria-label": "Undo", children: jsx(NiceIcon, { name: "arrow-left", size: 16 }) }), jsx("button", { onClick: onRedo, disabled: !canRedo, title: "Redo (Ctrl+Y)", "aria-label": "Redo", children: jsx(NiceIcon, { name: "arrow-right", size: 16 }) })] }), jsx("div", { className: styles.toolbarSeparator }), jsxs("div", { className: styles.toolbarGroup, children: [jsx("button", { onClick: onCut, disabled: !hasSelection, title: "Cut (Ctrl+X)", "aria-label": "Cut", children: jsx(NiceIcon, { name: "scissors", size: 16 }) }), jsx("button", { onClick: onCopy, disabled: !hasSelection, title: "Copy (Ctrl+C)", "aria-label": "Copy", children: jsx(NiceIcon, { name: "clipboard", size: 16 }) }), jsx("button", { onClick: onPaste, disabled: !canPaste, title: "Paste (Ctrl+V)", "aria-label": "Paste", children: jsx(NiceIcon, { name: "download", size: 16 }) }), jsx("button", { onClick: onDuplicate, disabled: !hasSelection, title: "Duplicate (Ctrl+D)", "aria-label": "Duplicate", children: jsx(NiceIcon, { name: "refresh", size: 16 }) }), jsx("button", { onClick: onDelete, disabled: !hasSelection, title: "Delete (Del)", "aria-label": "Delete", children: jsx(NiceIcon, { name: "trash", size: 16 }) })] }), jsx("div", { className: styles.toolbarSeparator }), jsxs("div", { className: styles.toolbarGroup, children: [jsx("button", { onClick: onSelectAll, title: "Select All (Ctrl+A)", "aria-label": "Select All", children: jsx(NiceIcon, { name: "square", size: 16 }) }), jsx("button", { onClick: onZoomToFit, title: "Zoom to Fit (F)", "aria-label": "Zoom to Fit", children: jsx(NiceIcon, { name: "search", size: 16 }) }), jsx("button", { onClick: onToggleGrid, className: showGrid ? styles.active : '', title: "Toggle Grid (G)", "aria-label": "Toggle Grid", children: jsx(NiceIcon, { name: "hash", size: 16 }) }), jsx("button", { onClick: onToggleSnap, className: snapToGrid ? styles.active : '', title: "Toggle Snap (S)", "aria-label": "Toggle Snap", children: jsx(NiceIcon, { name: "target", size: 16 }) })] }), jsx("div", { className: styles.toolbarSeparator }), jsxs("div", { className: styles.toolbarGroup, children: [jsx("button", { onClick: onToggleAutoCompile, className: autoCompile ? styles.active : '', title: "Auto Compile", "aria-label": "Auto Compile", children: jsx(NiceIcon, { name: "zap", size: 16 }) }), jsx("button", { onClick: onCompile, disabled: compiling, title: "Compile (F5)", "aria-label": "Compile", children: jsx(NiceIcon, { name: compiling ? 'clock' : 'play', size: 16 }) })] })] }));
457
+ const { t } = useNiceTranslation();
458
+ return (jsxs("div", { className: styles.toolbar, children: [jsxs("div", { className: styles.toolbarGroup, children: [jsx("button", { onClick: onNew, title: t('materialEditor.newMaterialCtrlN', 'New Material (Ctrl+N)'), "aria-label": t('materialEditor.newMaterial', 'New Material'), children: jsx(NiceIcon, { name: "file", size: 16 }) }), jsx("button", { onClick: onImport, title: t('materialEditor.importCtrlO', 'Import (Ctrl+O)'), "aria-label": t('materialEditor.import', 'Import'), children: jsx(NiceIcon, { name: "folder", size: 16 }) }), jsx("button", { onClick: onExport, title: t('materialEditor.exportCtrlS', 'Export (Ctrl+S)'), "aria-label": t('materialEditor.export', 'Export'), children: jsx(NiceIcon, { name: "save", size: 16 }) })] }), jsx("div", { className: styles.toolbarSeparator }), jsxs("div", { className: styles.toolbarGroup, children: [jsx("button", { onClick: onUndo, disabled: !canUndo, title: t('materialEditor.undoCtrlZ', 'Undo (Ctrl+Z)'), "aria-label": t('materialEditor.undo', 'Undo'), children: jsx(NiceIcon, { name: "arrow-left", size: 16 }) }), jsx("button", { onClick: onRedo, disabled: !canRedo, title: t('materialEditor.redoCtrlY', 'Redo (Ctrl+Y)'), "aria-label": t('materialEditor.redo', 'Redo'), children: jsx(NiceIcon, { name: "arrow-right", size: 16 }) })] }), jsx("div", { className: styles.toolbarSeparator }), jsxs("div", { className: styles.toolbarGroup, children: [jsx("button", { onClick: onCut, disabled: !hasSelection, title: t('materialEditor.cutCtrlX', 'Cut (Ctrl+X)'), "aria-label": t('materialEditor.cut', 'Cut'), children: jsx(NiceIcon, { name: "scissors", size: 16 }) }), jsx("button", { onClick: onCopy, disabled: !hasSelection, title: t('materialEditor.copyCtrlC', 'Copy (Ctrl+C)'), "aria-label": t('materialEditor.copy', 'Copy'), children: jsx(NiceIcon, { name: "clipboard", size: 16 }) }), jsx("button", { onClick: onPaste, disabled: !canPaste, title: t('materialEditor.pasteCtrlV', 'Paste (Ctrl+V)'), "aria-label": t('materialEditor.paste', 'Paste'), children: jsx(NiceIcon, { name: "download", size: 16 }) }), jsx("button", { onClick: onDuplicate, disabled: !hasSelection, title: t('materialEditor.duplicateCtrlD', 'Duplicate (Ctrl+D)'), "aria-label": t('materialEditor.duplicate', 'Duplicate'), children: jsx(NiceIcon, { name: "refresh", size: 16 }) }), jsx("button", { onClick: onDelete, disabled: !hasSelection, title: t('materialEditor.deleteDel', 'Delete (Del)'), "aria-label": t('materialEditor.delete', 'Delete'), children: jsx(NiceIcon, { name: "trash", size: 16 }) })] }), jsx("div", { className: styles.toolbarSeparator }), jsxs("div", { className: styles.toolbarGroup, children: [jsx("button", { onClick: onSelectAll, title: t('materialEditor.selectAllCtrlA', 'Select All (Ctrl+A)'), "aria-label": t('materialEditor.selectAll', 'Select All'), children: jsx(NiceIcon, { name: "square", size: 16 }) }), jsx("button", { onClick: onZoomToFit, title: t('materialEditor.zoomToFitF', 'Zoom to Fit (F)'), "aria-label": t('materialEditor.zoomToFit', 'Zoom to Fit'), children: jsx(NiceIcon, { name: "search", size: 16 }) }), jsx("button", { onClick: onToggleGrid, className: showGrid ? styles.active : '', title: t('materialEditor.toggleGridG', 'Toggle Grid (G)'), "aria-label": t('materialEditor.toggleGrid', 'Toggle Grid'), children: jsx(NiceIcon, { name: "hash", size: 16 }) }), jsx("button", { onClick: onToggleSnap, className: snapToGrid ? styles.active : '', title: t('materialEditor.toggleSnapS', 'Toggle Snap (S)'), "aria-label": t('materialEditor.toggleSnap', 'Toggle Snap'), children: jsx(NiceIcon, { name: "target", size: 16 }) })] }), jsx("div", { className: styles.toolbarSeparator }), jsxs("div", { className: styles.toolbarGroup, children: [jsx("button", { onClick: onToggleAutoCompile, className: autoCompile ? styles.active : '', title: t('materialEditor.autoCompile', 'Auto Compile'), "aria-label": t('materialEditor.autoCompile', 'Auto Compile'), children: jsx(NiceIcon, { name: "zap", size: 16 }) }), jsx("button", { onClick: onCompile, disabled: compiling, title: t('materialEditor.compileF5', 'Compile (F5)'), "aria-label": t('materialEditor.compile', 'Compile'), children: jsx(NiceIcon, { name: compiling ? 'clock' : 'play', size: 16 }) })] })] }));
456
459
  }
457
460
  function PreviewPanel({ shader, mode, compiling, onModeChange }) {
461
+ const { t } = useNiceTranslation();
458
462
  const canvasRef = useRef(null);
459
463
  const previewModes = ['sphere', 'cube', 'plane', 'cylinder', 'torus'];
460
464
  // Simple preview rendering (in real implementation would use Three.js)
@@ -484,7 +488,7 @@ function PreviewPanel({ shader, mode, compiling, onModeChange }) {
484
488
  ctx.arc(100, 100, 80, 0, Math.PI * 2);
485
489
  ctx.fill();
486
490
  }, [shader, mode]);
487
- return (jsxs("div", { className: styles.previewPanel, children: [jsxs("div", { className: styles.previewHeader, children: [jsx("span", { children: "Preview" }), jsx("select", { value: mode, onChange: (e) => onModeChange(e.target.value), className: styles.previewModeSelect, children: previewModes.map((m) => (jsx("option", { value: m, children: m.charAt(0).toUpperCase() + m.slice(1) }, m))) })] }), jsxs("div", { className: styles.previewCanvas, children: [compiling && jsx("div", { className: styles.previewCompiling, children: "Compiling..." }), jsx("canvas", { ref: canvasRef, width: 200, height: 200 })] }), (shader === null || shader === void 0 ? void 0 : shader.errors.length) ? (jsx("div", { className: styles.previewErrors, children: shader.errors.slice(0, 3).map((err, i) => (jsx("div", { className: styles.previewError, children: err.message }, i))) })) : null] }));
491
+ return (jsxs("div", { className: styles.previewPanel, children: [jsxs("div", { className: styles.previewHeader, children: [jsx("span", { children: t('materialEditor.preview', 'Preview') }), jsx("select", { value: mode, onChange: (e) => onModeChange(e.target.value), className: styles.previewModeSelect, children: previewModes.map((m) => (jsx("option", { value: m, children: m.charAt(0).toUpperCase() + m.slice(1) }, m))) })] }), jsxs("div", { className: styles.previewCanvas, children: [compiling && (jsx("div", { className: styles.previewCompiling, children: t('materialEditor.compiling', 'Compiling...') })), jsx("canvas", { ref: canvasRef, width: 200, height: 200 })] }), (shader === null || shader === void 0 ? void 0 : shader.errors.length) ? (jsx("div", { className: styles.previewErrors, children: shader.errors.slice(0, 3).map((err, i) => (jsx("div", { className: styles.previewError, children: err.message }, i))) })) : null] }));
488
492
  }
489
493
  /* ═══════════════════════════════════════════════════════════════════════════
490
494
  Main Component
@@ -42,7 +42,7 @@ const PORT_TYPE_COLORS = {
42
42
  vec4: 'var(--nice-warning, #ffc107)',
43
43
  color: 'var(--nice-danger, #ff5722)',
44
44
  texture2d: 'var(--nice-info, #00bcd4)',
45
- textureCube: '#03A9F4',
45
+ textureCube: 'var(--nice-color-primary, #03A9F4)',
46
46
  sampler: 'var(--nice-accent, #3f51b5)',
47
47
  matrix3: 'var(--nice-accent, #673ab7)',
48
48
  matrix4: 'var(--nice-accent, #9c27b0)',
@@ -1450,17 +1450,20 @@ const logicNodes = [
1450
1450
  EXPORTS
1451
1451
  ═══════════════════════════════════════════════════════════════════════════ */
1452
1452
  /** All built-in node definitions */
1453
- const MATERIAL_NODE_DEFINITIONS = [
1454
- ...inputNodes,
1455
- ...outputNodes,
1456
- ...mathNodes,
1457
- ...vectorNodes,
1458
- ...colorNodes,
1459
- ...textureNodes,
1460
- ...proceduralNodes,
1461
- ...utilityNodes,
1462
- ...logicNodes,
1463
- ];
1453
+ const MATERIAL_NODE_DEFINITIONS =
1454
+ /* @__PURE__ */ (function () {
1455
+ return [
1456
+ ...inputNodes,
1457
+ ...outputNodes,
1458
+ ...mathNodes,
1459
+ ...vectorNodes,
1460
+ ...colorNodes,
1461
+ ...textureNodes,
1462
+ ...proceduralNodes,
1463
+ ...utilityNodes,
1464
+ ...logicNodes,
1465
+ ];
1466
+ })();
1464
1467
  /** Node definitions map by type */
1465
1468
  const MATERIAL_NODE_MAP = new Map(MATERIAL_NODE_DEFINITIONS.map((def) => [def.type, def]));
1466
1469
  /** Get nodes by category */
@@ -1,5 +1,6 @@
1
1
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
2
2
  import { NiceAppShell } from '@nice2dev/ui';
3
+ import { EditorAskDialog } from '../core/editorAsk.js';
3
4
  import styles from './ModelEditor.module.css.js';
4
5
  import ModelEditorLeftPanel from './ModelEditorLeftPanel.js';
5
6
  import ModelEditorMenuBar from './ModelEditorMenuBar.js';
@@ -10,7 +11,7 @@ import { SUPPORTED_EXTENSIONS } from './modelEditorTypes.js';
10
11
  import ModelEditorViewport from './ModelEditorViewport.js';
11
12
  import { useModelEditor } from './useModelEditor.js';
12
13
 
13
- const ACCEPT = SUPPORTED_EXTENSIONS.map((e) => `.${e}`).join(',');
14
+ const ACCEPT = /* @__PURE__ */ SUPPORTED_EXTENSIONS.map((e) => `.${e}`).join(',');
14
15
  const NiceModelEditor = ({ className, onSaveToLibrary, onAIPoseVideo, }) => {
15
16
  const api = useModelEditor({ onSaveToLibrary, onAIPoseVideo });
16
17
  return (jsxs(Fragment, { children: [jsx("input", { ref: api.fileInputRef, type: "file", accept: ACCEPT, style: { display: 'none' }, onChange: (e) => {
@@ -27,9 +28,7 @@ const NiceModelEditor = ({ className, onSaveToLibrary, onAIPoseVideo, }) => {
27
28
  api.mergeAnimationFBX(f);
28
29
  }
29
30
  e.target.value = '';
30
- } }), jsx("input", { ref: api.videoInputRef, type: "file", accept: "video/*", style: { display: 'none' }, onChange: api.handleAIVideo }), jsx(NiceAppShell, { className: `${styles.root} ${className !== null && className !== void 0 ? className : ''}`, flush: true, menubar: jsx(ModelEditorMenuBar, { api: api, hasOnSaveToLibrary: !!onSaveToLibrary }), sidebar: jsxs("div", { style: { display: 'flex', minHeight: 0 }, children: [jsx(ModelEditorLeftPanel, { api: api }), jsx(ModelEditorToolbar, { api: api })] }), rightSidebar: jsx(ModelEditorRightPanel, { api: api }), bottomPanel: jsx(ModelEditorTimeline, { api: api }), statusBar: jsxs("div", { className: styles.statusBar, children: [jsx("span", { children: api.statusText }), 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: jsx(ModelEditorViewport, { api: api }) })] }));
31
+ } }), jsx("input", { ref: api.videoInputRef, type: "file", accept: "video/*", style: { display: 'none' }, onChange: api.handleAIVideo }), jsx(EditorAskDialog, { ask: api.pendingAsk }), jsx(NiceAppShell, { className: `${styles.root} ${className !== null && className !== void 0 ? className : ''}`, flush: true, menubar: jsx(ModelEditorMenuBar, { api: api, hasOnSaveToLibrary: !!onSaveToLibrary }), sidebar: jsxs("div", { style: { display: 'flex', minHeight: 0 }, children: [jsx(ModelEditorLeftPanel, { api: api }), jsx(ModelEditorToolbar, { api: api })] }), rightSidebar: jsx(ModelEditorRightPanel, { api: api }), bottomPanel: jsx(ModelEditorTimeline, { api: api }), statusBar: jsxs("div", { className: styles.statusBar, children: [jsx("span", { children: api.statusText }), 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: jsx(ModelEditorViewport, { api: api }) })] }));
31
32
  };
32
- /** @deprecated Use NiceModelEditor */
33
- const ModelEditor = NiceModelEditor;
34
33
 
35
- export { ModelEditor, NiceModelEditor };
34
+ export { NiceModelEditor };
@@ -1,9 +1,12 @@
1
1
  import { jsx, jsxs } from 'react/jsx-runtime';
2
2
  import { NiceIcon, NiceButton, NiceTextInput } from '@nice2dev/ui';
3
3
  import React from 'react';
4
+ import { useNiceTranslation } from '../core/i18n.js';
4
5
  import styles from './ModelEditor.module.css.js';
5
6
 
6
- const ModelEditorLeftPanel = React.memo(({ api }) => {
7
+ const ModelEditorLeftPanel = /* @__PURE__ */ Object.assign(
8
+ /* @__PURE__ */ React.memo(({ api }) => {
9
+ const { t } = useNiceTranslation();
7
10
  const { sceneTree, selectedId, outlinerSearch, setOutlinerSearch, selectObject, rebuildTree, toggleNodeExpanded, toggleNodeVisibility, } = api;
8
11
  const renderTreeNode = (node, depth) => {
9
12
  // Outliner search filter
@@ -37,8 +40,11 @@ const ModelEditorLeftPanel = React.memo(({ api }) => {
37
40
  toggleNodeVisibility(node);
38
41
  }, children: jsx(NiceIcon, { name: node.visible ? 'eye' : 'eye-off', size: 14 }) })] }), node.expanded && node.children.map((c) => renderTreeNode(c, depth + 1))] }, node.id));
39
42
  };
40
- return (jsx("div", { className: styles.leftPanel, children: jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx(NiceIcon, { name: "clipboard", size: 14 }), " Outliner", jsx(NiceButton, { variant: "ghost", size: "sm", onClick: rebuildTree, title: "Refresh", "aria-label": "Refresh outliner", children: jsx(NiceIcon, { name: "refresh", size: 14 }) })] }), jsx("div", { className: styles.outlinerSearch, children: jsx(NiceTextInput, { className: styles.propInput, placeholder: "Filter...", value: outlinerSearch, onChange: (val) => setOutlinerSearch(val), style: { width: '100%', marginBottom: 4 } }) }), jsxs("div", { className: styles.panelContent, style: { overflowY: 'auto', flex: 1 }, children: [sceneTree.length === 0 && (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))] })] }) }));
41
- });
42
- ModelEditorLeftPanel.displayName = 'ModelEditorLeftPanel';
43
+ return (jsx("div", { className: styles.leftPanel, children: jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx(NiceIcon, { name: "clipboard", size: 14 }), " ", t('modelEditorLeftPanel.outliner', 'Outliner'), jsx(NiceButton, { variant: "ghost", size: "sm", onClick: rebuildTree, title: t('modelEditorLeftPanel.refresh', 'Refresh'), "aria-label": t('modelEditorLeftPanel.refreshOutliner', 'Refresh outliner'), children: jsx(NiceIcon, { name: "refresh", size: 14 }) })] }), jsx("div", { className: styles.outlinerSearch, children: jsx(NiceTextInput, { className: styles.propInput, placeholder: t('modelEditorLeftPanel.filter', 'Filter...'), value: outlinerSearch, onChange: (val) => setOutlinerSearch(val), style: { width: '100%', marginBottom: 4 } }) }), jsxs("div", { className: styles.panelContent, style: { overflowY: 'auto', flex: 1 }, children: [sceneTree.length === 0 && (jsx("div", { style: {
44
+ color: 'var(--nice-text-secondary, #666)',
45
+ fontSize: 10,
46
+ padding: '8px 0',
47
+ }, children: t('modelEditorLeftPanel.dropA3dFileTo', 'Drop a 3D file to begin') })), sceneTree.map((n) => renderTreeNode(n, 0))] })] }) }));
48
+ }), { displayName: 'ModelEditorLeftPanel' });
43
49
 
44
50
  export { ModelEditorLeftPanel as default };
@@ -1,15 +1,18 @@
1
1
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
2
  import { NiceSelect, NiceButton, NiceIcon, NiceCheckbox, NiceColorPicker } from '@nice2dev/ui';
3
3
  import React from 'react';
4
+ import { useNiceTranslation } from '../core/i18n.js';
4
5
  import styles from './ModelEditor.module.css.js';
5
6
 
6
- const ModelEditorMenuBar = React.memo(({ api, hasOnSaveToLibrary }) => {
7
+ const ModelEditorMenuBar = /* @__PURE__ */ Object.assign(
8
+ /* @__PURE__ */ React.memo(({ api, hasOnSaveToLibrary }) => {
9
+ const { t } = useNiceTranslation();
7
10
  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;
8
11
  return (jsxs("div", { className: styles.menuBar, children: [jsx(NiceSelect, { className: styles.modeSelect, value: editorMode, onChange: (val) => setEditorMode(val), options: [
9
12
  { value: 'object', label: 'Object Mode' },
10
13
  { value: 'edit', label: 'Edit Mode' },
11
14
  { value: 'pose', label: 'Pose Mode' },
12
- ] }), jsx("div", { className: styles.menuSep }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: [jsx(NiceIcon, { name: "folder", size: 14 }), " Open"] }), jsxs(NiceButton, { className: styles.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: [jsx(NiceIcon, { name: "plus", size: 14 }), " Merge Anim"] }), jsx("div", { className: styles.menuSep }), jsxs("div", { className: styles.menuDropdownWrap, children: [jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => setAddMenuOpen(addMenuOpen ? null : 'mesh'), children: [jsx(NiceIcon, { name: "plus", size: 14 }), " Add ", jsx(NiceIcon, { name: "chevron-down", size: 14 })] }), addMenuOpen && (jsxs("div", { className: styles.dropdownMenu, children: [jsx("div", { className: styles.dropdownTitle, children: "Mesh" }), [
15
+ ] }), jsx("div", { className: styles.menuSep }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: [jsx(NiceIcon, { name: "folder", size: 14 }), " ", t('modelEditorMenuBar.open', 'Open')] }), jsxs(NiceButton, { className: styles.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: [jsx(NiceIcon, { name: "plus", size: 14 }), " ", t('modelEditorMenuBar.mergeAnim', 'Merge Anim')] }), jsx("div", { className: styles.menuSep }), jsxs("div", { className: styles.menuDropdownWrap, children: [jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => setAddMenuOpen(addMenuOpen ? null : 'mesh'), children: [jsx(NiceIcon, { name: "plus", size: 14 }), " Add ", jsx(NiceIcon, { name: "chevron-down", size: 14 })] }), addMenuOpen && (jsxs("div", { className: styles.dropdownMenu, children: [jsx("div", { className: styles.dropdownTitle, children: t('modelEditorMenuBar.mesh', 'Mesh') }), [
13
16
  'cube',
14
17
  'sphere',
15
18
  'cylinder',
@@ -23,8 +26,7 @@ const ModelEditorMenuBar = React.memo(({ api, hasOnSaveToLibrary }) => {
23
26
  'octahedron',
24
27
  'tetrahedron',
25
28
  'torusKnot',
26
- ].map((t) => (jsx("div", { className: styles.dropdownItem, onClick: () => addPrimitive(t), children: t.charAt(0).toUpperCase() + t.slice(1) }, t))), jsx("div", { className: styles.dropdownSep }), jsx("div", { className: styles.dropdownTitle, children: "Light" }), ['point', 'spot', 'directional', 'hemisphere'].map((t) => (jsxs("div", { className: styles.dropdownItem, onClick: () => addSceneLight(t), children: [jsx(NiceIcon, { name: "lightbulb", size: 14 }), " ", t.charAt(0).toUpperCase() + t.slice(1)] }, t))), jsx("div", { className: styles.dropdownSep }), jsx("div", { className: styles.dropdownTitle, children: "Other" }), jsxs("div", { className: styles.dropdownItem, onClick: addCameraObject, children: [jsx(NiceIcon, { name: "camera", size: 14 }), " Camera"] }), jsxs("div", { className: styles.dropdownItem, onClick: () => addEmpty('arrows'), children: [jsx(NiceIcon, { name: "move", size: 14 }), " Empty (Arrows)"] })] }))] }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: duplicateSelected, disabled: !selectedNode, title: "Duplicate selected (Shift+D)", children: [jsx(NiceIcon, { name: "clipboard", size: 14 }), " Duplicate"] }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: deleteSelected, disabled: !selectedNode, title: "Delete selected (Delete)", children: [jsx(NiceIcon, { name: "close", size: 14 }), " Delete"] }), jsx("div", { className: styles.menuSep }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => exportGLTF(true), children: [jsx(NiceIcon, { name: "save", size: 14 }), " Export GLB"] }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => exportGLTF(false), children: [jsx(NiceIcon, { name: "file", size: 14 }), " Export GLTF"] }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: exportOBJ, children: [jsx(NiceIcon, { name: "file", size: 14 }), " Export OBJ"] }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => exportSTL(true), children: [jsx(NiceIcon, { name: "file", size: 14 }), " Export STL"] }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => exportPLY(true), children: [jsx(NiceIcon, { name: "file", size: 14 }), " Export PLY"] }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: exportUSDZ, children: [jsx(NiceIcon, { name: "file", size: 14 }), " Export USDZ"] }), hasOnSaveToLibrary && (jsxs(Fragment, { children: [jsx("div", { className: styles.menuSep }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: saveToLibrary, children: [jsx(NiceIcon, { name: "box", size: 14 }), " Save to Library"] })] })), jsx("div", { className: styles.menuSep }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: clearScene, children: [jsx(NiceIcon, { name: "trash", size: 14 }), " Clear"] }), jsx("div", { className: styles.menuSep }), jsx(NiceCheckbox, { checked: showGrid, onChange: () => setShowGrid((v) => !v), label: "Grid" }), jsx(NiceCheckbox, { checked: showAxes, onChange: () => setShowAxes((v) => !v), label: "Axes" }), jsx(NiceCheckbox, { checked: wireframe, onChange: () => setWireframe((v) => !v), label: "Wire" }), jsx(NiceCheckbox, { checked: showSkeleton, onChange: () => setShowSkeleton((v) => !v), label: "Skel" }), jsx(NiceCheckbox, { checked: showLightHelpers, onChange: () => setShowLightHelpers((v) => !v), label: "Helpers" }), jsxs("div", { className: styles.menuRight, children: [jsx("label", { className: styles.menuLabel, children: "BG" }), jsx(NiceColorPicker, { value: bgColor, onChange: (c) => setBgColor(c) })] })] }));
27
- });
28
- ModelEditorMenuBar.displayName = 'ModelEditorMenuBar';
29
+ ].map((t) => (jsx("div", { className: styles.dropdownItem, onClick: () => addPrimitive(t), children: t.charAt(0).toUpperCase() + t.slice(1) }, t))), jsx("div", { className: styles.dropdownSep }), jsx("div", { className: styles.dropdownTitle, children: t('modelEditorMenuBar.light', 'Light') }), ['point', 'spot', 'directional', 'hemisphere'].map((t) => (jsxs("div", { className: styles.dropdownItem, onClick: () => addSceneLight(t), children: [jsx(NiceIcon, { name: "lightbulb", size: 14 }), " ", t.charAt(0).toUpperCase() + t.slice(1)] }, t))), jsx("div", { className: styles.dropdownSep }), jsx("div", { className: styles.dropdownTitle, children: t('modelEditorMenuBar.other', 'Other') }), jsxs("div", { className: styles.dropdownItem, onClick: addCameraObject, children: [jsx(NiceIcon, { name: "camera", size: 14 }), " ", t('modelEditorMenuBar.camera', 'Camera')] }), jsxs("div", { className: styles.dropdownItem, onClick: () => addEmpty('arrows'), children: [jsx(NiceIcon, { name: "move", size: 14 }), " Empty (Arrows)"] })] }))] }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: duplicateSelected, disabled: !selectedNode, title: t('modelEditorMenuBar.duplicateSelectedShiftD', 'Duplicate selected (Shift+D)'), children: [jsx(NiceIcon, { name: "clipboard", size: 14 }), " ", t('modelEditorMenuBar.duplicate', 'Duplicate')] }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: deleteSelected, disabled: !selectedNode, title: t('modelEditorMenuBar.deleteSelectedDelete', 'Delete selected (Delete)'), children: [jsx(NiceIcon, { name: "close", size: 14 }), " ", t('modelEditorMenuBar.delete', 'Delete')] }), jsx("div", { className: styles.menuSep }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => exportGLTF(true), children: [jsx(NiceIcon, { name: "save", size: 14 }), " ", t('modelEditorMenuBar.exportGlb', 'Export GLB')] }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => exportGLTF(false), children: [jsx(NiceIcon, { name: "file", size: 14 }), " ", t('modelEditorMenuBar.exportGltf', 'Export GLTF')] }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: exportOBJ, children: [jsx(NiceIcon, { name: "file", size: 14 }), " ", t('modelEditorMenuBar.exportObj', 'Export OBJ')] }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => exportSTL(true), children: [jsx(NiceIcon, { name: "file", size: 14 }), " ", t('modelEditorMenuBar.exportStl', 'Export STL')] }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => exportPLY(true), children: [jsx(NiceIcon, { name: "file", size: 14 }), " ", t('modelEditorMenuBar.exportPly', 'Export PLY')] }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: exportUSDZ, children: [jsx(NiceIcon, { name: "file", size: 14 }), " ", t('modelEditorMenuBar.exportUsdz', 'Export USDZ')] }), hasOnSaveToLibrary && (jsxs(Fragment, { children: [jsx("div", { className: styles.menuSep }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: saveToLibrary, children: [jsx(NiceIcon, { name: "box", size: 14 }), ' ', t('modelEditorMenuBar.saveToLibrary', 'Save to Library')] })] })), jsx("div", { className: styles.menuSep }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: clearScene, children: [jsx(NiceIcon, { name: "trash", size: 14 }), " ", t('modelEditorMenuBar.clear', 'Clear')] }), jsx("div", { className: styles.menuSep }), jsx(NiceCheckbox, { checked: showGrid, onChange: () => setShowGrid((v) => !v), label: t('modelEditorMenuBar.grid', 'Grid') }), jsx(NiceCheckbox, { checked: showAxes, onChange: () => setShowAxes((v) => !v), label: t('modelEditorMenuBar.axes', 'Axes') }), jsx(NiceCheckbox, { checked: wireframe, onChange: () => setWireframe((v) => !v), label: t('modelEditorMenuBar.wire', 'Wire') }), jsx(NiceCheckbox, { checked: showSkeleton, onChange: () => setShowSkeleton((v) => !v), label: t('modelEditorMenuBar.skel', 'Skel') }), jsx(NiceCheckbox, { checked: showLightHelpers, onChange: () => setShowLightHelpers((v) => !v), label: t('modelEditorMenuBar.helpers', 'Helpers') }), jsxs("div", { className: styles.menuRight, children: [jsx("label", { className: styles.menuLabel, children: "BG" }), jsx(NiceColorPicker, { value: bgColor, onChange: (c) => setBgColor(c) })] })] }));
30
+ }), { displayName: 'ModelEditorMenuBar' });
29
31
 
30
32
  export { ModelEditorMenuBar as default };
@@ -2,11 +2,14 @@ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
2
  import { NiceButton, NiceIcon, NiceTextInput, NiceColorPicker, NiceSlider, NiceCheckbox, NiceNumberInput } from '@nice2dev/ui';
3
3
  import React from 'react';
4
4
  import * as THREE from 'three';
5
+ import { useNiceTranslation } from '../core/i18n.js';
5
6
  import styles from './ModelEditor.module.css.js';
6
7
  import { Vec3Row, Vec3RowDeg, MaterialCard } from './ModelEditorSubComponents.js';
7
8
  import { DEG } from './modelEditorTypes.js';
8
9
 
9
- const ModelEditorRightPanel = React.memo(({ api }) => {
10
+ const ModelEditorRightPanel = /* @__PURE__ */ Object.assign(
11
+ /* @__PURE__ */ React.memo(({ api }) => {
12
+ const { t } = useNiceTranslation();
10
13
  const { propTab, setPropTab, selectedNode, selectedMaterials, selMaterialIdx, setSelMaterialIdx, setMatRefresh, rebuildTree, aiBusy, aiStatus, rootObjectRef, videoInputRef, bgColor, setBgColor, fogEnabled, setFogEnabled, fogColor, setFogColor, fogNear, setFogNear, fogFar, setFogFar, showLightHelpers, setShowLightHelpers, showGrid, setShowGrid, showAxes, setShowAxes, showSkeleton, setShowSkeleton, snapEnabled, setSnapEnabled, snapGrid, setSnapGrid, setStatusText, } = api;
11
14
  return (jsxs("div", { className: styles.rightPanel, children: [jsx("div", { className: styles.propTabs, children: ['object', 'material', 'world', 'modifiers', 'physics'].map((t) => (jsx(NiceButton, { className: styles.propTabBtn, variant: propTab === t ? 'primary' : 'ghost', size: "sm", onClick: () => setPropTab(t), title: t.charAt(0).toUpperCase() + t.slice(1), "aria-label": t.charAt(0).toUpperCase() + t.slice(1), children: jsx(NiceIcon, { name: t === 'object'
12
15
  ? 'settings'
@@ -16,13 +19,13 @@ const ModelEditorRightPanel = React.memo(({ api }) => {
16
19
  ? 'globe'
17
20
  : t === 'modifiers'
18
21
  ? 'settings'
19
- : 'zap', size: 16 }) }, t))) }), propTab === 'object' && (jsxs(Fragment, { children: [jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx(NiceIcon, { name: "settings", size: 14 }), " Transform"] }), jsx("div", { className: styles.panelContent, children: selectedNode ? (jsxs(Fragment, { children: [jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: "Name" }), jsx(NiceTextInput, { className: styles.propInput, value: selectedNode.name, onChange: (val) => {
22
+ : 'zap', size: 16 }) }, t))) }), propTab === 'object' && (jsxs(Fragment, { children: [jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx(NiceIcon, { name: "settings", size: 14 }), ' ', t('modelEditorRightPanel.transform', 'Transform')] }), jsx("div", { className: styles.panelContent, children: selectedNode ? (jsxs(Fragment, { children: [jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorRightPanel.name', 'Name') }), jsx(NiceTextInput, { className: styles.propInput, value: selectedNode.name, onChange: (val) => {
20
23
  selectedNode.object.name = val;
21
24
  rebuildTree();
22
- } })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: "Type" }), jsx("span", { style: { fontSize: 10, color: 'var(--nice-text-secondary, #888)' }, children: selectedNode.type })] }), jsx(Vec3Row, { label: "Position", value: selectedNode.object.position, onChange: () => setMatRefresh((n) => n + 1) }), jsx(Vec3RowDeg, { label: "Rotation", value: selectedNode.object.rotation, onChange: () => setMatRefresh((n) => n + 1) }), jsx(Vec3Row, { label: "Scale", value: selectedNode.object.scale, onChange: () => setMatRefresh((n) => n + 1) })] })) : (jsx("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10 }, children: "Select an object" })) })] }), selectedNode && selectedNode.object instanceof THREE.Light && (jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx(NiceIcon, { name: "lightbulb", size: 14 }), " Light"] }), jsxs("div", { className: styles.panelContent, children: [jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: "Color" }), jsx(NiceColorPicker, { value: `#${selectedNode.object.color.getHexString()}`, onChange: (c) => {
25
+ } })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorRightPanel.type', 'Type') }), jsx("span", { style: { fontSize: 10, color: 'var(--nice-text-secondary, #888)' }, children: selectedNode.type })] }), jsx(Vec3Row, { label: t('modelEditorRightPanel.position', 'Position'), value: selectedNode.object.position, onChange: () => setMatRefresh((n) => n + 1) }), jsx(Vec3RowDeg, { label: t('modelEditorRightPanel.rotation', 'Rotation'), value: selectedNode.object.rotation, onChange: () => setMatRefresh((n) => n + 1) }), jsx(Vec3Row, { label: t('modelEditorRightPanel.scale', 'Scale'), value: selectedNode.object.scale, onChange: () => setMatRefresh((n) => n + 1) })] })) : (jsx("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10 }, children: t('modelEditorRightPanel.selectAnObject', 'Select an object') })) })] }), selectedNode && selectedNode.object instanceof THREE.Light && (jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx(NiceIcon, { name: "lightbulb", size: 14 }), ' ', t('modelEditorRightPanel.light', 'Light')] }), jsxs("div", { className: styles.panelContent, children: [jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorRightPanel.color', 'Color') }), jsx(NiceColorPicker, { value: `#${selectedNode.object.color.getHexString()}`, onChange: (c) => {
23
26
  selectedNode.object.color.set(c);
24
27
  setMatRefresh((n) => n + 1);
25
- } })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: "Intensity" }), jsx(NiceSlider, { min: 0, max: 10, step: 0.1, value: selectedNode.object.intensity, onChange: (val) => {
28
+ } })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorRightPanel.intensity', 'Intensity') }), jsx(NiceSlider, { min: 0, max: 10, step: 0.1, value: selectedNode.object.intensity, onChange: (val) => {
26
29
  selectedNode.object.intensity = val;
27
30
  setMatRefresh((n) => n + 1);
28
31
  }, style: { flex: 1 } }), jsx("span", { style: {
@@ -30,10 +33,10 @@ const ModelEditorRightPanel = React.memo(({ api }) => {
30
33
  color: 'var(--nice-text-secondary, #888)',
31
34
  width: 28,
32
35
  textAlign: 'right',
33
- }, children: selectedNode.object.intensity.toFixed(1) })] }), 'castShadow' in selectedNode.object && (jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: "Shadow" }), jsx(NiceCheckbox, { checked: selectedNode.object.castShadow, onChange: (checked) => {
36
+ }, children: selectedNode.object.intensity.toFixed(1) })] }), 'castShadow' in selectedNode.object && (jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorRightPanel.shadow', 'Shadow') }), jsx(NiceCheckbox, { checked: selectedNode.object.castShadow, onChange: (checked) => {
34
37
  selectedNode.object.castShadow = checked;
35
38
  setMatRefresh((n) => n + 1);
36
- } })] })), selectedNode.object instanceof THREE.SpotLight && (jsxs(Fragment, { children: [jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: "Angle" }), jsx(NiceSlider, { min: 0, max: 90, step: 1, value: selectedNode.object.angle * DEG, onChange: (val) => {
39
+ } })] })), selectedNode.object instanceof THREE.SpotLight && (jsxs(Fragment, { children: [jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorRightPanel.angle', 'Angle') }), jsx(NiceSlider, { min: 0, max: 90, step: 1, value: selectedNode.object.angle * DEG, onChange: (val) => {
37
40
  selectedNode.object.angle = val / DEG;
38
41
  setMatRefresh((n) => n + 1);
39
42
  }, style: { flex: 1 } }), jsxs("span", { style: {
@@ -41,10 +44,10 @@ const ModelEditorRightPanel = React.memo(({ api }) => {
41
44
  color: 'var(--nice-text-secondary, #888)',
42
45
  width: 28,
43
46
  textAlign: 'right',
44
- }, children: [(selectedNode.object.angle * DEG).toFixed(0), "\u00B0"] })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: "Penumbra" }), jsx(NiceSlider, { min: 0, max: 1, step: 0.01, value: selectedNode.object.penumbra, onChange: (val) => {
47
+ }, children: [(selectedNode.object.angle * DEG).toFixed(0), "\u00B0"] })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorRightPanel.penumbra', 'Penumbra') }), jsx(NiceSlider, { min: 0, max: 1, step: 0.01, value: selectedNode.object.penumbra, onChange: (val) => {
45
48
  selectedNode.object.penumbra = val;
46
49
  setMatRefresh((n) => n + 1);
47
- }, style: { flex: 1 } })] })] }))] })] })), selectedNode && selectedNode.object instanceof THREE.PerspectiveCamera && (jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx(NiceIcon, { name: "camera", size: 14 }), " Camera"] }), jsxs("div", { className: styles.panelContent, children: [jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: "FOV" }), jsx(NiceSlider, { min: 10, max: 120, step: 1, value: selectedNode.object.fov, onChange: (val) => {
50
+ }, style: { flex: 1 } })] })] }))] })] })), selectedNode && selectedNode.object instanceof THREE.PerspectiveCamera && (jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx(NiceIcon, { name: "camera", size: 14 }), " ", t('modelEditorRightPanel.camera', 'Camera')] }), jsxs("div", { className: styles.panelContent, children: [jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: "FOV" }), jsx(NiceSlider, { min: 10, max: 120, step: 1, value: selectedNode.object.fov, onChange: (val) => {
48
51
  const cam = selectedNode.object;
49
52
  cam.fov = val;
50
53
  cam.updateProjectionMatrix();
@@ -54,7 +57,7 @@ const ModelEditorRightPanel = React.memo(({ api }) => {
54
57
  color: 'var(--nice-text-secondary, #888)',
55
58
  width: 28,
56
59
  textAlign: 'right',
57
- }, children: [selectedNode.object.fov.toFixed(0), "\u00B0"] })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: "Near" }), jsx(NiceNumberInput, { className: styles.propInput, step: 0.01, value: selectedNode.object.near, onChange: (val) => {
60
+ }, children: [selectedNode.object.fov.toFixed(0), "\u00B0"] })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorRightPanel.near', 'Near') }), jsx(NiceNumberInput, { className: styles.propInput, step: 0.01, value: selectedNode.object.near, onChange: (val) => {
58
61
  const cam = selectedNode.object;
59
62
  cam.near = val !== null && val !== void 0 ? val : 0.1;
60
63
  cam.updateProjectionMatrix();
@@ -64,11 +67,11 @@ const ModelEditorRightPanel = React.memo(({ api }) => {
64
67
  cam.far = val !== null && val !== void 0 ? val : 1000;
65
68
  cam.updateProjectionMatrix();
66
69
  setMatRefresh((n) => n + 1);
67
- }, style: { width: 80 } })] })] })] })), jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx(NiceIcon, { name: "wand", size: 14 }), " AI Animation"] }), jsx("div", { className: styles.panelContent, children: jsxs("div", { className: styles.aiPanel, children: [jsxs("div", { className: styles.aiHeader, children: [jsx(NiceIcon, { name: "wand", size: 14 }), " Generate animation from video"] }), jsx("p", { style: {
70
+ }, style: { width: 80 } })] })] })] })), jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx(NiceIcon, { name: "wand", size: 14 }), ' ', t('modelEditorRightPanel.aiAnimation', 'AI Animation')] }), jsx("div", { className: styles.panelContent, children: jsxs("div", { className: styles.aiPanel, children: [jsxs("div", { className: styles.aiHeader, children: [jsx(NiceIcon, { name: "wand", size: 14 }), ' ', t('modelEditorRightPanel.generateAnimationFromVideo', 'Generate animation from video')] }), jsx("p", { style: {
68
71
  fontSize: 10,
69
72
  color: 'var(--nice-text-secondary, #888)',
70
73
  margin: '0 0 6px',
71
- }, children: "Upload a video and AI will extract 3D pose data to create an animation clip for the current model." }), jsx("div", { className: styles.aiBtnRow, children: jsx(NiceButton, { className: styles.aiBtn, variant: "ghost", size: "sm", disabled: aiBusy || !rootObjectRef.current, onClick: () => { var _a; return (_a = videoInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: aiBusy ? 'Processing...' : 'Upload Video' }) }), aiStatus && jsx("div", { className: styles.aiStatus, children: aiStatus })] }) })] })] })), propTab === 'material' && (jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx(NiceIcon, { name: "palette", size: 14 }), " Materials"] }), jsx("div", { className: styles.panelContent, children: selectedMaterials.length > 0 ? (selectedMaterials.map((mat, i) => (jsx(MaterialCard, { material: mat, active: i === selMaterialIdx, onClick: () => setSelMaterialIdx(i), onUpdate: () => setMatRefresh((n) => n + 1) }, i)))) : (jsx("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10 }, children: selectedNode ? 'No materials on this object' : 'Select a mesh to inspect' })) })] })), propTab === 'world' && (jsxs(Fragment, { children: [jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx(NiceIcon, { name: "globe", size: 14 }), " Environment"] }), jsx("div", { className: styles.panelContent, children: jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: "Background" }), jsx(NiceColorPicker, { value: bgColor, onChange: (c) => setBgColor(c) }), jsx("span", { style: { fontSize: 9, color: 'var(--nice-text-secondary, #888)' }, children: bgColor })] }) })] }), jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx(NiceIcon, { name: "cloud", size: 14 }), " Fog"] }), jsxs("div", { className: styles.panelContent, children: [jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: "Enable" }), jsx(NiceCheckbox, { checked: fogEnabled, onChange: () => setFogEnabled((v) => !v) })] }), fogEnabled && (jsxs(Fragment, { children: [jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: "Color" }), jsx(NiceColorPicker, { value: fogColor, onChange: (c) => setFogColor(c) })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: "Near" }), jsx(NiceNumberInput, { className: styles.propInput, value: fogNear, onChange: (val) => setFogNear(val !== null && val !== void 0 ? val : 1), style: { width: 60 } })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: "Far" }), jsx(NiceNumberInput, { className: styles.propInput, value: fogFar, onChange: (val) => setFogFar(val !== null && val !== void 0 ? val : 100), style: { width: 60 } })] })] }))] })] }), jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx(NiceIcon, { name: "lightbulb", size: 14 }), " Scene Lights"] }), jsxs("div", { className: styles.panelContent, children: [jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: "Show Helpers" }), jsx(NiceCheckbox, { checked: showLightHelpers, onChange: () => setShowLightHelpers((v) => !v) })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: "Grid" }), jsx(NiceCheckbox, { checked: showGrid, onChange: () => setShowGrid((v) => !v) })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: "Axes" }), jsx(NiceCheckbox, { checked: showAxes, onChange: () => setShowAxes((v) => !v) })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: "Skeleton" }), jsx(NiceCheckbox, { checked: showSkeleton, onChange: () => setShowSkeleton((v) => !v) })] })] })] }), jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx(NiceIcon, { name: "grid", size: 14 }), " Snapping"] }), jsxs("div", { className: styles.panelContent, children: [jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: "Snap" }), jsx(NiceCheckbox, { checked: snapEnabled, onChange: () => setSnapEnabled((v) => !v) })] }), snapEnabled && (jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: "Grid Size" }), jsx(NiceNumberInput, { className: styles.propInput, step: 0.25, min: 0.1, value: snapGrid, onChange: (val) => setSnapGrid(val !== null && val !== void 0 ? val : 1), style: { width: 60 } })] }))] })] })] })), propTab === 'modifiers' && (jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx(NiceIcon, { name: "settings", size: 14 }), " Modifiers"] }), jsx("div", { className: styles.panelContent, children: selectedNode && selectedNode.object instanceof THREE.Mesh ? (jsxs(Fragment, { children: [jsx(NiceButton, { className: styles.btnSecondary, variant: "ghost", size: "sm", style: { width: '100%', marginBottom: 4 }, onClick: () => {
74
+ }, children: "Upload a video and AI will extract 3D pose data to create an animation clip for the current model." }), jsx("div", { className: styles.aiBtnRow, children: jsx(NiceButton, { className: styles.aiBtn, variant: "ghost", size: "sm", disabled: aiBusy || !rootObjectRef.current, onClick: () => { var _a; return (_a = videoInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: aiBusy ? 'Processing...' : 'Upload Video' }) }), aiStatus && jsx("div", { className: styles.aiStatus, children: aiStatus })] }) })] })] })), propTab === 'material' && (jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx(NiceIcon, { name: "palette", size: 14 }), ' ', t('modelEditorRightPanel.materials', 'Materials')] }), jsx("div", { className: styles.panelContent, children: selectedMaterials.length > 0 ? (selectedMaterials.map((mat, i) => (jsx(MaterialCard, { material: mat, active: i === selMaterialIdx, onClick: () => setSelMaterialIdx(i), onUpdate: () => setMatRefresh((n) => n + 1) }, i)))) : (jsx("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10 }, children: selectedNode ? 'No materials on this object' : 'Select a mesh to inspect' })) })] })), propTab === 'world' && (jsxs(Fragment, { children: [jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx(NiceIcon, { name: "globe", size: 14 }), ' ', t('modelEditorRightPanel.environment', 'Environment')] }), jsx("div", { className: styles.panelContent, children: jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorRightPanel.background', 'Background') }), jsx(NiceColorPicker, { value: bgColor, onChange: (c) => setBgColor(c) }), jsx("span", { style: { fontSize: 9, color: 'var(--nice-text-secondary, #888)' }, children: bgColor })] }) })] }), jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx(NiceIcon, { name: "cloud", size: 14 }), " Fog"] }), jsxs("div", { className: styles.panelContent, children: [jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorRightPanel.enable', 'Enable') }), jsx(NiceCheckbox, { checked: fogEnabled, onChange: () => setFogEnabled((v) => !v) })] }), fogEnabled && (jsxs(Fragment, { children: [jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorRightPanel.color', 'Color') }), jsx(NiceColorPicker, { value: fogColor, onChange: (c) => setFogColor(c) })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorRightPanel.near', 'Near') }), jsx(NiceNumberInput, { className: styles.propInput, value: fogNear, onChange: (val) => setFogNear(val !== null && val !== void 0 ? val : 1), style: { width: 60 } })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: "Far" }), jsx(NiceNumberInput, { className: styles.propInput, value: fogFar, onChange: (val) => setFogFar(val !== null && val !== void 0 ? val : 100), style: { width: 60 } })] })] }))] })] }), jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx(NiceIcon, { name: "lightbulb", size: 14 }), ' ', t('modelEditorRightPanel.sceneLights', 'Scene Lights')] }), jsxs("div", { className: styles.panelContent, children: [jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorRightPanel.showHelpers', 'Show Helpers') }), jsx(NiceCheckbox, { checked: showLightHelpers, onChange: () => setShowLightHelpers((v) => !v) })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorRightPanel.grid', 'Grid') }), jsx(NiceCheckbox, { checked: showGrid, onChange: () => setShowGrid((v) => !v) })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorRightPanel.axes', 'Axes') }), jsx(NiceCheckbox, { checked: showAxes, onChange: () => setShowAxes((v) => !v) })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorRightPanel.skeleton', 'Skeleton') }), jsx(NiceCheckbox, { checked: showSkeleton, onChange: () => setShowSkeleton((v) => !v) })] })] })] }), jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx(NiceIcon, { name: "grid", size: 14 }), " ", t('modelEditorRightPanel.snapping', 'Snapping')] }), jsxs("div", { className: styles.panelContent, children: [jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorRightPanel.snap', 'Snap') }), jsx(NiceCheckbox, { checked: snapEnabled, onChange: () => setSnapEnabled((v) => !v) })] }), snapEnabled && (jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorRightPanel.gridSize', 'Grid Size') }), jsx(NiceNumberInput, { className: styles.propInput, step: 0.25, min: 0.1, value: snapGrid, onChange: (val) => setSnapGrid(val !== null && val !== void 0 ? val : 1), style: { width: 60 } })] }))] })] })] })), propTab === 'modifiers' && (jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx(NiceIcon, { name: "settings", size: 14 }), ' ', t('modelEditorRightPanel.modifiers', 'Modifiers')] }), jsx("div", { className: styles.panelContent, children: selectedNode && selectedNode.object instanceof THREE.Mesh ? (jsxs(Fragment, { children: [jsx(NiceButton, { className: styles.btnSecondary, variant: "ghost", size: "sm", style: { width: '100%', marginBottom: 4 }, onClick: () => {
72
75
  const mesh = selectedNode.object;
73
76
  const geo = mesh.geometry;
74
77
  const pos = geo.attributes.position;
@@ -82,13 +85,13 @@ const ModelEditorRightPanel = React.memo(({ api }) => {
82
85
  const mesh = selectedNode.object;
83
86
  mesh.geometry.computeVertexNormals();
84
87
  setStatusText('Recomputed normals');
85
- }, children: "Recompute Normals" }), jsx(NiceButton, { className: styles.btnSecondary, variant: "ghost", size: "sm", style: { width: '100%', marginBottom: 4 }, onClick: () => {
88
+ }, children: t('modelEditorRightPanel.recomputeNormals', 'Recompute Normals') }), jsx(NiceButton, { className: styles.btnSecondary, variant: "ghost", size: "sm", style: { width: '100%', marginBottom: 4 }, onClick: () => {
86
89
  const mesh = selectedNode.object;
87
90
  const geo = mesh.geometry;
88
91
  geo.center();
89
92
  setStatusText('Centered geometry');
90
93
  setMatRefresh((n) => n + 1);
91
- }, children: "Center Geometry" }), jsx(NiceButton, { className: styles.btnSecondary, variant: "ghost", size: "sm", style: { width: '100%', marginBottom: 4 }, onClick: () => {
94
+ }, children: t('modelEditorRightPanel.centerGeometry', 'Center Geometry') }), jsx(NiceButton, { className: styles.btnSecondary, variant: "ghost", size: "sm", style: { width: '100%', marginBottom: 4 }, onClick: () => {
92
95
  const mesh = selectedNode.object;
93
96
  const geo = mesh.geometry;
94
97
  const idx = geo.index;
@@ -103,8 +106,7 @@ const ModelEditorRightPanel = React.memo(({ api }) => {
103
106
  geo.computeVertexNormals();
104
107
  setStatusText('Flipped normals');
105
108
  }
106
- }, children: "Flip Normals" })] })) : (jsx("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10 }, children: "Select a mesh to apply modifiers" })) })] })), propTab === 'physics' && (jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx(NiceIcon, { name: "zap", size: 14 }), " Physics (Info)"] }), jsx("div", { className: styles.panelContent, children: jsxs("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10, lineHeight: 1.5 }, children: ["Physics simulation preview is not yet available.", jsx("br", {}), jsx("br", {}), "Planned features:", jsx("br", {}), "\u2022 Rigid body simulation", jsx("br", {}), "\u2022 Soft body dynamics", jsx("br", {}), "\u2022 Cloth simulation", jsx("br", {}), "\u2022 Particle systems"] }) })] }))] }));
107
- });
108
- ModelEditorRightPanel.displayName = 'ModelEditorRightPanel';
109
+ }, children: t('modelEditorRightPanel.flipNormals', 'Flip Normals') })] })) : (jsx("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10 }, children: t('modelEditorRightPanel.selectAMeshToApply', 'Select a mesh to apply modifiers') })) })] })), propTab === 'physics' && (jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx(NiceIcon, { name: "zap", size: 14 }), " Physics (Info)"] }), jsx("div", { className: styles.panelContent, children: jsxs("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10, lineHeight: 1.5 }, children: [t('modelEditorRightPanel.physicsSimulationPreviewIsNot', 'Physics simulation preview is not yet available.'), jsx("br", {}), jsx("br", {}), "Planned features:", jsx("br", {}), "\u2022 Rigid body simulation", jsx("br", {}), "\u2022 Soft body dynamics", jsx("br", {}), "\u2022 Cloth simulation", jsx("br", {}), "\u2022 Particle systems"] }) })] }))] }));
110
+ }), { displayName: 'ModelEditorRightPanel' });
109
111
 
110
112
  export { ModelEditorRightPanel as default };
@@ -2,6 +2,7 @@ import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import { NiceTextInput, NiceColorPicker, NiceSlider, NiceSelect } from '@nice2dev/ui';
3
3
  import { useState } from 'react';
4
4
  import * as THREE from 'three';
5
+ import { useNiceTranslation } from '../core/i18n.js';
5
6
  import styles from './ModelEditor.module.css.js';
6
7
  import { DEG } from './modelEditorTypes.js';
7
8
 
@@ -37,6 +38,7 @@ const Vec3RowDeg = ({ label, value, onChange }) => {
37
38
  /** Material card with editable properties */
38
39
  const MaterialCard = ({ material, active, onClick, onUpdate }) => {
39
40
  var _a, _b, _c, _d;
41
+ const { t } = useNiceTranslation();
40
42
  const mat = material;
41
43
  const hasColor = 'color' in mat;
42
44
  const hasMetalness = 'metalness' in mat;
@@ -56,21 +58,21 @@ const MaterialCard = ({ material, active, onClick, onUpdate }) => {
56
58
  mat.needsUpdate = true;
57
59
  onUpdate();
58
60
  };
59
- return (jsxs("div", { className: `${styles.materialCard} ${active ? styles.materialCardActive : ''}`, onClick: onClick, children: [jsx("div", { className: styles.materialHeader, children: jsx("span", { className: styles.materialName, children: mat.name || mat.type }) }), hasColor && (jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: "Color" }), jsx(NiceColorPicker, { value: `#${mat.color.getHexString()}`, onChange: (c) => setColor(c) }), jsxs("span", { style: { fontSize: 10, color: 'var(--nice-text-secondary, #888)' }, children: ["#", mat.color.getHexString()] })] })), hasMetalness && (jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: "Metal" }), jsx(NiceSlider, { min: 0, max: 1, step: 0.01, value: mat.metalness, onChange: (val) => setNum('metalness', val), style: { flex: 1 } }), jsx("span", { style: {
61
+ return (jsxs("div", { className: `${styles.materialCard} ${active ? styles.materialCardActive : ''}`, onClick: onClick, children: [jsx("div", { className: styles.materialHeader, children: jsx("span", { className: styles.materialName, children: mat.name || mat.type }) }), hasColor && (jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorSubComponents.color', 'Color') }), jsx(NiceColorPicker, { value: `#${mat.color.getHexString()}`, onChange: (c) => setColor(c) }), jsxs("span", { style: { fontSize: 10, color: 'var(--nice-text-secondary, #888)' }, children: ["#", mat.color.getHexString()] })] })), hasMetalness && (jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorSubComponents.metal', 'Metal') }), jsx(NiceSlider, { min: 0, max: 1, step: 0.01, value: mat.metalness, onChange: (val) => setNum('metalness', val), style: { flex: 1 } }), jsx("span", { style: {
60
62
  fontSize: 9,
61
63
  color: 'var(--nice-text-secondary, #888)',
62
64
  width: 28,
63
65
  textAlign: 'right',
64
- }, children: mat.metalness.toFixed(2) })] })), hasRoughness && (jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: "Rough" }), jsx(NiceSlider, { min: 0, max: 1, step: 0.01, value: mat.roughness, onChange: (val) => setNum('roughness', val), style: { flex: 1 } }), jsx("span", { style: {
66
+ }, children: mat.metalness.toFixed(2) })] })), hasRoughness && (jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorSubComponents.rough', 'Rough') }), jsx(NiceSlider, { min: 0, max: 1, step: 0.01, value: mat.roughness, onChange: (val) => setNum('roughness', val), style: { flex: 1 } }), jsx("span", { style: {
65
67
  fontSize: 9,
66
68
  color: 'var(--nice-text-secondary, #888)',
67
69
  width: 28,
68
70
  textAlign: 'right',
69
- }, children: mat.roughness.toFixed(2) })] })), hasEmissive && mat.emissive && (jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: "Emissive" }), jsx(NiceColorPicker, { value: `#${mat.emissive.getHexString()}`, onChange: (c) => {
71
+ }, children: mat.roughness.toFixed(2) })] })), hasEmissive && mat.emissive && (jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorSubComponents.emissive', 'Emissive') }), jsx(NiceColorPicker, { value: `#${mat.emissive.getHexString()}`, onChange: (c) => {
70
72
  mat.emissive.set(c);
71
73
  mat.needsUpdate = true;
72
74
  onUpdate();
73
- } })] })), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: "Opacity" }), jsx(NiceSlider, { min: 0, max: 1, step: 0.01, value: mat.opacity, onChange: (val) => {
75
+ } })] })), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorSubComponents.opacity', 'Opacity') }), jsx(NiceSlider, { min: 0, max: 1, step: 0.01, value: mat.opacity, onChange: (val) => {
74
76
  mat.opacity = val;
75
77
  mat.transparent = val < 1;
76
78
  mat.needsUpdate = true;
@@ -80,7 +82,7 @@ const MaterialCard = ({ material, active, onClick, onUpdate }) => {
80
82
  color: 'var(--nice-text-secondary, #888)',
81
83
  width: 28,
82
84
  textAlign: 'right',
83
- }, children: mat.opacity.toFixed(2) })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: "Side" }), jsx(NiceSelect, { className: styles.propSelect, value: String(mat.side), onChange: (val) => {
85
+ }, children: mat.opacity.toFixed(2) })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorSubComponents.side', 'Side') }), jsx(NiceSelect, { className: styles.propSelect, value: String(mat.side), onChange: (val) => {
84
86
  mat.side = parseInt(val);
85
87
  mat.needsUpdate = true;
86
88
  onUpdate();
@@ -88,7 +90,7 @@ const MaterialCard = ({ material, active, onClick, onUpdate }) => {
88
90
  { value: String(THREE.FrontSide), label: 'Front' },
89
91
  { value: String(THREE.BackSide), label: 'Back' },
90
92
  { value: String(THREE.DoubleSide), label: 'Double' },
91
- ] })] }), hasMap && mat.map && (jsxs("div", { className: styles.textureSlot, children: [jsx("div", { className: styles.textureThumb, children: mat.map.image != null && (jsx("img", { src: (_a = mat.map.image.src) !== null && _a !== void 0 ? _a : '', alt: "diffuse" })) }), jsxs("div", { className: styles.textureInfo, children: [jsx("div", { className: styles.textureName, children: "Diffuse Map" }), jsxs("div", { style: { fontSize: 9, color: 'var(--nice-text-secondary, #666)' }, children: [(_b = mat.map.image) === null || _b === void 0 ? void 0 : _b.width, "\u00D7", (_c = mat.map.image) === null || _c === void 0 ? void 0 : _c.height] })] })] })), hasNormalMap && mat.normalMap && (jsxs("div", { className: styles.textureSlot, children: [jsx("div", { className: styles.textureThumb, children: mat.normalMap.image != null && (jsx("img", { src: (_d = mat.normalMap.image.src) !== null && _d !== void 0 ? _d : '', alt: "normal" })) }), jsx("div", { className: styles.textureInfo, children: jsx("div", { className: styles.textureName, children: "Normal Map" }) })] }))] }));
93
+ ] })] }), hasMap && mat.map && (jsxs("div", { className: styles.textureSlot, children: [jsx("div", { className: styles.textureThumb, children: mat.map.image != null && (jsx("img", { src: (_a = mat.map.image.src) !== null && _a !== void 0 ? _a : '', alt: "diffuse" })) }), jsxs("div", { className: styles.textureInfo, children: [jsx("div", { className: styles.textureName, children: t('modelEditorSubComponents.diffuseMap', 'Diffuse Map') }), jsxs("div", { style: { fontSize: 9, color: 'var(--nice-text-secondary, #666)' }, children: [(_b = mat.map.image) === null || _b === void 0 ? void 0 : _b.width, "\u00D7", (_c = mat.map.image) === null || _c === void 0 ? void 0 : _c.height] })] })] })), hasNormalMap && mat.normalMap && (jsxs("div", { className: styles.textureSlot, children: [jsx("div", { className: styles.textureThumb, children: mat.normalMap.image != null && (jsx("img", { src: (_d = mat.normalMap.image.src) !== null && _d !== void 0 ? _d : '', alt: "normal" })) }), jsx("div", { className: styles.textureInfo, children: jsx("div", { className: styles.textureName, children: t('modelEditorSubComponents.normalMap', 'Normal Map') }) })] }))] }));
92
94
  };
93
95
 
94
96
  export { MaterialCard, Vec3Row, Vec3RowDeg };
@@ -1,12 +1,15 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import { NiceIcon, NiceButton, NiceSlider } from '@nice2dev/ui';
3
3
  import React from 'react';
4
+ import { useNiceTranslation } from '../core/i18n.js';
4
5
  import styles from './ModelEditor.module.css.js';
5
6
  import { fmtTime } from './modelEditorTypes.js';
6
7
 
7
- const ModelEditorTimeline = React.memo(({ api }) => {
8
+ const ModelEditorTimeline = /* @__PURE__ */ Object.assign(
9
+ /* @__PURE__ */ React.memo(({ api }) => {
10
+ const { t } = useNiceTranslation();
8
11
  const { bottomCollapsed, setBottomCollapsed, animations, activeAnimIdx, isPlaying, animTime, animDuration, animSpeed, setAnimSpeed, loopAnim, setLoopAnim, seekAnim, togglePlay, stopAnim, playClip, mergeInputRef, } = api;
9
- return (jsxs("div", { className: `${styles.bottomPanel} ${bottomCollapsed ? styles.bottomPanelCollapsed : ''}`, children: [jsxs("div", { className: styles.bottomPanelHeader, children: [jsx("span", { className: styles.bottomToggle, onClick: () => setBottomCollapsed((c) => !c), children: jsx(NiceIcon, { name: bottomCollapsed ? 'chevron-right' : 'chevron-down', size: 14 }) }), jsxs("span", { className: styles.bottomPanelTitle, onClick: () => setBottomCollapsed((c) => !c), children: ["Animation (", animations.length, " clip", animations.length !== 1 ? 's' : '', ")"] }), jsx("div", { className: styles.menuSep }), jsxs("div", { className: styles.transportBar, children: [jsx(NiceButton, { className: styles.transportBtn, variant: "ghost", size: "sm", onClick: () => seekAnim(0), title: "Go to start", "aria-label": "Go to start", children: jsx(NiceIcon, { name: "chevron-left", size: 16 }) }), jsx(NiceButton, { className: styles.transportBtn, variant: isPlaying ? 'primary' : 'ghost', size: "sm", onClick: togglePlay, title: "Play/Pause (Space)", "aria-label": "Play/Pause (Space)", children: jsx(NiceIcon, { name: isPlaying ? 'pause' : 'play', size: 16 }) }), jsx(NiceButton, { className: styles.transportBtn, variant: "ghost", size: "sm", onClick: stopAnim, title: "Stop", "aria-label": "Stop", children: jsx(NiceIcon, { name: "stop", size: 16 }) }), jsx(NiceButton, { className: styles.transportBtn, variant: loopAnim ? 'primary' : 'ghost', size: "sm", onClick: () => setLoopAnim((v) => !v), title: "Loop", "aria-label": "Loop", children: jsx(NiceIcon, { name: "refresh", size: 16 }) })] }), jsxs("span", { className: styles.transportTime, children: [fmtTime(animTime), " / ", fmtTime(animDuration)] }), jsx("div", { className: styles.menuSep }), jsx("label", { className: styles.menuLabel, children: "Speed" }), jsx(NiceSlider, { min: 0.1, max: 3, step: 0.1, value: animSpeed, onChange: (val) => setAnimSpeed(val), style: { width: 60 } }), jsxs("span", { className: styles.menuLabel, children: [animSpeed.toFixed(1), "\u00D7"] })] }), !bottomCollapsed && (jsxs("div", { className: styles.animListArea, children: [jsxs("div", { className: styles.animList, children: [animations.length === 0 && (jsx("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10, padding: 8 }, children: "No animations loaded" })), animations.map((anim, i) => (jsxs("div", { className: `${styles.animItem} ${i === activeAnimIdx ? styles.animItemActive : ''}`, onClick: () => playClip(i), children: [jsx("span", { children: anim.name }), jsxs("span", { className: styles.animDuration, children: [anim.duration.toFixed(1), "s"] })] }, `${anim.name}-${i}`))), jsx("div", { style: { padding: '4px 8px' }, children: jsx(NiceButton, { className: styles.btnSecondary, variant: "ghost", size: "sm", style: { width: '100%', fontSize: 10 }, onClick: () => { var _a; return (_a = mergeInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: "+ Add Animation File" }) })] }), jsxs("div", { className: styles.timelineArea, children: [jsxs("div", { className: styles.timelineRuler, children: [animDuration > 0 &&
12
+ return (jsxs("div", { className: `${styles.bottomPanel} ${bottomCollapsed ? styles.bottomPanelCollapsed : ''}`, children: [jsxs("div", { className: styles.bottomPanelHeader, children: [jsx("span", { className: styles.bottomToggle, onClick: () => setBottomCollapsed((c) => !c), children: jsx(NiceIcon, { name: bottomCollapsed ? 'chevron-right' : 'chevron-down', size: 14 }) }), jsxs("span", { className: styles.bottomPanelTitle, onClick: () => setBottomCollapsed((c) => !c), children: ["Animation (", animations.length, " clip", animations.length !== 1 ? 's' : '', ")"] }), jsx("div", { className: styles.menuSep }), jsxs("div", { className: styles.transportBar, children: [jsx(NiceButton, { className: styles.transportBtn, variant: "ghost", size: "sm", onClick: () => seekAnim(0), title: t('modelEditorTimeline.goToStart', 'Go to start'), "aria-label": t('modelEditorTimeline.goToStart', 'Go to start'), children: jsx(NiceIcon, { name: "chevron-left", size: 16 }) }), jsx(NiceButton, { className: styles.transportBtn, variant: isPlaying ? 'primary' : 'ghost', size: "sm", onClick: togglePlay, title: t('modelEditorTimeline.playPauseSpace', 'Play/Pause (Space)'), "aria-label": t('modelEditorTimeline.playPauseSpace', 'Play/Pause (Space)'), children: jsx(NiceIcon, { name: isPlaying ? 'pause' : 'play', size: 16 }) }), jsx(NiceButton, { className: styles.transportBtn, variant: "ghost", size: "sm", onClick: stopAnim, title: t('modelEditorTimeline.stop', 'Stop'), "aria-label": t('modelEditorTimeline.stop', 'Stop'), children: jsx(NiceIcon, { name: "stop", size: 16 }) }), jsx(NiceButton, { className: styles.transportBtn, variant: loopAnim ? 'primary' : 'ghost', size: "sm", onClick: () => setLoopAnim((v) => !v), title: t('modelEditorTimeline.loop', 'Loop'), "aria-label": t('modelEditorTimeline.loop', 'Loop'), children: jsx(NiceIcon, { name: "refresh", size: 16 }) })] }), jsxs("span", { className: styles.transportTime, children: [fmtTime(animTime), " / ", fmtTime(animDuration)] }), jsx("div", { className: styles.menuSep }), jsx("label", { className: styles.menuLabel, children: t('modelEditorTimeline.speed', 'Speed') }), jsx(NiceSlider, { min: 0.1, max: 3, step: 0.1, value: animSpeed, onChange: (val) => setAnimSpeed(val), style: { width: 60 } }), jsxs("span", { className: styles.menuLabel, children: [animSpeed.toFixed(1), "\u00D7"] })] }), !bottomCollapsed && (jsxs("div", { className: styles.animListArea, children: [jsxs("div", { className: styles.animList, children: [animations.length === 0 && (jsx("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10, padding: 8 }, children: t('modelEditorTimeline.noAnimationsLoaded', 'No animations loaded') })), animations.map((anim, i) => (jsxs("div", { className: `${styles.animItem} ${i === activeAnimIdx ? styles.animItemActive : ''}`, onClick: () => playClip(i), children: [jsx("span", { children: anim.name }), jsxs("span", { className: styles.animDuration, children: [anim.duration.toFixed(1), "s"] })] }, `${anim.name}-${i}`))), jsx("div", { style: { padding: '4px 8px' }, children: jsx(NiceButton, { className: styles.btnSecondary, variant: "ghost", size: "sm", style: { width: '100%', fontSize: 10 }, onClick: () => { var _a; return (_a = mergeInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: "+ Add Animation File" }) })] }), jsxs("div", { className: styles.timelineArea, children: [jsxs("div", { className: styles.timelineRuler, children: [animDuration > 0 &&
10
13
  Array.from({ length: Math.ceil(animDuration) + 1 }, (_, i) => {
11
14
  const pct = (i / animDuration) * 100;
12
15
  return (jsxs(React.Fragment, { children: [jsx("div", { className: styles.rulerTick, style: { left: `${pct}%` } }), jsxs("span", { className: styles.rulerLabel, style: { left: `${pct}%` }, children: [i, "s"] })] }, i));
@@ -24,7 +27,6 @@ const ModelEditorTimeline = React.memo(({ api }) => {
24
27
  left: `${(animTime / animDuration) * 100}%`,
25
28
  height: '100%',
26
29
  } })) })] })] }))] }));
27
- });
28
- ModelEditorTimeline.displayName = 'ModelEditorTimeline';
30
+ }), { displayName: 'ModelEditorTimeline' });
29
31
 
30
32
  export { ModelEditorTimeline as default };
@@ -1,12 +1,14 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import { NiceButton, NiceIcon } from '@nice2dev/ui';
3
3
  import React from 'react';
4
+ import { useNiceTranslation } from '../core/i18n.js';
4
5
  import styles from './ModelEditor.module.css.js';
5
6
 
6
- const ModelEditorToolbar = React.memo(({ api }) => {
7
+ const ModelEditorToolbar = /* @__PURE__ */ Object.assign(
8
+ /* @__PURE__ */ React.memo(({ api }) => {
9
+ const { t } = useNiceTranslation();
7
10
  const { transformMode, setTransformMode, snapEnabled, setSnapEnabled, gizmoSpace, setGizmoSpace, rootObjectRef, focusOnObject, setCameraPreset, deleteSelected, duplicateSelected, selectedNode, } = api;
8
- return (jsxs("div", { className: styles.toolbar, children: [jsx(NiceButton, { className: styles.toolBtn, variant: transformMode === 'translate' ? 'primary' : 'ghost', size: "sm", onClick: () => setTransformMode('translate'), title: "Translate (G)", "aria-label": "Translate (G)", children: jsx(NiceIcon, { name: "move", size: 16 }) }), jsx(NiceButton, { className: styles.toolBtn, variant: transformMode === 'rotate' ? 'primary' : 'ghost', size: "sm", onClick: () => setTransformMode('rotate'), title: "Rotate (R)", "aria-label": "Rotate (R)", children: jsx(NiceIcon, { name: "refresh", size: 16 }) }), jsx(NiceButton, { className: styles.toolBtn, variant: transformMode === 'scale' ? 'primary' : 'ghost', size: "sm", onClick: () => setTransformMode('scale'), title: "Scale (S)", "aria-label": "Scale (S)", children: jsx(NiceIcon, { name: "hexagon", size: 16 }) }), jsx("div", { className: styles.toolSep }), jsx(NiceButton, { className: styles.toolBtn, variant: snapEnabled ? 'primary' : 'ghost', size: "sm", onClick: () => setSnapEnabled((v) => !v), title: `Snap to Grid (${snapEnabled ? 'ON' : 'OFF'})`, "aria-label": "Snap to Grid", children: jsx(NiceIcon, { name: "grid", size: 16 }) }), jsx(NiceButton, { className: styles.toolBtn, variant: "ghost", size: "sm", onClick: () => setGizmoSpace((s) => (s === 'local' ? 'world' : 'local')), title: `Orientation: ${gizmoSpace}`, "aria-label": `Orientation: ${gizmoSpace}`, children: jsx(NiceIcon, { name: gizmoSpace === 'local' ? 'target' : 'globe', size: 16 }) }), jsx("div", { className: styles.toolSep }), jsx(NiceButton, { className: styles.toolBtn, variant: "ghost", size: "sm", onClick: () => rootObjectRef.current && focusOnObject(rootObjectRef.current), title: "Focus (F)", "aria-label": "Focus (F)", children: jsx(NiceIcon, { name: "target", size: 16 }) }), jsx(NiceButton, { className: styles.toolBtn, variant: "ghost", size: "sm", onClick: () => setCameraPreset('front'), title: "Front view (Numpad 1)", children: "1" }), jsx(NiceButton, { className: styles.toolBtn, variant: "ghost", size: "sm", onClick: () => setCameraPreset('right'), title: "Right view (Numpad 3)", children: "3" }), jsx(NiceButton, { className: styles.toolBtn, variant: "ghost", size: "sm", onClick: () => setCameraPreset('top'), title: "Top view (Numpad 7)", children: "7" }), jsx("div", { className: styles.toolSep }), jsx(NiceButton, { className: styles.toolBtn, variant: "ghost", size: "sm", onClick: deleteSelected, title: "Delete selected (Del)", "aria-label": "Delete selected (Del)", children: jsx(NiceIcon, { name: "trash", size: 16 }) }), jsx(NiceButton, { className: styles.toolBtn, variant: "ghost", size: "sm", onClick: duplicateSelected, title: "Duplicate (Shift+D)", disabled: !selectedNode, "aria-label": "Duplicate (Shift+D)", children: jsx(NiceIcon, { name: "clipboard", size: 16 }) })] }));
9
- });
10
- ModelEditorToolbar.displayName = 'ModelEditorToolbar';
11
+ return (jsxs("div", { className: styles.toolbar, children: [jsx(NiceButton, { className: styles.toolBtn, variant: transformMode === 'translate' ? 'primary' : 'ghost', size: "sm", onClick: () => setTransformMode('translate'), title: t('modelEditorToolbar.translateG', 'Translate (G)'), "aria-label": t('modelEditorToolbar.translateG', 'Translate (G)'), children: jsx(NiceIcon, { name: "move", size: 16 }) }), jsx(NiceButton, { className: styles.toolBtn, variant: transformMode === 'rotate' ? 'primary' : 'ghost', size: "sm", onClick: () => setTransformMode('rotate'), title: t('modelEditorToolbar.rotateR', 'Rotate (R)'), "aria-label": t('modelEditorToolbar.rotateR', 'Rotate (R)'), children: jsx(NiceIcon, { name: "refresh", size: 16 }) }), jsx(NiceButton, { className: styles.toolBtn, variant: transformMode === 'scale' ? 'primary' : 'ghost', size: "sm", onClick: () => setTransformMode('scale'), title: t('modelEditorToolbar.scaleS', 'Scale (S)'), "aria-label": t('modelEditorToolbar.scaleS', 'Scale (S)'), children: jsx(NiceIcon, { name: "hexagon", size: 16 }) }), jsx("div", { className: styles.toolSep }), jsx(NiceButton, { className: styles.toolBtn, variant: snapEnabled ? 'primary' : 'ghost', size: "sm", onClick: () => setSnapEnabled((v) => !v), title: `Snap to Grid (${snapEnabled ? 'ON' : 'OFF'})`, "aria-label": t('modelEditorToolbar.snapToGrid', 'Snap to Grid'), children: jsx(NiceIcon, { name: "grid", size: 16 }) }), jsx(NiceButton, { className: styles.toolBtn, variant: "ghost", size: "sm", onClick: () => setGizmoSpace((s) => (s === 'local' ? 'world' : 'local')), title: `Orientation: ${gizmoSpace}`, "aria-label": `Orientation: ${gizmoSpace}`, children: jsx(NiceIcon, { name: gizmoSpace === 'local' ? 'target' : 'globe', size: 16 }) }), jsx("div", { className: styles.toolSep }), jsx(NiceButton, { className: styles.toolBtn, variant: "ghost", size: "sm", onClick: () => rootObjectRef.current && focusOnObject(rootObjectRef.current), title: t('modelEditorToolbar.focusF', 'Focus (F)'), "aria-label": t('modelEditorToolbar.focusF', 'Focus (F)'), children: jsx(NiceIcon, { name: "target", size: 16 }) }), jsx(NiceButton, { className: styles.toolBtn, variant: "ghost", size: "sm", onClick: () => setCameraPreset('front'), title: t('modelEditorToolbar.frontViewNumpad1', 'Front view (Numpad 1)'), children: "1" }), jsx(NiceButton, { className: styles.toolBtn, variant: "ghost", size: "sm", onClick: () => setCameraPreset('right'), title: t('modelEditorToolbar.rightViewNumpad3', 'Right view (Numpad 3)'), children: "3" }), jsx(NiceButton, { className: styles.toolBtn, variant: "ghost", size: "sm", onClick: () => setCameraPreset('top'), title: t('modelEditorToolbar.topViewNumpad7', 'Top view (Numpad 7)'), children: "7" }), jsx("div", { className: styles.toolSep }), jsx(NiceButton, { className: styles.toolBtn, variant: "ghost", size: "sm", onClick: deleteSelected, title: t('modelEditorToolbar.deleteSelectedDel', 'Delete selected (Del)'), "aria-label": t('modelEditorToolbar.deleteSelectedDel', 'Delete selected (Del)'), children: jsx(NiceIcon, { name: "trash", size: 16 }) }), jsx(NiceButton, { className: styles.toolBtn, variant: "ghost", size: "sm", onClick: duplicateSelected, title: t('modelEditorToolbar.duplicateShiftD', 'Duplicate (Shift+D)'), disabled: !selectedNode, "aria-label": t('modelEditorToolbar.duplicateShiftD', 'Duplicate (Shift+D)'), children: jsx(NiceIcon, { name: "clipboard", size: 16 }) })] }));
12
+ }), { displayName: 'ModelEditorToolbar' });
11
13
 
12
14
  export { ModelEditorToolbar as default };