@nice2dev/ui-3d 1.2.0 → 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 (74) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/dist/cjs/core/editorAsk.js +48 -0
  3. package/dist/cjs/core/i18n.js +7 -3
  4. package/dist/cjs/dance/DanceScoreEngine.js +7 -0
  5. package/dist/cjs/dance/PoseDetector.js +4 -2
  6. package/dist/cjs/material/NiceMaterialEditor.js +7 -3
  7. package/dist/cjs/material/materialEditorTypes.js +1 -1
  8. package/dist/cjs/material/materialNodeDefinitions.js +14 -11
  9. package/dist/cjs/model/ModelEditor.js +3 -2
  10. package/dist/cjs/model/ModelEditorLeftPanel.js +10 -4
  11. package/dist/cjs/model/ModelEditorMenuBar.js +7 -5
  12. package/dist/cjs/model/ModelEditorRightPanel.js +18 -16
  13. package/dist/cjs/model/ModelEditorSubComponents.js +8 -6
  14. package/dist/cjs/model/ModelEditorTimeline.js +6 -4
  15. package/dist/cjs/model/ModelEditorToolbar.js +6 -4
  16. package/dist/cjs/model/ModelEditorViewport.js +16 -6
  17. package/dist/cjs/model/ModelViewer.js +6 -4
  18. package/dist/cjs/model/NiceArmatureEditor.js +6 -4
  19. package/dist/cjs/model/NiceMorphTargetEditor.js +5 -3
  20. package/dist/cjs/model/NiceOctree.js +3 -1
  21. package/dist/cjs/model/NicePhysicsSimulation.js +6 -4
  22. package/dist/cjs/model/NiceProceduralGeometry.js +5 -3
  23. package/dist/cjs/model/NiceTerrainEditor.js +8 -6
  24. package/dist/cjs/model/NiceWeightPainter.js +4 -2
  25. package/dist/cjs/model/NiceXRPreview.js +4 -2
  26. package/dist/cjs/model/useModelEditor.js +22 -15
  27. package/dist/cjs/nice2dev-ui-3d.css +1 -1
  28. package/dist/cjs/particle/NiceParticleEditor.js +14 -9
  29. package/dist/cjs/rendering/NiceCascadedShadows.js +3 -1
  30. package/dist/cjs/rendering/NiceRenderExport.js +9 -3
  31. package/dist/cjs/rendering/NiceSSAO.js +3 -1
  32. package/dist/cjs/rendering/NiceSSR.js +3 -1
  33. package/dist/cjs/rendering/NiceWebGPURenderer.js +4 -2
  34. package/dist/cjs/uv/NiceUVEditor.js +16 -12
  35. package/dist/cjs/uv/uvEditorTypes.js +1 -0
  36. package/dist/esm/core/editorAsk.js +45 -0
  37. package/dist/esm/core/i18n.js +8 -4
  38. package/dist/esm/dance/DanceScoreEngine.js +7 -0
  39. package/dist/esm/dance/PoseDetector.js +4 -2
  40. package/dist/esm/material/NiceMaterialEditor.js +7 -3
  41. package/dist/esm/material/materialEditorTypes.js +1 -1
  42. package/dist/esm/material/materialNodeDefinitions.js +14 -11
  43. package/dist/esm/model/ModelEditor.js +3 -2
  44. package/dist/esm/model/ModelEditorLeftPanel.js +10 -4
  45. package/dist/esm/model/ModelEditorMenuBar.js +7 -5
  46. package/dist/esm/model/ModelEditorRightPanel.js +18 -16
  47. package/dist/esm/model/ModelEditorSubComponents.js +8 -6
  48. package/dist/esm/model/ModelEditorTimeline.js +6 -4
  49. package/dist/esm/model/ModelEditorToolbar.js +6 -4
  50. package/dist/esm/model/ModelEditorViewport.js +16 -6
  51. package/dist/esm/model/ModelViewer.js +6 -4
  52. package/dist/esm/model/NiceArmatureEditor.js +6 -4
  53. package/dist/esm/model/NiceMorphTargetEditor.js +5 -3
  54. package/dist/esm/model/NiceOctree.js +3 -1
  55. package/dist/esm/model/NicePhysicsSimulation.js +6 -4
  56. package/dist/esm/model/NiceProceduralGeometry.js +5 -3
  57. package/dist/esm/model/NiceTerrainEditor.js +8 -6
  58. package/dist/esm/model/NiceWeightPainter.js +4 -2
  59. package/dist/esm/model/NiceXRPreview.js +4 -2
  60. package/dist/esm/model/useModelEditor.js +22 -15
  61. package/dist/esm/nice2dev-ui-3d.css +1 -1
  62. package/dist/esm/particle/NiceParticleEditor.js +14 -9
  63. package/dist/esm/rendering/NiceCascadedShadows.js +3 -1
  64. package/dist/esm/rendering/NiceRenderExport.js +9 -3
  65. package/dist/esm/rendering/NiceSSAO.js +3 -1
  66. package/dist/esm/rendering/NiceSSR.js +3 -1
  67. package/dist/esm/rendering/NiceWebGPURenderer.js +4 -2
  68. package/dist/esm/uv/NiceUVEditor.js +16 -12
  69. package/dist/esm/uv/uvEditorTypes.js +1 -0
  70. package/dist/types/core/editorAsk.d.ts +26 -0
  71. package/dist/types/core/i18n.d.ts +1 -17
  72. package/dist/types/dance/DanceScoreEngine.d.ts +0 -6
  73. package/dist/types/model/useModelEditor.d.ts +3 -0
  74. package/package.json +3 -3
@@ -4,6 +4,7 @@ var jsxRuntime = require('react/jsx-runtime');
4
4
  var ui = require('@nice2dev/ui');
5
5
  var React = require('react');
6
6
  var THREE = require('three');
7
+ var i18n = require('../core/i18n.js');
7
8
  var ModelEditor_module = require('./ModelEditor.module.css.js');
8
9
  var ModelEditorSubComponents = require('./ModelEditorSubComponents.js');
9
10
  var modelEditorTypes = require('./modelEditorTypes.js');
@@ -27,7 +28,9 @@ function _interopNamespaceDefault(e) {
27
28
 
28
29
  var THREE__namespace = /*#__PURE__*/_interopNamespaceDefault(THREE);
29
30
 
30
- const ModelEditorRightPanel = React.memo(({ api }) => {
31
+ const ModelEditorRightPanel = /* @__PURE__ */ Object.assign(
32
+ /* @__PURE__ */ React.memo(({ api }) => {
33
+ const { t } = i18n.useNiceTranslation();
31
34
  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;
32
35
  return (jsxRuntime.jsxs("div", { className: ModelEditor_module.rightPanel, children: [jsxRuntime.jsx("div", { className: ModelEditor_module.propTabs, children: ['object', 'material', 'world', 'modifiers', 'physics'].map((t) => (jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.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: jsxRuntime.jsx(ui.NiceIcon, { name: t === 'object'
33
36
  ? 'settings'
@@ -37,13 +40,13 @@ const ModelEditorRightPanel = React.memo(({ api }) => {
37
40
  ? 'globe'
38
41
  : t === 'modifiers'
39
42
  ? 'settings'
40
- : 'zap', size: 16 }) }, t))) }), propTab === 'object' && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "settings", size: 14 }), " Transform"] }), jsxRuntime.jsx("div", { className: ModelEditor_module.panelContent, children: selectedNode ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "Name" }), jsxRuntime.jsx(ui.NiceTextInput, { className: ModelEditor_module.propInput, value: selectedNode.name, onChange: (val) => {
43
+ : 'zap', size: 16 }) }, t))) }), propTab === 'object' && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "settings", size: 14 }), ' ', t('modelEditorRightPanel.transform', 'Transform')] }), jsxRuntime.jsx("div", { className: ModelEditor_module.panelContent, children: selectedNode ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.name', 'Name') }), jsxRuntime.jsx(ui.NiceTextInput, { className: ModelEditor_module.propInput, value: selectedNode.name, onChange: (val) => {
41
44
  selectedNode.object.name = val;
42
45
  rebuildTree();
43
- } })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "Type" }), jsxRuntime.jsx("span", { style: { fontSize: 10, color: 'var(--nice-text-secondary, #888)' }, children: selectedNode.type })] }), jsxRuntime.jsx(ModelEditorSubComponents.Vec3Row, { label: "Position", value: selectedNode.object.position, onChange: () => setMatRefresh((n) => n + 1) }), jsxRuntime.jsx(ModelEditorSubComponents.Vec3RowDeg, { label: "Rotation", value: selectedNode.object.rotation, onChange: () => setMatRefresh((n) => n + 1) }), jsxRuntime.jsx(ModelEditorSubComponents.Vec3Row, { label: "Scale", value: selectedNode.object.scale, onChange: () => setMatRefresh((n) => n + 1) })] })) : (jsxRuntime.jsx("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10 }, children: "Select an object" })) })] }), selectedNode && selectedNode.object instanceof THREE__namespace.Light && (jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "lightbulb", size: 14 }), " Light"] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelContent, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "Color" }), jsxRuntime.jsx(ui.NiceColorPicker, { value: `#${selectedNode.object.color.getHexString()}`, onChange: (c) => {
46
+ } })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.type', 'Type') }), jsxRuntime.jsx("span", { style: { fontSize: 10, color: 'var(--nice-text-secondary, #888)' }, children: selectedNode.type })] }), jsxRuntime.jsx(ModelEditorSubComponents.Vec3Row, { label: t('modelEditorRightPanel.position', 'Position'), value: selectedNode.object.position, onChange: () => setMatRefresh((n) => n + 1) }), jsxRuntime.jsx(ModelEditorSubComponents.Vec3RowDeg, { label: t('modelEditorRightPanel.rotation', 'Rotation'), value: selectedNode.object.rotation, onChange: () => setMatRefresh((n) => n + 1) }), jsxRuntime.jsx(ModelEditorSubComponents.Vec3Row, { label: t('modelEditorRightPanel.scale', 'Scale'), value: selectedNode.object.scale, onChange: () => setMatRefresh((n) => n + 1) })] })) : (jsxRuntime.jsx("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10 }, children: t('modelEditorRightPanel.selectAnObject', 'Select an object') })) })] }), selectedNode && selectedNode.object instanceof THREE__namespace.Light && (jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "lightbulb", size: 14 }), ' ', t('modelEditorRightPanel.light', 'Light')] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelContent, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.color', 'Color') }), jsxRuntime.jsx(ui.NiceColorPicker, { value: `#${selectedNode.object.color.getHexString()}`, onChange: (c) => {
44
47
  selectedNode.object.color.set(c);
45
48
  setMatRefresh((n) => n + 1);
46
- } })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "Intensity" }), jsxRuntime.jsx(ui.NiceSlider, { min: 0, max: 10, step: 0.1, value: selectedNode.object.intensity, onChange: (val) => {
49
+ } })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.intensity', 'Intensity') }), jsxRuntime.jsx(ui.NiceSlider, { min: 0, max: 10, step: 0.1, value: selectedNode.object.intensity, onChange: (val) => {
47
50
  selectedNode.object.intensity = val;
48
51
  setMatRefresh((n) => n + 1);
49
52
  }, style: { flex: 1 } }), jsxRuntime.jsx("span", { style: {
@@ -51,10 +54,10 @@ const ModelEditorRightPanel = React.memo(({ api }) => {
51
54
  color: 'var(--nice-text-secondary, #888)',
52
55
  width: 28,
53
56
  textAlign: 'right',
54
- }, children: selectedNode.object.intensity.toFixed(1) })] }), 'castShadow' in selectedNode.object && (jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "Shadow" }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: selectedNode.object.castShadow, onChange: (checked) => {
57
+ }, children: selectedNode.object.intensity.toFixed(1) })] }), 'castShadow' in selectedNode.object && (jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.shadow', 'Shadow') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: selectedNode.object.castShadow, onChange: (checked) => {
55
58
  selectedNode.object.castShadow = checked;
56
59
  setMatRefresh((n) => n + 1);
57
- } })] })), selectedNode.object instanceof THREE__namespace.SpotLight && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "Angle" }), jsxRuntime.jsx(ui.NiceSlider, { min: 0, max: 90, step: 1, value: selectedNode.object.angle * modelEditorTypes.DEG, onChange: (val) => {
60
+ } })] })), selectedNode.object instanceof THREE__namespace.SpotLight && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.angle', 'Angle') }), jsxRuntime.jsx(ui.NiceSlider, { min: 0, max: 90, step: 1, value: selectedNode.object.angle * modelEditorTypes.DEG, onChange: (val) => {
58
61
  selectedNode.object.angle = val / modelEditorTypes.DEG;
59
62
  setMatRefresh((n) => n + 1);
60
63
  }, style: { flex: 1 } }), jsxRuntime.jsxs("span", { style: {
@@ -62,10 +65,10 @@ const ModelEditorRightPanel = React.memo(({ api }) => {
62
65
  color: 'var(--nice-text-secondary, #888)',
63
66
  width: 28,
64
67
  textAlign: 'right',
65
- }, children: [(selectedNode.object.angle * modelEditorTypes.DEG).toFixed(0), "\u00B0"] })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "Penumbra" }), jsxRuntime.jsx(ui.NiceSlider, { min: 0, max: 1, step: 0.01, value: selectedNode.object.penumbra, onChange: (val) => {
68
+ }, children: [(selectedNode.object.angle * modelEditorTypes.DEG).toFixed(0), "\u00B0"] })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.penumbra', 'Penumbra') }), jsxRuntime.jsx(ui.NiceSlider, { min: 0, max: 1, step: 0.01, value: selectedNode.object.penumbra, onChange: (val) => {
66
69
  selectedNode.object.penumbra = val;
67
70
  setMatRefresh((n) => n + 1);
68
- }, style: { flex: 1 } })] })] }))] })] })), selectedNode && selectedNode.object instanceof THREE__namespace.PerspectiveCamera && (jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "camera", size: 14 }), " Camera"] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelContent, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "FOV" }), jsxRuntime.jsx(ui.NiceSlider, { min: 10, max: 120, step: 1, value: selectedNode.object.fov, onChange: (val) => {
71
+ }, style: { flex: 1 } })] })] }))] })] })), selectedNode && selectedNode.object instanceof THREE__namespace.PerspectiveCamera && (jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "camera", size: 14 }), " ", t('modelEditorRightPanel.camera', 'Camera')] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelContent, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "FOV" }), jsxRuntime.jsx(ui.NiceSlider, { min: 10, max: 120, step: 1, value: selectedNode.object.fov, onChange: (val) => {
69
72
  const cam = selectedNode.object;
70
73
  cam.fov = val;
71
74
  cam.updateProjectionMatrix();
@@ -75,7 +78,7 @@ const ModelEditorRightPanel = React.memo(({ api }) => {
75
78
  color: 'var(--nice-text-secondary, #888)',
76
79
  width: 28,
77
80
  textAlign: 'right',
78
- }, children: [selectedNode.object.fov.toFixed(0), "\u00B0"] })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "Near" }), jsxRuntime.jsx(ui.NiceNumberInput, { className: ModelEditor_module.propInput, step: 0.01, value: selectedNode.object.near, onChange: (val) => {
81
+ }, children: [selectedNode.object.fov.toFixed(0), "\u00B0"] })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.near', 'Near') }), jsxRuntime.jsx(ui.NiceNumberInput, { className: ModelEditor_module.propInput, step: 0.01, value: selectedNode.object.near, onChange: (val) => {
79
82
  const cam = selectedNode.object;
80
83
  cam.near = val !== null && val !== void 0 ? val : 0.1;
81
84
  cam.updateProjectionMatrix();
@@ -85,11 +88,11 @@ const ModelEditorRightPanel = React.memo(({ api }) => {
85
88
  cam.far = val !== null && val !== void 0 ? val : 1000;
86
89
  cam.updateProjectionMatrix();
87
90
  setMatRefresh((n) => n + 1);
88
- }, style: { width: 80 } })] })] })] })), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "wand", size: 14 }), " AI Animation"] }), jsxRuntime.jsx("div", { className: ModelEditor_module.panelContent, children: jsxRuntime.jsxs("div", { className: ModelEditor_module.aiPanel, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.aiHeader, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "wand", size: 14 }), " Generate animation from video"] }), jsxRuntime.jsx("p", { style: {
91
+ }, style: { width: 80 } })] })] })] })), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "wand", size: 14 }), ' ', t('modelEditorRightPanel.aiAnimation', 'AI Animation')] }), jsxRuntime.jsx("div", { className: ModelEditor_module.panelContent, children: jsxRuntime.jsxs("div", { className: ModelEditor_module.aiPanel, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.aiHeader, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "wand", size: 14 }), ' ', t('modelEditorRightPanel.generateAnimationFromVideo', 'Generate animation from video')] }), jsxRuntime.jsx("p", { style: {
89
92
  fontSize: 10,
90
93
  color: 'var(--nice-text-secondary, #888)',
91
94
  margin: '0 0 6px',
92
- }, children: "Upload a video and AI will extract 3D pose data to create an animation clip for the current model." }), jsxRuntime.jsx("div", { className: ModelEditor_module.aiBtnRow, children: jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.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 && jsxRuntime.jsx("div", { className: ModelEditor_module.aiStatus, children: aiStatus })] }) })] })] })), propTab === 'material' && (jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "palette", size: 14 }), " Materials"] }), jsxRuntime.jsx("div", { className: ModelEditor_module.panelContent, children: selectedMaterials.length > 0 ? (selectedMaterials.map((mat, i) => (jsxRuntime.jsx(ModelEditorSubComponents.MaterialCard, { material: mat, active: i === selMaterialIdx, onClick: () => setSelMaterialIdx(i), onUpdate: () => setMatRefresh((n) => n + 1) }, i)))) : (jsxRuntime.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' && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "globe", size: 14 }), " Environment"] }), jsxRuntime.jsx("div", { className: ModelEditor_module.panelContent, children: jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "Background" }), jsxRuntime.jsx(ui.NiceColorPicker, { value: bgColor, onChange: (c) => setBgColor(c) }), jsxRuntime.jsx("span", { style: { fontSize: 9, color: 'var(--nice-text-secondary, #888)' }, children: bgColor })] }) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "cloud", size: 14 }), " Fog"] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelContent, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "Enable" }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: fogEnabled, onChange: () => setFogEnabled((v) => !v) })] }), fogEnabled && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "Color" }), jsxRuntime.jsx(ui.NiceColorPicker, { value: fogColor, onChange: (c) => setFogColor(c) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "Near" }), jsxRuntime.jsx(ui.NiceNumberInput, { className: ModelEditor_module.propInput, value: fogNear, onChange: (val) => setFogNear(val !== null && val !== void 0 ? val : 1), style: { width: 60 } })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "Far" }), jsxRuntime.jsx(ui.NiceNumberInput, { className: ModelEditor_module.propInput, value: fogFar, onChange: (val) => setFogFar(val !== null && val !== void 0 ? val : 100), style: { width: 60 } })] })] }))] })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "lightbulb", size: 14 }), " Scene Lights"] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelContent, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "Show Helpers" }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showLightHelpers, onChange: () => setShowLightHelpers((v) => !v) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "Grid" }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showGrid, onChange: () => setShowGrid((v) => !v) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "Axes" }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showAxes, onChange: () => setShowAxes((v) => !v) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "Skeleton" }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showSkeleton, onChange: () => setShowSkeleton((v) => !v) })] })] })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "grid", size: 14 }), " Snapping"] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelContent, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "Snap" }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: snapEnabled, onChange: () => setSnapEnabled((v) => !v) })] }), snapEnabled && (jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "Grid Size" }), jsxRuntime.jsx(ui.NiceNumberInput, { className: ModelEditor_module.propInput, step: 0.25, min: 0.1, value: snapGrid, onChange: (val) => setSnapGrid(val !== null && val !== void 0 ? val : 1), style: { width: 60 } })] }))] })] })] })), propTab === 'modifiers' && (jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "settings", size: 14 }), " Modifiers"] }), jsxRuntime.jsx("div", { className: ModelEditor_module.panelContent, children: selectedNode && selectedNode.object instanceof THREE__namespace.Mesh ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.btnSecondary, variant: "ghost", size: "sm", style: { width: '100%', marginBottom: 4 }, onClick: () => {
95
+ }, children: "Upload a video and AI will extract 3D pose data to create an animation clip for the current model." }), jsxRuntime.jsx("div", { className: ModelEditor_module.aiBtnRow, children: jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.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 && jsxRuntime.jsx("div", { className: ModelEditor_module.aiStatus, children: aiStatus })] }) })] })] })), propTab === 'material' && (jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "palette", size: 14 }), ' ', t('modelEditorRightPanel.materials', 'Materials')] }), jsxRuntime.jsx("div", { className: ModelEditor_module.panelContent, children: selectedMaterials.length > 0 ? (selectedMaterials.map((mat, i) => (jsxRuntime.jsx(ModelEditorSubComponents.MaterialCard, { material: mat, active: i === selMaterialIdx, onClick: () => setSelMaterialIdx(i), onUpdate: () => setMatRefresh((n) => n + 1) }, i)))) : (jsxRuntime.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' && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "globe", size: 14 }), ' ', t('modelEditorRightPanel.environment', 'Environment')] }), jsxRuntime.jsx("div", { className: ModelEditor_module.panelContent, children: jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.background', 'Background') }), jsxRuntime.jsx(ui.NiceColorPicker, { value: bgColor, onChange: (c) => setBgColor(c) }), jsxRuntime.jsx("span", { style: { fontSize: 9, color: 'var(--nice-text-secondary, #888)' }, children: bgColor })] }) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "cloud", size: 14 }), " Fog"] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelContent, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.enable', 'Enable') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: fogEnabled, onChange: () => setFogEnabled((v) => !v) })] }), fogEnabled && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.color', 'Color') }), jsxRuntime.jsx(ui.NiceColorPicker, { value: fogColor, onChange: (c) => setFogColor(c) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.near', 'Near') }), jsxRuntime.jsx(ui.NiceNumberInput, { className: ModelEditor_module.propInput, value: fogNear, onChange: (val) => setFogNear(val !== null && val !== void 0 ? val : 1), style: { width: 60 } })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "Far" }), jsxRuntime.jsx(ui.NiceNumberInput, { className: ModelEditor_module.propInput, value: fogFar, onChange: (val) => setFogFar(val !== null && val !== void 0 ? val : 100), style: { width: 60 } })] })] }))] })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "lightbulb", size: 14 }), ' ', t('modelEditorRightPanel.sceneLights', 'Scene Lights')] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelContent, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.showHelpers', 'Show Helpers') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showLightHelpers, onChange: () => setShowLightHelpers((v) => !v) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.grid', 'Grid') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showGrid, onChange: () => setShowGrid((v) => !v) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.axes', 'Axes') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showAxes, onChange: () => setShowAxes((v) => !v) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.skeleton', 'Skeleton') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showSkeleton, onChange: () => setShowSkeleton((v) => !v) })] })] })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "grid", size: 14 }), " ", t('modelEditorRightPanel.snapping', 'Snapping')] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelContent, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.snap', 'Snap') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: snapEnabled, onChange: () => setSnapEnabled((v) => !v) })] }), snapEnabled && (jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.gridSize', 'Grid Size') }), jsxRuntime.jsx(ui.NiceNumberInput, { className: ModelEditor_module.propInput, step: 0.25, min: 0.1, value: snapGrid, onChange: (val) => setSnapGrid(val !== null && val !== void 0 ? val : 1), style: { width: 60 } })] }))] })] })] })), propTab === 'modifiers' && (jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "settings", size: 14 }), ' ', t('modelEditorRightPanel.modifiers', 'Modifiers')] }), jsxRuntime.jsx("div", { className: ModelEditor_module.panelContent, children: selectedNode && selectedNode.object instanceof THREE__namespace.Mesh ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.btnSecondary, variant: "ghost", size: "sm", style: { width: '100%', marginBottom: 4 }, onClick: () => {
93
96
  const mesh = selectedNode.object;
94
97
  const geo = mesh.geometry;
95
98
  const pos = geo.attributes.position;
@@ -103,13 +106,13 @@ const ModelEditorRightPanel = React.memo(({ api }) => {
103
106
  const mesh = selectedNode.object;
104
107
  mesh.geometry.computeVertexNormals();
105
108
  setStatusText('Recomputed normals');
106
- }, children: "Recompute Normals" }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.btnSecondary, variant: "ghost", size: "sm", style: { width: '100%', marginBottom: 4 }, onClick: () => {
109
+ }, children: t('modelEditorRightPanel.recomputeNormals', 'Recompute Normals') }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.btnSecondary, variant: "ghost", size: "sm", style: { width: '100%', marginBottom: 4 }, onClick: () => {
107
110
  const mesh = selectedNode.object;
108
111
  const geo = mesh.geometry;
109
112
  geo.center();
110
113
  setStatusText('Centered geometry');
111
114
  setMatRefresh((n) => n + 1);
112
- }, children: "Center Geometry" }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.btnSecondary, variant: "ghost", size: "sm", style: { width: '100%', marginBottom: 4 }, onClick: () => {
115
+ }, children: t('modelEditorRightPanel.centerGeometry', 'Center Geometry') }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.btnSecondary, variant: "ghost", size: "sm", style: { width: '100%', marginBottom: 4 }, onClick: () => {
113
116
  const mesh = selectedNode.object;
114
117
  const geo = mesh.geometry;
115
118
  const idx = geo.index;
@@ -124,8 +127,7 @@ const ModelEditorRightPanel = React.memo(({ api }) => {
124
127
  geo.computeVertexNormals();
125
128
  setStatusText('Flipped normals');
126
129
  }
127
- }, children: "Flip Normals" })] })) : (jsxRuntime.jsx("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10 }, children: "Select a mesh to apply modifiers" })) })] })), propTab === 'physics' && (jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "zap", size: 14 }), " Physics (Info)"] }), jsxRuntime.jsx("div", { className: ModelEditor_module.panelContent, children: jsxRuntime.jsxs("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10, lineHeight: 1.5 }, children: ["Physics simulation preview is not yet available.", jsxRuntime.jsx("br", {}), jsxRuntime.jsx("br", {}), "Planned features:", jsxRuntime.jsx("br", {}), "\u2022 Rigid body simulation", jsxRuntime.jsx("br", {}), "\u2022 Soft body dynamics", jsxRuntime.jsx("br", {}), "\u2022 Cloth simulation", jsxRuntime.jsx("br", {}), "\u2022 Particle systems"] }) })] }))] }));
128
- });
129
- ModelEditorRightPanel.displayName = 'ModelEditorRightPanel';
130
+ }, children: t('modelEditorRightPanel.flipNormals', 'Flip Normals') })] })) : (jsxRuntime.jsx("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10 }, children: t('modelEditorRightPanel.selectAMeshToApply', 'Select a mesh to apply modifiers') })) })] })), propTab === 'physics' && (jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "zap", size: 14 }), " Physics (Info)"] }), jsxRuntime.jsx("div", { className: ModelEditor_module.panelContent, children: jsxRuntime.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.'), jsxRuntime.jsx("br", {}), jsxRuntime.jsx("br", {}), "Planned features:", jsxRuntime.jsx("br", {}), "\u2022 Rigid body simulation", jsxRuntime.jsx("br", {}), "\u2022 Soft body dynamics", jsxRuntime.jsx("br", {}), "\u2022 Cloth simulation", jsxRuntime.jsx("br", {}), "\u2022 Particle systems"] }) })] }))] }));
131
+ }), { displayName: 'ModelEditorRightPanel' });
130
132
 
131
133
  module.exports = ModelEditorRightPanel;
@@ -4,6 +4,7 @@ var jsxRuntime = require('react/jsx-runtime');
4
4
  var ui = require('@nice2dev/ui');
5
5
  var React = require('react');
6
6
  var THREE = require('three');
7
+ var i18n = require('../core/i18n.js');
7
8
  var ModelEditor_module = require('./ModelEditor.module.css.js');
8
9
  var modelEditorTypes = require('./modelEditorTypes.js');
9
10
 
@@ -58,6 +59,7 @@ const Vec3RowDeg = ({ label, value, onChange }) => {
58
59
  /** Material card with editable properties */
59
60
  const MaterialCard = ({ material, active, onClick, onUpdate }) => {
60
61
  var _a, _b, _c, _d;
62
+ const { t } = i18n.useNiceTranslation();
61
63
  const mat = material;
62
64
  const hasColor = 'color' in mat;
63
65
  const hasMetalness = 'metalness' in mat;
@@ -77,21 +79,21 @@ const MaterialCard = ({ material, active, onClick, onUpdate }) => {
77
79
  mat.needsUpdate = true;
78
80
  onUpdate();
79
81
  };
80
- return (jsxRuntime.jsxs("div", { className: `${ModelEditor_module.materialCard} ${active ? ModelEditor_module.materialCardActive : ''}`, onClick: onClick, children: [jsxRuntime.jsx("div", { className: ModelEditor_module.materialHeader, children: jsxRuntime.jsx("span", { className: ModelEditor_module.materialName, children: mat.name || mat.type }) }), hasColor && (jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "Color" }), jsxRuntime.jsx(ui.NiceColorPicker, { value: `#${mat.color.getHexString()}`, onChange: (c) => setColor(c) }), jsxRuntime.jsxs("span", { style: { fontSize: 10, color: 'var(--nice-text-secondary, #888)' }, children: ["#", mat.color.getHexString()] })] })), hasMetalness && (jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "Metal" }), jsxRuntime.jsx(ui.NiceSlider, { min: 0, max: 1, step: 0.01, value: mat.metalness, onChange: (val) => setNum('metalness', val), style: { flex: 1 } }), jsxRuntime.jsx("span", { style: {
82
+ return (jsxRuntime.jsxs("div", { className: `${ModelEditor_module.materialCard} ${active ? ModelEditor_module.materialCardActive : ''}`, onClick: onClick, children: [jsxRuntime.jsx("div", { className: ModelEditor_module.materialHeader, children: jsxRuntime.jsx("span", { className: ModelEditor_module.materialName, children: mat.name || mat.type }) }), hasColor && (jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorSubComponents.color', 'Color') }), jsxRuntime.jsx(ui.NiceColorPicker, { value: `#${mat.color.getHexString()}`, onChange: (c) => setColor(c) }), jsxRuntime.jsxs("span", { style: { fontSize: 10, color: 'var(--nice-text-secondary, #888)' }, children: ["#", mat.color.getHexString()] })] })), hasMetalness && (jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorSubComponents.metal', 'Metal') }), jsxRuntime.jsx(ui.NiceSlider, { min: 0, max: 1, step: 0.01, value: mat.metalness, onChange: (val) => setNum('metalness', val), style: { flex: 1 } }), jsxRuntime.jsx("span", { style: {
81
83
  fontSize: 9,
82
84
  color: 'var(--nice-text-secondary, #888)',
83
85
  width: 28,
84
86
  textAlign: 'right',
85
- }, children: mat.metalness.toFixed(2) })] })), hasRoughness && (jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "Rough" }), jsxRuntime.jsx(ui.NiceSlider, { min: 0, max: 1, step: 0.01, value: mat.roughness, onChange: (val) => setNum('roughness', val), style: { flex: 1 } }), jsxRuntime.jsx("span", { style: {
87
+ }, children: mat.metalness.toFixed(2) })] })), hasRoughness && (jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorSubComponents.rough', 'Rough') }), jsxRuntime.jsx(ui.NiceSlider, { min: 0, max: 1, step: 0.01, value: mat.roughness, onChange: (val) => setNum('roughness', val), style: { flex: 1 } }), jsxRuntime.jsx("span", { style: {
86
88
  fontSize: 9,
87
89
  color: 'var(--nice-text-secondary, #888)',
88
90
  width: 28,
89
91
  textAlign: 'right',
90
- }, children: mat.roughness.toFixed(2) })] })), hasEmissive && mat.emissive && (jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "Emissive" }), jsxRuntime.jsx(ui.NiceColorPicker, { value: `#${mat.emissive.getHexString()}`, onChange: (c) => {
92
+ }, children: mat.roughness.toFixed(2) })] })), hasEmissive && mat.emissive && (jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorSubComponents.emissive', 'Emissive') }), jsxRuntime.jsx(ui.NiceColorPicker, { value: `#${mat.emissive.getHexString()}`, onChange: (c) => {
91
93
  mat.emissive.set(c);
92
94
  mat.needsUpdate = true;
93
95
  onUpdate();
94
- } })] })), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "Opacity" }), jsxRuntime.jsx(ui.NiceSlider, { min: 0, max: 1, step: 0.01, value: mat.opacity, onChange: (val) => {
96
+ } })] })), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorSubComponents.opacity', 'Opacity') }), jsxRuntime.jsx(ui.NiceSlider, { min: 0, max: 1, step: 0.01, value: mat.opacity, onChange: (val) => {
95
97
  mat.opacity = val;
96
98
  mat.transparent = val < 1;
97
99
  mat.needsUpdate = true;
@@ -101,7 +103,7 @@ const MaterialCard = ({ material, active, onClick, onUpdate }) => {
101
103
  color: 'var(--nice-text-secondary, #888)',
102
104
  width: 28,
103
105
  textAlign: 'right',
104
- }, children: mat.opacity.toFixed(2) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "Side" }), jsxRuntime.jsx(ui.NiceSelect, { className: ModelEditor_module.propSelect, value: String(mat.side), onChange: (val) => {
106
+ }, children: mat.opacity.toFixed(2) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorSubComponents.side', 'Side') }), jsxRuntime.jsx(ui.NiceSelect, { className: ModelEditor_module.propSelect, value: String(mat.side), onChange: (val) => {
105
107
  mat.side = parseInt(val);
106
108
  mat.needsUpdate = true;
107
109
  onUpdate();
@@ -109,7 +111,7 @@ const MaterialCard = ({ material, active, onClick, onUpdate }) => {
109
111
  { value: String(THREE__namespace.FrontSide), label: 'Front' },
110
112
  { value: String(THREE__namespace.BackSide), label: 'Back' },
111
113
  { value: String(THREE__namespace.DoubleSide), label: 'Double' },
112
- ] })] }), hasMap && mat.map && (jsxRuntime.jsxs("div", { className: ModelEditor_module.textureSlot, children: [jsxRuntime.jsx("div", { className: ModelEditor_module.textureThumb, children: mat.map.image != null && (jsxRuntime.jsx("img", { src: (_a = mat.map.image.src) !== null && _a !== void 0 ? _a : '', alt: "diffuse" })) }), jsxRuntime.jsxs("div", { className: ModelEditor_module.textureInfo, children: [jsxRuntime.jsx("div", { className: ModelEditor_module.textureName, children: "Diffuse Map" }), jsxRuntime.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 && (jsxRuntime.jsxs("div", { className: ModelEditor_module.textureSlot, children: [jsxRuntime.jsx("div", { className: ModelEditor_module.textureThumb, children: mat.normalMap.image != null && (jsxRuntime.jsx("img", { src: (_d = mat.normalMap.image.src) !== null && _d !== void 0 ? _d : '', alt: "normal" })) }), jsxRuntime.jsx("div", { className: ModelEditor_module.textureInfo, children: jsxRuntime.jsx("div", { className: ModelEditor_module.textureName, children: "Normal Map" }) })] }))] }));
114
+ ] })] }), hasMap && mat.map && (jsxRuntime.jsxs("div", { className: ModelEditor_module.textureSlot, children: [jsxRuntime.jsx("div", { className: ModelEditor_module.textureThumb, children: mat.map.image != null && (jsxRuntime.jsx("img", { src: (_a = mat.map.image.src) !== null && _a !== void 0 ? _a : '', alt: "diffuse" })) }), jsxRuntime.jsxs("div", { className: ModelEditor_module.textureInfo, children: [jsxRuntime.jsx("div", { className: ModelEditor_module.textureName, children: t('modelEditorSubComponents.diffuseMap', 'Diffuse Map') }), jsxRuntime.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 && (jsxRuntime.jsxs("div", { className: ModelEditor_module.textureSlot, children: [jsxRuntime.jsx("div", { className: ModelEditor_module.textureThumb, children: mat.normalMap.image != null && (jsxRuntime.jsx("img", { src: (_d = mat.normalMap.image.src) !== null && _d !== void 0 ? _d : '', alt: "normal" })) }), jsxRuntime.jsx("div", { className: ModelEditor_module.textureInfo, children: jsxRuntime.jsx("div", { className: ModelEditor_module.textureName, children: t('modelEditorSubComponents.normalMap', 'Normal Map') }) })] }))] }));
113
115
  };
114
116
 
115
117
  exports.MaterialCard = MaterialCard;
@@ -3,12 +3,15 @@
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
4
  var ui = require('@nice2dev/ui');
5
5
  var React = require('react');
6
+ var i18n = require('../core/i18n.js');
6
7
  var ModelEditor_module = require('./ModelEditor.module.css.js');
7
8
  var modelEditorTypes = require('./modelEditorTypes.js');
8
9
 
9
- const ModelEditorTimeline = React.memo(({ api }) => {
10
+ const ModelEditorTimeline = /* @__PURE__ */ Object.assign(
11
+ /* @__PURE__ */ React.memo(({ api }) => {
12
+ const { t } = i18n.useNiceTranslation();
10
13
  const { bottomCollapsed, setBottomCollapsed, animations, activeAnimIdx, isPlaying, animTime, animDuration, animSpeed, setAnimSpeed, loopAnim, setLoopAnim, seekAnim, togglePlay, stopAnim, playClip, mergeInputRef, } = api;
11
- return (jsxRuntime.jsxs("div", { className: `${ModelEditor_module.bottomPanel} ${bottomCollapsed ? ModelEditor_module.bottomPanelCollapsed : ''}`, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.bottomPanelHeader, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.bottomToggle, onClick: () => setBottomCollapsed((c) => !c), children: jsxRuntime.jsx(ui.NiceIcon, { name: bottomCollapsed ? 'chevron-right' : 'chevron-down', size: 14 }) }), jsxRuntime.jsxs("span", { className: ModelEditor_module.bottomPanelTitle, onClick: () => setBottomCollapsed((c) => !c), children: ["Animation (", animations.length, " clip", animations.length !== 1 ? 's' : '', ")"] }), jsxRuntime.jsx("div", { className: ModelEditor_module.menuSep }), jsxRuntime.jsxs("div", { className: ModelEditor_module.transportBar, children: [jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.transportBtn, variant: "ghost", size: "sm", onClick: () => seekAnim(0), title: "Go to start", "aria-label": "Go to start", children: jsxRuntime.jsx(ui.NiceIcon, { name: "chevron-left", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.transportBtn, variant: isPlaying ? 'primary' : 'ghost', size: "sm", onClick: togglePlay, title: "Play/Pause (Space)", "aria-label": "Play/Pause (Space)", children: jsxRuntime.jsx(ui.NiceIcon, { name: isPlaying ? 'pause' : 'play', size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.transportBtn, variant: "ghost", size: "sm", onClick: stopAnim, title: "Stop", "aria-label": "Stop", children: jsxRuntime.jsx(ui.NiceIcon, { name: "stop", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.transportBtn, variant: loopAnim ? 'primary' : 'ghost', size: "sm", onClick: () => setLoopAnim((v) => !v), title: "Loop", "aria-label": "Loop", children: jsxRuntime.jsx(ui.NiceIcon, { name: "refresh", size: 16 }) })] }), jsxRuntime.jsxs("span", { className: ModelEditor_module.transportTime, children: [modelEditorTypes.fmtTime(animTime), " / ", modelEditorTypes.fmtTime(animDuration)] }), jsxRuntime.jsx("div", { className: ModelEditor_module.menuSep }), jsxRuntime.jsx("label", { className: ModelEditor_module.menuLabel, children: "Speed" }), jsxRuntime.jsx(ui.NiceSlider, { min: 0.1, max: 3, step: 0.1, value: animSpeed, onChange: (val) => setAnimSpeed(val), style: { width: 60 } }), jsxRuntime.jsxs("span", { className: ModelEditor_module.menuLabel, children: [animSpeed.toFixed(1), "\u00D7"] })] }), !bottomCollapsed && (jsxRuntime.jsxs("div", { className: ModelEditor_module.animListArea, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.animList, children: [animations.length === 0 && (jsxRuntime.jsx("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10, padding: 8 }, children: "No animations loaded" })), animations.map((anim, i) => (jsxRuntime.jsxs("div", { className: `${ModelEditor_module.animItem} ${i === activeAnimIdx ? ModelEditor_module.animItemActive : ''}`, onClick: () => playClip(i), children: [jsxRuntime.jsx("span", { children: anim.name }), jsxRuntime.jsxs("span", { className: ModelEditor_module.animDuration, children: [anim.duration.toFixed(1), "s"] })] }, `${anim.name}-${i}`))), jsxRuntime.jsx("div", { style: { padding: '4px 8px' }, children: jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.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" }) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.timelineArea, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.timelineRuler, children: [animDuration > 0 &&
14
+ return (jsxRuntime.jsxs("div", { className: `${ModelEditor_module.bottomPanel} ${bottomCollapsed ? ModelEditor_module.bottomPanelCollapsed : ''}`, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.bottomPanelHeader, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.bottomToggle, onClick: () => setBottomCollapsed((c) => !c), children: jsxRuntime.jsx(ui.NiceIcon, { name: bottomCollapsed ? 'chevron-right' : 'chevron-down', size: 14 }) }), jsxRuntime.jsxs("span", { className: ModelEditor_module.bottomPanelTitle, onClick: () => setBottomCollapsed((c) => !c), children: ["Animation (", animations.length, " clip", animations.length !== 1 ? 's' : '', ")"] }), jsxRuntime.jsx("div", { className: ModelEditor_module.menuSep }), jsxRuntime.jsxs("div", { className: ModelEditor_module.transportBar, children: [jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.transportBtn, variant: "ghost", size: "sm", onClick: () => seekAnim(0), title: t('modelEditorTimeline.goToStart', 'Go to start'), "aria-label": t('modelEditorTimeline.goToStart', 'Go to start'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "chevron-left", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.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: jsxRuntime.jsx(ui.NiceIcon, { name: isPlaying ? 'pause' : 'play', size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.transportBtn, variant: "ghost", size: "sm", onClick: stopAnim, title: t('modelEditorTimeline.stop', 'Stop'), "aria-label": t('modelEditorTimeline.stop', 'Stop'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "stop", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.transportBtn, variant: loopAnim ? 'primary' : 'ghost', size: "sm", onClick: () => setLoopAnim((v) => !v), title: t('modelEditorTimeline.loop', 'Loop'), "aria-label": t('modelEditorTimeline.loop', 'Loop'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "refresh", size: 16 }) })] }), jsxRuntime.jsxs("span", { className: ModelEditor_module.transportTime, children: [modelEditorTypes.fmtTime(animTime), " / ", modelEditorTypes.fmtTime(animDuration)] }), jsxRuntime.jsx("div", { className: ModelEditor_module.menuSep }), jsxRuntime.jsx("label", { className: ModelEditor_module.menuLabel, children: t('modelEditorTimeline.speed', 'Speed') }), jsxRuntime.jsx(ui.NiceSlider, { min: 0.1, max: 3, step: 0.1, value: animSpeed, onChange: (val) => setAnimSpeed(val), style: { width: 60 } }), jsxRuntime.jsxs("span", { className: ModelEditor_module.menuLabel, children: [animSpeed.toFixed(1), "\u00D7"] })] }), !bottomCollapsed && (jsxRuntime.jsxs("div", { className: ModelEditor_module.animListArea, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.animList, children: [animations.length === 0 && (jsxRuntime.jsx("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10, padding: 8 }, children: t('modelEditorTimeline.noAnimationsLoaded', 'No animations loaded') })), animations.map((anim, i) => (jsxRuntime.jsxs("div", { className: `${ModelEditor_module.animItem} ${i === activeAnimIdx ? ModelEditor_module.animItemActive : ''}`, onClick: () => playClip(i), children: [jsxRuntime.jsx("span", { children: anim.name }), jsxRuntime.jsxs("span", { className: ModelEditor_module.animDuration, children: [anim.duration.toFixed(1), "s"] })] }, `${anim.name}-${i}`))), jsxRuntime.jsx("div", { style: { padding: '4px 8px' }, children: jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.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" }) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.timelineArea, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.timelineRuler, children: [animDuration > 0 &&
12
15
  Array.from({ length: Math.ceil(animDuration) + 1 }, (_, i) => {
13
16
  const pct = (i / animDuration) * 100;
14
17
  return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx("div", { className: ModelEditor_module.rulerTick, style: { left: `${pct}%` } }), jsxRuntime.jsxs("span", { className: ModelEditor_module.rulerLabel, style: { left: `${pct}%` }, children: [i, "s"] })] }, i));
@@ -26,7 +29,6 @@ const ModelEditorTimeline = React.memo(({ api }) => {
26
29
  left: `${(animTime / animDuration) * 100}%`,
27
30
  height: '100%',
28
31
  } })) })] })] }))] }));
29
- });
30
- ModelEditorTimeline.displayName = 'ModelEditorTimeline';
32
+ }), { displayName: 'ModelEditorTimeline' });
31
33
 
32
34
  module.exports = ModelEditorTimeline;
@@ -3,12 +3,14 @@
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
4
  var ui = require('@nice2dev/ui');
5
5
  var React = require('react');
6
+ var i18n = require('../core/i18n.js');
6
7
  var ModelEditor_module = require('./ModelEditor.module.css.js');
7
8
 
8
- const ModelEditorToolbar = React.memo(({ api }) => {
9
+ const ModelEditorToolbar = /* @__PURE__ */ Object.assign(
10
+ /* @__PURE__ */ React.memo(({ api }) => {
11
+ const { t } = i18n.useNiceTranslation();
9
12
  const { transformMode, setTransformMode, snapEnabled, setSnapEnabled, gizmoSpace, setGizmoSpace, rootObjectRef, focusOnObject, setCameraPreset, deleteSelected, duplicateSelected, selectedNode, } = api;
10
- return (jsxRuntime.jsxs("div", { className: ModelEditor_module.toolbar, children: [jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: transformMode === 'translate' ? 'primary' : 'ghost', size: "sm", onClick: () => setTransformMode('translate'), title: "Translate (G)", "aria-label": "Translate (G)", children: jsxRuntime.jsx(ui.NiceIcon, { name: "move", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: transformMode === 'rotate' ? 'primary' : 'ghost', size: "sm", onClick: () => setTransformMode('rotate'), title: "Rotate (R)", "aria-label": "Rotate (R)", children: jsxRuntime.jsx(ui.NiceIcon, { name: "refresh", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: transformMode === 'scale' ? 'primary' : 'ghost', size: "sm", onClick: () => setTransformMode('scale'), title: "Scale (S)", "aria-label": "Scale (S)", children: jsxRuntime.jsx(ui.NiceIcon, { name: "hexagon", size: 16 }) }), jsxRuntime.jsx("div", { className: ModelEditor_module.toolSep }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: snapEnabled ? 'primary' : 'ghost', size: "sm", onClick: () => setSnapEnabled((v) => !v), title: `Snap to Grid (${snapEnabled ? 'ON' : 'OFF'})`, "aria-label": "Snap to Grid", children: jsxRuntime.jsx(ui.NiceIcon, { name: "grid", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: () => setGizmoSpace((s) => (s === 'local' ? 'world' : 'local')), title: `Orientation: ${gizmoSpace}`, "aria-label": `Orientation: ${gizmoSpace}`, children: jsxRuntime.jsx(ui.NiceIcon, { name: gizmoSpace === 'local' ? 'target' : 'globe', size: 16 }) }), jsxRuntime.jsx("div", { className: ModelEditor_module.toolSep }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: () => rootObjectRef.current && focusOnObject(rootObjectRef.current), title: "Focus (F)", "aria-label": "Focus (F)", children: jsxRuntime.jsx(ui.NiceIcon, { name: "target", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: () => setCameraPreset('front'), title: "Front view (Numpad 1)", children: "1" }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: () => setCameraPreset('right'), title: "Right view (Numpad 3)", children: "3" }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: () => setCameraPreset('top'), title: "Top view (Numpad 7)", children: "7" }), jsxRuntime.jsx("div", { className: ModelEditor_module.toolSep }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: deleteSelected, title: "Delete selected (Del)", "aria-label": "Delete selected (Del)", children: jsxRuntime.jsx(ui.NiceIcon, { name: "trash", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: duplicateSelected, title: "Duplicate (Shift+D)", disabled: !selectedNode, "aria-label": "Duplicate (Shift+D)", children: jsxRuntime.jsx(ui.NiceIcon, { name: "clipboard", size: 16 }) })] }));
11
- });
12
- ModelEditorToolbar.displayName = 'ModelEditorToolbar';
13
+ return (jsxRuntime.jsxs("div", { className: ModelEditor_module.toolbar, children: [jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.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: jsxRuntime.jsx(ui.NiceIcon, { name: "move", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.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: jsxRuntime.jsx(ui.NiceIcon, { name: "refresh", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.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: jsxRuntime.jsx(ui.NiceIcon, { name: "hexagon", size: 16 }) }), jsxRuntime.jsx("div", { className: ModelEditor_module.toolSep }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.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: jsxRuntime.jsx(ui.NiceIcon, { name: "grid", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: () => setGizmoSpace((s) => (s === 'local' ? 'world' : 'local')), title: `Orientation: ${gizmoSpace}`, "aria-label": `Orientation: ${gizmoSpace}`, children: jsxRuntime.jsx(ui.NiceIcon, { name: gizmoSpace === 'local' ? 'target' : 'globe', size: 16 }) }), jsxRuntime.jsx("div", { className: ModelEditor_module.toolSep }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.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: jsxRuntime.jsx(ui.NiceIcon, { name: "target", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: () => setCameraPreset('front'), title: t('modelEditorToolbar.frontViewNumpad1', 'Front view (Numpad 1)'), children: "1" }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: () => setCameraPreset('right'), title: t('modelEditorToolbar.rightViewNumpad3', 'Right view (Numpad 3)'), children: "3" }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: () => setCameraPreset('top'), title: t('modelEditorToolbar.topViewNumpad7', 'Top view (Numpad 7)'), children: "7" }), jsxRuntime.jsx("div", { className: ModelEditor_module.toolSep }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: deleteSelected, title: t('modelEditorToolbar.deleteSelectedDel', 'Delete selected (Del)'), "aria-label": t('modelEditorToolbar.deleteSelectedDel', 'Delete selected (Del)'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "trash", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.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: jsxRuntime.jsx(ui.NiceIcon, { name: "clipboard", size: 16 }) })] }));
14
+ }), { displayName: 'ModelEditorToolbar' });
13
15
 
14
16
  module.exports = ModelEditorToolbar;
@@ -3,11 +3,22 @@
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
4
  var ui = require('@nice2dev/ui');
5
5
  var React = require('react');
6
+ var i18n = require('../core/i18n.js');
6
7
  var ModelEditor_module = require('./ModelEditor.module.css.js');
7
8
 
8
- const ModelEditorViewport = React.memo(({ api }) => {
9
+ /** The viewport badge per editor mode: [key, English]. */
10
+ const MODE_BADGE = {
11
+ object: ['modelEditorViewport.objectMode', 'OBJECT MODE'],
12
+ edit: ['modelEditorViewport.editMode', 'EDIT MODE'],
13
+ pose: ['modelEditorViewport.poseMode', 'POSE MODE'],
14
+ cad: ['modelEditorViewport.cadMode', 'CAD MODE'],
15
+ sims: ['modelEditorViewport.simsMode', 'SIMS MODE'],
16
+ };
17
+ const ModelEditorViewport = /* @__PURE__ */ Object.assign(
18
+ /* @__PURE__ */ React.memo(({ api }) => {
19
+ const { t } = i18n.useNiceTranslation();
9
20
  const { mountRef, dragOver, handleDragOver, handleDragLeave, handleDrop, handleViewportClick, handleContextMenu, editorMode, polyCount, meshCount, boneCount, shadingMode, setShadingMode, gizmoSpace, setGizmoSpace, contextMenu, closeContextMenu, addPrimitive, addSceneLight, duplicateSelected, deleteSelected, selectedNode, rootObjectRef, focusOnObject, snapEnabled, setSnapEnabled, } = api;
10
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { ref: mountRef, className: `${ModelEditor_module.viewportContainer} ${dragOver ? ModelEditor_module.dragOver : ''}`, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, onClick: handleViewportClick, onContextMenu: handleContextMenu, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.viewportOverlay, children: [jsxRuntime.jsxs("span", { className: ModelEditor_module.viewportBadge, children: [editorMode.toUpperCase(), " MODE"] }), jsxRuntime.jsxs("span", { className: ModelEditor_module.viewportBadge, children: [polyCount.toLocaleString(), " tris \u00B7 ", meshCount, " meshes \u00B7 ", boneCount, " bones"] })] }), jsxRuntime.jsx("div", { className: ModelEditor_module.viewportShadingBar, children: ['wireframe', 'solid', 'material', 'rendered'].map((m) => (jsxRuntime.jsx("button", { className: `${ModelEditor_module.shadingBtn} ${shadingMode === m ? ModelEditor_module.shadingBtnActive : ''}`, onClick: (e) => {
21
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { ref: mountRef, className: `${ModelEditor_module.viewportContainer} ${dragOver ? ModelEditor_module.dragOver : ''}`, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, onClick: handleViewportClick, onContextMenu: handleContextMenu, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.viewportOverlay, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.viewportBadge, children: t(...MODE_BADGE[editorMode]) }), jsxRuntime.jsxs("span", { className: ModelEditor_module.viewportBadge, children: [polyCount.toLocaleString(), " tris \u00B7 ", meshCount, " meshes \u00B7 ", boneCount, " bones"] })] }), jsxRuntime.jsx("div", { className: ModelEditor_module.viewportShadingBar, children: ['wireframe', 'solid', 'material', 'rendered'].map((m) => (jsxRuntime.jsx("button", { className: `${ModelEditor_module.shadingBtn} ${shadingMode === m ? ModelEditor_module.shadingBtnActive : ''}`, onClick: (e) => {
11
22
  e.stopPropagation();
12
23
  setShadingMode(m);
13
24
  }, title: m.charAt(0).toUpperCase() + m.slice(1), "aria-label": m.charAt(0).toUpperCase() + m.slice(1), children: jsxRuntime.jsx(ui.NiceIcon, { name: m === 'wireframe'
@@ -19,12 +30,11 @@ const ModelEditorViewport = React.memo(({ api }) => {
19
30
  : 'lightbulb', size: 16 }) }, m))) }), jsxRuntime.jsx("div", { className: ModelEditor_module.viewportGizmoInfo, children: jsxRuntime.jsx("button", { className: ModelEditor_module.shadingBtn, onClick: (e) => {
20
31
  e.stopPropagation();
21
32
  setGizmoSpace((s) => (s === 'local' ? 'world' : 'local'));
22
- }, title: `Orientation: ${gizmoSpace}`, "aria-label": `Orientation: ${gizmoSpace}`, children: gizmoSpace === 'local' ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "target", size: 14 }), " Local"] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "globe", size: 14 }), " World"] })) }) }), dragOver && (jsxRuntime.jsx("div", { className: ModelEditor_module.viewportDropZone, children: jsxRuntime.jsx("span", { children: "Drop 3D files here" }) }))] }), contextMenu && (jsxRuntime.jsxs("div", { className: ModelEditor_module.contextMenu, style: { top: contextMenu.y, left: contextMenu.x }, onClick: closeContextMenu, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.contextItem, onClick: () => addPrimitive('cube'), children: [jsxRuntime.jsx("span", { children: "Add Cube" }), jsxRuntime.jsx("span", { className: ModelEditor_module.contextKey, children: "Shift+A" })] }), jsxRuntime.jsx("div", { className: ModelEditor_module.contextItem, onClick: () => addPrimitive('sphere'), children: jsxRuntime.jsx("span", { children: "Add Sphere" }) }), jsxRuntime.jsx("div", { className: ModelEditor_module.contextItem, onClick: () => addSceneLight('point'), children: jsxRuntime.jsx("span", { children: "Add Point Light" }) }), jsxRuntime.jsx("div", { className: ModelEditor_module.contextSep }), jsxRuntime.jsxs("div", { className: ModelEditor_module.contextItem, onClick: duplicateSelected, style: !selectedNode ? { opacity: 0.4, pointerEvents: 'none' } : {}, children: [jsxRuntime.jsx("span", { children: "Duplicate" }), jsxRuntime.jsx("span", { className: ModelEditor_module.contextKey, children: "Shift+D" })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.contextItem, onClick: deleteSelected, style: !selectedNode ? { opacity: 0.4, pointerEvents: 'none' } : {}, children: [jsxRuntime.jsx("span", { children: "Delete" }), jsxRuntime.jsx("span", { className: ModelEditor_module.contextKey, children: "Del" })] }), jsxRuntime.jsx("div", { className: ModelEditor_module.contextSep }), jsxRuntime.jsxs("div", { className: ModelEditor_module.contextItem, onClick: () => {
33
+ }, title: `Orientation: ${gizmoSpace}`, "aria-label": `Orientation: ${gizmoSpace}`, children: gizmoSpace === 'local' ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "target", size: 14 }), " ", t('modelEditorViewport.local', 'Local')] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "globe", size: 14 }), " ", t('modelEditorViewport.world', 'World')] })) }) }), dragOver && (jsxRuntime.jsx("div", { className: ModelEditor_module.viewportDropZone, children: jsxRuntime.jsx("span", { children: t('modelEditorViewport.drop3dFilesHere', 'Drop 3D files here') }) }))] }), contextMenu && (jsxRuntime.jsxs("div", { className: ModelEditor_module.contextMenu, style: { top: contextMenu.y, left: contextMenu.x }, onClick: closeContextMenu, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.contextItem, onClick: () => addPrimitive('cube'), children: [jsxRuntime.jsx("span", { children: t('modelEditorViewport.addCube', 'Add Cube') }), jsxRuntime.jsx("span", { className: ModelEditor_module.contextKey, children: "Shift+A" })] }), jsxRuntime.jsx("div", { className: ModelEditor_module.contextItem, onClick: () => addPrimitive('sphere'), children: jsxRuntime.jsx("span", { children: t('modelEditorViewport.addSphere', 'Add Sphere') }) }), jsxRuntime.jsx("div", { className: ModelEditor_module.contextItem, onClick: () => addSceneLight('point'), children: jsxRuntime.jsx("span", { children: t('modelEditorViewport.addPointLight', 'Add Point Light') }) }), jsxRuntime.jsx("div", { className: ModelEditor_module.contextSep }), jsxRuntime.jsxs("div", { className: ModelEditor_module.contextItem, onClick: duplicateSelected, style: !selectedNode ? { opacity: 0.4, pointerEvents: 'none' } : {}, children: [jsxRuntime.jsx("span", { children: t('modelEditorViewport.duplicate', 'Duplicate') }), jsxRuntime.jsx("span", { className: ModelEditor_module.contextKey, children: "Shift+D" })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.contextItem, onClick: deleteSelected, style: !selectedNode ? { opacity: 0.4, pointerEvents: 'none' } : {}, children: [jsxRuntime.jsx("span", { children: t('modelEditorViewport.delete', 'Delete') }), jsxRuntime.jsx("span", { className: ModelEditor_module.contextKey, children: "Del" })] }), jsxRuntime.jsx("div", { className: ModelEditor_module.contextSep }), jsxRuntime.jsxs("div", { className: ModelEditor_module.contextItem, onClick: () => {
23
34
  if (rootObjectRef.current) {
24
35
  focusOnObject(rootObjectRef.current);
25
36
  }
26
- }, children: [jsxRuntime.jsx("span", { children: "Focus Camera" }), jsxRuntime.jsx("span", { className: ModelEditor_module.contextKey, children: "F" })] }), jsxRuntime.jsx("div", { className: ModelEditor_module.contextItem, onClick: () => setSnapEnabled((v) => !v), children: jsxRuntime.jsxs("span", { children: [snapEnabled && jsxRuntime.jsx(ui.NiceIcon, { name: "check", size: 14 }), " Snap to Grid"] }) })] }))] }));
27
- });
28
- ModelEditorViewport.displayName = 'ModelEditorViewport';
37
+ }, children: [jsxRuntime.jsx("span", { children: t('modelEditorViewport.focusCamera', 'Focus Camera') }), jsxRuntime.jsx("span", { className: ModelEditor_module.contextKey, children: "F" })] }), jsxRuntime.jsx("div", { className: ModelEditor_module.contextItem, onClick: () => setSnapEnabled((v) => !v), children: jsxRuntime.jsxs("span", { children: [snapEnabled && jsxRuntime.jsx(ui.NiceIcon, { name: "check", size: 14 }), ' ', t('modelEditorViewport.snapToGrid', 'Snap to Grid')] }) })] }))] }));
38
+ }), { displayName: 'ModelEditorViewport' });
29
39
 
30
40
  module.exports = ModelEditorViewport;
@@ -3,6 +3,7 @@
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
4
  var ui = require('@nice2dev/ui');
5
5
  var React = require('react');
6
+ var i18n = require('../core/i18n.js');
6
7
  var ModelViewer_module = require('./ModelViewer.module.css.js');
7
8
  var useModelViewer = require('./useModelViewer.js');
8
9
 
@@ -14,6 +15,7 @@ const NAV_META = {
14
15
  };
15
16
  const NavLabel = ({ mode }) => (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: NAV_META[mode].icon, size: 14 }), " ", NAV_META[mode].label] }));
16
17
  const NiceModelViewer = ({ className, showToolbar = true, showStatusBar = true, showInfoPanel = true, toolbarExtra, onFileDrop, children, ...viewerProps }) => {
18
+ const { t } = i18n.useNiceTranslation();
17
19
  const api = useModelViewer.useModelViewer(viewerProps);
18
20
  const [panelOpen, setPanelOpen] = React.useState(false);
19
21
  const [dragOver, setDragOver] = React.useState(false);
@@ -43,24 +45,24 @@ const NiceModelViewer = ({ className, showToolbar = true, showStatusBar = true,
43
45
  const idx = navModes.indexOf(api.navigationMode);
44
46
  api.setNavigationMode(navModes[(idx + 1) % navModes.length]);
45
47
  }, [api]);
46
- return (jsxRuntime.jsxs("div", { className: `${ModelViewer_module.root} ${className !== null && className !== void 0 ? className : ''}`, children: [showToolbar && (jsxRuntime.jsxs("div", { className: ModelViewer_module.toolbar, children: [jsxRuntime.jsxs(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: "ghost", size: "sm", title: "Open file\u2026", onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "folder", size: 14 }), " Open"] }), jsxRuntime.jsx("input", { ref: fileInputRef, type: "file", accept: ".fbx,.glb,.gltf,.obj,.dae,.stl,.ply", style: { display: 'none' }, onChange: (e) => {
48
+ return (jsxRuntime.jsxs("div", { className: `${ModelViewer_module.root} ${className !== null && className !== void 0 ? className : ''}`, children: [showToolbar && (jsxRuntime.jsxs("div", { className: ModelViewer_module.toolbar, children: [jsxRuntime.jsxs(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: "ghost", size: "sm", title: t('modelViewer.openFile', 'Open file…'), onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "folder", size: 14 }), " ", t('modelViewer.open', 'Open')] }), jsxRuntime.jsx("input", { ref: fileInputRef, type: "file", accept: ".fbx,.glb,.gltf,.obj,.dae,.stl,.ply", style: { display: 'none' }, onChange: (e) => {
47
49
  var _a;
48
50
  const f = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
49
51
  if (f) {
50
52
  api.loadFile(f);
51
53
  }
52
54
  e.target.value = '';
53
- } }), jsxRuntime.jsx("div", { className: ModelViewer_module.toolSep }), jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: "ghost", size: "sm", title: "Navigation mode", onClick: cycleNav, children: jsxRuntime.jsx(NavLabel, { mode: api.navigationMode }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: api.wireframe ? 'primary' : 'ghost', size: "sm", onClick: () => api.setWireframe(!api.wireframe), title: "Wireframe", "aria-label": "Wireframe", children: jsxRuntime.jsx(ui.NiceIcon, { name: "square", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: api.showGrid ? 'primary' : 'ghost', size: "sm", onClick: () => api.setShowGrid(!api.showGrid), title: "Grid", "aria-label": "Grid", children: jsxRuntime.jsx(ui.NiceIcon, { name: "grid", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: api.clippingEnabled ? 'primary' : 'ghost', size: "sm", onClick: () => api.setClippingEnabled(!api.clippingEnabled), title: "Clipping plane", "aria-label": "Clipping plane", children: jsxRuntime.jsx(ui.NiceIcon, { name: "scissors", size: 16 }) }), api.clippingEnabled && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceSelect, { className: ModelViewer_module.animSelect, value: api.clippingAxis, onChange: (val) => api.setClippingAxis(val), options: [
55
+ } }), jsxRuntime.jsx("div", { className: ModelViewer_module.toolSep }), jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: "ghost", size: "sm", title: t('modelViewer.navigationMode', 'Navigation mode'), onClick: cycleNav, children: jsxRuntime.jsx(NavLabel, { mode: api.navigationMode }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: api.wireframe ? 'primary' : 'ghost', size: "sm", onClick: () => api.setWireframe(!api.wireframe), title: t('modelViewer.wireframe', 'Wireframe'), "aria-label": t('modelViewer.wireframe', 'Wireframe'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "square", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: api.showGrid ? 'primary' : 'ghost', size: "sm", onClick: () => api.setShowGrid(!api.showGrid), title: t('modelViewer.grid', 'Grid'), "aria-label": t('modelViewer.grid', 'Grid'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "grid", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: api.clippingEnabled ? 'primary' : 'ghost', size: "sm", onClick: () => api.setClippingEnabled(!api.clippingEnabled), title: t('modelViewer.clippingPlane', 'Clipping plane'), "aria-label": t('modelViewer.clippingPlane', 'Clipping plane'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "scissors", size: 16 }) }), api.clippingEnabled && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceSelect, { className: ModelViewer_module.animSelect, value: api.clippingAxis, onChange: (val) => api.setClippingAxis(val), options: [
54
56
  { value: 'x', label: 'X' },
55
57
  { value: 'y', label: 'Y' },
56
58
  { value: 'z', label: 'Z' },
57
- ] }), jsxRuntime.jsx(ui.NiceSlider, { min: -10, max: 10, step: 0.1, value: api.clippingPosition, onChange: (val) => api.setClippingPosition(val), style: { width: 60 } })] })), jsxRuntime.jsx("div", { className: ModelViewer_module.toolSep }), jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: "ghost", size: "sm", onClick: api.zoomToFit, title: "Zoom to fit", "aria-label": "Zoom to fit", children: jsxRuntime.jsx(ui.NiceIcon, { name: "search", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: "ghost", size: "sm", onClick: api.resetCamera, title: "Reset camera", "aria-label": "Reset camera", children: jsxRuntime.jsx(ui.NiceIcon, { name: "home", size: 16 }) }), jsxRuntime.jsx("div", { className: ModelViewer_module.toolSep }), jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: "ghost", size: "sm", onClick: () => {
59
+ ] }), jsxRuntime.jsx(ui.NiceSlider, { min: -10, max: 10, step: 0.1, value: api.clippingPosition, onChange: (val) => api.setClippingPosition(val), style: { width: 60 } })] })), jsxRuntime.jsx("div", { className: ModelViewer_module.toolSep }), jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: "ghost", size: "sm", onClick: api.zoomToFit, title: t('modelViewer.zoomToFit', 'Zoom to fit'), "aria-label": t('modelViewer.zoomToFit', 'Zoom to fit'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "search", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: "ghost", size: "sm", onClick: api.resetCamera, title: t('modelViewer.resetCamera', 'Reset camera'), "aria-label": t('modelViewer.resetCamera', 'Reset camera'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "home", size: 16 }) }), jsxRuntime.jsx("div", { className: ModelViewer_module.toolSep }), jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: "ghost", size: "sm", onClick: () => {
58
60
  const url = api.screenshot();
59
61
  const a = document.createElement('a');
60
62
  a.href = url;
61
63
  a.download = 'screenshot.png';
62
64
  a.click();
63
- }, title: "Screenshot", "aria-label": "Screenshot", children: jsxRuntime.jsx(ui.NiceIcon, { name: "camera", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: "ghost", size: "sm", onClick: api.toggleFullscreen, title: "Fullscreen", "aria-label": "Fullscreen", children: jsxRuntime.jsx(ui.NiceIcon, { name: api.isFullscreen ? 'collapse' : 'expand', size: 16 }) }), toolbarExtra, showInfoPanel && (jsxRuntime.jsx("div", { className: ModelViewer_module.toolRight, children: jsxRuntime.jsx(ui.NiceButton, { className: `${ModelViewer_module.toolBtn} ${panelOpen ? ModelViewer_module.active : ''}`, variant: panelOpen ? 'primary' : 'ghost', size: "sm", onClick: () => setPanelOpen(!panelOpen), title: "Info panel", "aria-label": "Info panel", children: jsxRuntime.jsx(ui.NiceIcon, { name: "info", size: 16 }) }) }))] })), jsxRuntime.jsxs("div", { className: ModelViewer_module.viewport, ref: api.mountRef, tabIndex: 0, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, children: [api.loading && (jsxRuntime.jsxs("div", { className: ModelViewer_module.loadingOverlay, children: [jsxRuntime.jsx("span", { className: ModelViewer_module.loadingText, children: "Loading model\u2026" }), jsxRuntime.jsx("div", { className: ModelViewer_module.loadingBar, children: jsxRuntime.jsx("div", { className: ModelViewer_module.loadingBarFill, style: { width: `${(api.loadProgress * 100).toFixed(0)}%` } }) })] })), api.error && (jsxRuntime.jsxs("div", { className: ModelViewer_module.errorOverlay, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "warning", size: 28 }), jsxRuntime.jsx("span", { style: { marginTop: 8 }, children: api.error })] })), dragOver && jsxRuntime.jsx("div", { className: ModelViewer_module.dropZone, children: "Drop 3D model file here" })] }), jsxRuntime.jsxs("div", { className: `${ModelViewer_module.sidePanel} ${!panelOpen ? ModelViewer_module.hidden : ''}`, children: [jsxRuntime.jsxs("div", { className: ModelViewer_module.panelSection, children: [jsxRuntime.jsx("div", { className: ModelViewer_module.panelTitle, children: "Model Info" }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: "Triangles" }), jsxRuntime.jsx("span", { children: api.polyCount.toLocaleString() })] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: "Meshes" }), jsxRuntime.jsx("span", { children: api.meshCount })] }), api.dimensions && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: "Width" }), jsxRuntime.jsx("span", { children: api.dimensions.width })] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: "Height" }), jsxRuntime.jsx("span", { children: api.dimensions.height })] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: "Depth" }), jsxRuntime.jsx("span", { children: api.dimensions.depth })] })] }))] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelSection, children: [jsxRuntime.jsx("div", { className: ModelViewer_module.panelTitle, children: "Navigation" }), navModes.map((m) => (jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: api.navigationMode === m ? 'primary' : 'ghost', size: "sm", onClick: () => api.setNavigationMode(m), children: jsxRuntime.jsx(NavLabel, { mode: m }) }, m)))] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelSection, children: [jsxRuntime.jsx("div", { className: ModelViewer_module.panelTitle, children: "Auto Rotate" }), jsxRuntime.jsx(ui.NiceSlider, { min: 0, max: 10, step: 0.5, value: api.autoRotateSpeed, onChange: (val) => api.setAutoRotateSpeed(val), style: { width: '100%' } })] }), api.animations.length > 0 && (jsxRuntime.jsxs("div", { className: ModelViewer_module.panelSection, children: [jsxRuntime.jsx("div", { className: ModelViewer_module.panelTitle, children: "Animations" }), api.animations.map((name, i) => (jsxRuntime.jsxs(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: api.activeAnimIdx === i ? 'primary' : 'ghost', size: "sm", onClick: () => api.playAnimation(i), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "play", size: 14 }), " ", name] }, i)))] }))] }), jsxRuntime.jsxs("div", { className: `${ModelViewer_module.animBar} ${api.animations.length === 0 ? ModelViewer_module.hidden : ''}`, children: [jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: "ghost", size: "sm", onClick: api.togglePlay, "aria-label": "Play/Pause", children: jsxRuntime.jsx(ui.NiceIcon, { name: api.isPlaying ? 'pause' : 'play', size: 16 }) }), jsxRuntime.jsx(ui.NiceSelect, { className: ModelViewer_module.animSelect, value: String(api.activeAnimIdx), onChange: (val) => api.playAnimation(parseInt(val)), options: api.animations.map((n, i) => ({ value: String(i), label: n })) }), jsxRuntime.jsx(ui.NiceSlider, { className: ModelViewer_module.animSlider, min: 0, max: api.animDuration || 1, step: 0.01, value: api.animTime, onChange: (val) => api.seekAnim(val) }), jsxRuntime.jsxs("span", { className: ModelViewer_module.toolLabel, children: [api.animTime.toFixed(1), "s / ", api.animDuration.toFixed(1), "s"] })] }), showStatusBar && (jsxRuntime.jsxs("div", { className: ModelViewer_module.statusBar, children: [jsxRuntime.jsx("span", { children: api.statusText }), jsxRuntime.jsxs("div", { className: ModelViewer_module.statusRight, children: [jsxRuntime.jsx("span", { children: jsxRuntime.jsx(NavLabel, { mode: api.navigationMode }) }), api.dimensions && (jsxRuntime.jsxs("span", { children: [api.dimensions.width, "\u00D7", api.dimensions.height, "\u00D7", api.dimensions.depth] }))] })] })), children === null || children === void 0 ? void 0 : children(api)] }));
65
+ }, title: t('modelViewer.screenshot', 'Screenshot'), "aria-label": t('modelViewer.screenshot', 'Screenshot'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "camera", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: "ghost", size: "sm", onClick: api.toggleFullscreen, title: t('modelViewer.fullscreen', 'Fullscreen'), "aria-label": t('modelViewer.fullscreen', 'Fullscreen'), children: jsxRuntime.jsx(ui.NiceIcon, { name: api.isFullscreen ? 'collapse' : 'expand', size: 16 }) }), toolbarExtra, showInfoPanel && (jsxRuntime.jsx("div", { className: ModelViewer_module.toolRight, children: jsxRuntime.jsx(ui.NiceButton, { className: `${ModelViewer_module.toolBtn} ${panelOpen ? ModelViewer_module.active : ''}`, variant: panelOpen ? 'primary' : 'ghost', size: "sm", onClick: () => setPanelOpen(!panelOpen), title: t('modelViewer.infoPanel', 'Info panel'), "aria-label": t('modelViewer.infoPanel', 'Info panel'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "info", size: 16 }) }) }))] })), jsxRuntime.jsxs("div", { className: ModelViewer_module.viewport, ref: api.mountRef, tabIndex: 0, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, children: [api.loading && (jsxRuntime.jsxs("div", { className: ModelViewer_module.loadingOverlay, children: [jsxRuntime.jsx("span", { className: ModelViewer_module.loadingText, children: "Loading model\u2026" }), jsxRuntime.jsx("div", { className: ModelViewer_module.loadingBar, children: jsxRuntime.jsx("div", { className: ModelViewer_module.loadingBarFill, style: { width: `${(api.loadProgress * 100).toFixed(0)}%` } }) })] })), api.error && (jsxRuntime.jsxs("div", { className: ModelViewer_module.errorOverlay, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "warning", size: 28 }), jsxRuntime.jsx("span", { style: { marginTop: 8 }, children: api.error })] })), dragOver && (jsxRuntime.jsx("div", { className: ModelViewer_module.dropZone, children: t('modelViewer.drop3dModelFileHere', 'Drop 3D model file here') }))] }), jsxRuntime.jsxs("div", { className: `${ModelViewer_module.sidePanel} ${!panelOpen ? ModelViewer_module.hidden : ''}`, children: [jsxRuntime.jsxs("div", { className: ModelViewer_module.panelSection, children: [jsxRuntime.jsx("div", { className: ModelViewer_module.panelTitle, children: t('modelViewer.modelInfo', 'Model Info') }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: t('modelViewer.triangles', 'Triangles') }), jsxRuntime.jsx("span", { children: api.polyCount.toLocaleString() })] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: t('modelViewer.meshes', 'Meshes') }), jsxRuntime.jsx("span", { children: api.meshCount })] }), api.dimensions && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: t('modelViewer.width', 'Width') }), jsxRuntime.jsx("span", { children: api.dimensions.width })] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: t('modelViewer.height', 'Height') }), jsxRuntime.jsx("span", { children: api.dimensions.height })] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: t('modelViewer.depth', 'Depth') }), jsxRuntime.jsx("span", { children: api.dimensions.depth })] })] }))] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelSection, children: [jsxRuntime.jsx("div", { className: ModelViewer_module.panelTitle, children: t('modelViewer.navigation', 'Navigation') }), navModes.map((m) => (jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: api.navigationMode === m ? 'primary' : 'ghost', size: "sm", onClick: () => api.setNavigationMode(m), children: jsxRuntime.jsx(NavLabel, { mode: m }) }, m)))] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelSection, children: [jsxRuntime.jsx("div", { className: ModelViewer_module.panelTitle, children: t('modelViewer.autoRotate', 'Auto Rotate') }), jsxRuntime.jsx(ui.NiceSlider, { min: 0, max: 10, step: 0.5, value: api.autoRotateSpeed, onChange: (val) => api.setAutoRotateSpeed(val), style: { width: '100%' } })] }), api.animations.length > 0 && (jsxRuntime.jsxs("div", { className: ModelViewer_module.panelSection, children: [jsxRuntime.jsx("div", { className: ModelViewer_module.panelTitle, children: t('modelViewer.animations', 'Animations') }), api.animations.map((name, i) => (jsxRuntime.jsxs(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: api.activeAnimIdx === i ? 'primary' : 'ghost', size: "sm", onClick: () => api.playAnimation(i), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "play", size: 14 }), " ", name] }, i)))] }))] }), jsxRuntime.jsxs("div", { className: `${ModelViewer_module.animBar} ${api.animations.length === 0 ? ModelViewer_module.hidden : ''}`, children: [jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: "ghost", size: "sm", onClick: api.togglePlay, "aria-label": t('modelViewer.playPause', 'Play/Pause'), children: jsxRuntime.jsx(ui.NiceIcon, { name: api.isPlaying ? 'pause' : 'play', size: 16 }) }), jsxRuntime.jsx(ui.NiceSelect, { className: ModelViewer_module.animSelect, value: String(api.activeAnimIdx), onChange: (val) => api.playAnimation(parseInt(val)), options: api.animations.map((n, i) => ({ value: String(i), label: n })) }), jsxRuntime.jsx(ui.NiceSlider, { className: ModelViewer_module.animSlider, min: 0, max: api.animDuration || 1, step: 0.01, value: api.animTime, onChange: (val) => api.seekAnim(val) }), jsxRuntime.jsxs("span", { className: ModelViewer_module.toolLabel, children: [api.animTime.toFixed(1), "s / ", api.animDuration.toFixed(1), "s"] })] }), showStatusBar && (jsxRuntime.jsxs("div", { className: ModelViewer_module.statusBar, children: [jsxRuntime.jsx("span", { children: api.statusText }), jsxRuntime.jsxs("div", { className: ModelViewer_module.statusRight, children: [jsxRuntime.jsx("span", { children: jsxRuntime.jsx(NavLabel, { mode: api.navigationMode }) }), api.dimensions && (jsxRuntime.jsxs("span", { children: [api.dimensions.width, "\u00D7", api.dimensions.height, "\u00D7", api.dimensions.depth] }))] })] })), children === null || children === void 0 ? void 0 : children(api)] }));
64
66
  };
65
67
 
66
68
  exports.NiceModelViewer = NiceModelViewer;
@@ -4,6 +4,7 @@ var jsxRuntime = require('react/jsx-runtime');
4
4
  var ui = require('@nice2dev/ui');
5
5
  var React = require('react');
6
6
  var THREE = require('three');
7
+ var i18n = require('../core/i18n.js');
7
8
 
8
9
  function _interopNamespaceDefault(e) {
9
10
  var n = Object.create(null);
@@ -232,6 +233,7 @@ function useArmatureEditor(skeleton, scene, onBoneSelect) {
232
233
  }
233
234
  /* ── Component ── */
234
235
  const NiceArmatureEditor = ({ skeleton, skinnedMesh: _skinnedMesh, scene, camera: _camera, onBoneSelect, onPoseChange: _onPoseChange, className = '', tutorial, }) => {
236
+ const { t } = i18n.useNiceTranslation();
235
237
  const { bones, selectedBoneId, selectBone, setBonePosition: _setBonePosition, setBoneRotation, addBone, removeBone, savePose, loadPose, resetPose, savedPoses, skeletonHelper: _skeletonHelper, } = useArmatureEditor(skeleton, scene, onBoneSelect);
236
238
  const [newBoneName, setNewBoneName] = React.useState('');
237
239
  const selectedBone = React.useMemo(() => bones.find((b) => b.id === selectedBoneId), [bones, selectedBoneId]);
@@ -249,13 +251,13 @@ const NiceArmatureEditor = ({ skeleton, skinnedMesh: _skinnedMesh, scene, camera
249
251
  borderRadius: 4,
250
252
  }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "target", size: 14 }), " ", boneInfo.name] }), childBones.map((child) => renderBoneTree(child, depth + 1))] }, boneInfo.id));
251
253
  };
252
- return (jsxRuntime.jsxs("div", { className: `nice-armature-editor ${className}`, style: { position: 'relative', display: 'flex', flexDirection: 'column', gap: 8 }, children: [jsxRuntime.jsx("span", { style: { position: 'absolute', top: 0, right: 0, zIndex: 20 }, children: jsxRuntime.jsx(ui.NiceTutorialButton, { steps: ui.resolveTutorial(tutorial, ARMATURE_EDITOR_TOUR) }) }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4 }, children: [jsxRuntime.jsxs("button", { type: "button", onClick: resetPose, title: "Reset Pose", children: [jsxRuntime.jsx(ui.NiceIcon, { name: "refresh", size: 14 }), " Reset"] }), jsxRuntime.jsxs("button", { type: "button", onClick: () => savePose(`Pose ${savedPoses.length + 1}`), title: "Save Pose", children: [jsxRuntime.jsx(ui.NiceIcon, { name: "save", size: 14 }), " Save Pose"] })] }), jsxRuntime.jsx("div", { style: {
254
+ return (jsxRuntime.jsxs("div", { className: `nice-armature-editor ${className}`, style: { position: 'relative', display: 'flex', flexDirection: 'column', gap: 8 }, children: [jsxRuntime.jsx("span", { style: { position: 'absolute', top: 0, right: 0, zIndex: 20 }, children: jsxRuntime.jsx(ui.NiceTutorialButton, { steps: ui.resolveTutorial(tutorial, ARMATURE_EDITOR_TOUR) }) }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4 }, children: [jsxRuntime.jsxs("button", { type: "button", onClick: resetPose, title: t('armatureEditor.resetPose', 'Reset Pose'), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "refresh", size: 14 }), " ", t('armatureEditor.reset', 'Reset')] }), jsxRuntime.jsxs("button", { type: "button", onClick: () => savePose(`Pose ${savedPoses.length + 1}`), title: t('armatureEditor.savePose', 'Save Pose'), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "save", size: 14 }), " ", t('armatureEditor.savePose', 'Save Pose')] })] }), jsxRuntime.jsx("div", { style: {
253
255
  maxHeight: 200,
254
256
  overflowY: 'auto',
255
257
  border: '1px solid var(--nice-border, #e2e8f0)',
256
258
  borderRadius: 6,
257
259
  padding: 4,
258
- }, children: rootBones.length === 0 ? (jsxRuntime.jsx("div", { style: { padding: 16, textAlign: 'center', color: 'var(--nice-text-muted, #999)' }, children: "No skeleton found" })) : (rootBones.map((b) => renderBoneTree(b))) }), selectedBoneId && (jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4 }, children: [jsxRuntime.jsx("input", { type: "text", value: newBoneName, onChange: (e) => setNewBoneName(e.target.value), placeholder: "New bone name", style: {
260
+ }, children: rootBones.length === 0 ? (jsxRuntime.jsx("div", { style: { padding: 16, textAlign: 'center', color: 'var(--nice-text-muted, #999)' }, children: t('armatureEditor.noSkeletonFound', 'No skeleton found') })) : (rootBones.map((b) => renderBoneTree(b))) }), selectedBoneId && (jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4 }, children: [jsxRuntime.jsx("input", { type: "text", value: newBoneName, onChange: (e) => setNewBoneName(e.target.value), placeholder: t('armatureEditor.newBoneName', 'New bone name'), style: {
259
261
  flex: 1,
260
262
  padding: '4px 8px',
261
263
  borderRadius: 4,
@@ -263,7 +265,7 @@ const NiceArmatureEditor = ({ skeleton, skinnedMesh: _skinnedMesh, scene, camera
263
265
  } }), jsxRuntime.jsx("button", { type: "button", disabled: !newBoneName.trim(), onClick: () => {
264
266
  addBone(selectedBoneId, newBoneName.trim());
265
267
  setNewBoneName('');
266
- }, children: "+ Add" }), jsxRuntime.jsx("button", { type: "button", onClick: () => removeBone(selectedBoneId), "aria-label": "Remove bone", children: jsxRuntime.jsx(ui.NiceIcon, { name: "trash", size: 14 }) })] })), selectedBone && (jsxRuntime.jsxs("div", { style: {
268
+ }, children: "+ Add" }), jsxRuntime.jsx("button", { type: "button", onClick: () => removeBone(selectedBoneId), "aria-label": t('armatureEditor.removeBone', 'Remove bone'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "trash", size: 14 }) })] })), selectedBone && (jsxRuntime.jsxs("div", { style: {
267
269
  display: 'grid',
268
270
  gap: 4,
269
271
  fontSize: 12,
@@ -282,7 +284,7 @@ const NiceArmatureEditor = ({ skeleton, skinnedMesh: _skinnedMesh, scene, camera
282
284
  const rot = selectedBone.localRotation.clone();
283
285
  rot.z = parseFloat(e.target.value) * (Math.PI / 180);
284
286
  setBoneRotation(selectedBone.id, rot);
285
- } })] })), savedPoses.length > 0 && (jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsx("strong", { style: { fontSize: 12 }, children: "Saved Poses" }), savedPoses.map((pose) => (jsxRuntime.jsx("button", { type: "button", onClick: () => loadPose(pose), style: { textAlign: 'left', padding: '4px 8px', fontSize: 12 }, children: pose.name }, pose.id)))] }))] }));
287
+ } })] })), savedPoses.length > 0 && (jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsx("strong", { style: { fontSize: 12 }, children: t('armatureEditor.savedPoses', 'Saved Poses') }), savedPoses.map((pose) => (jsxRuntime.jsx("button", { type: "button", onClick: () => loadPose(pose), style: { textAlign: 'left', padding: '4px 8px', fontSize: 12 }, children: pose.name }, pose.id)))] }))] }));
286
288
  };
287
289
 
288
290
  exports.NiceArmatureEditor = NiceArmatureEditor;