@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
 
8
9
  function _interopNamespaceDefault(e) {
9
10
  var n = Object.create(null);
@@ -211,6 +212,7 @@ function useMorphTargetEditor(mesh, onInfluenceChange) {
211
212
  }
212
213
  /* ── Component ── */
213
214
  const NiceMorphTargetEditor = ({ mesh, onInfluenceChange, className = '', tutorial, }) => {
215
+ const { t } = i18n.useNiceTranslation();
214
216
  const { targets, setInfluence, createMorphTarget, deleteMorphTarget, resetAllInfluences, isAnimating, startAnimation, stopAnimation, animations, addAnimation: _addAnimation, removeAnimation, } = useMorphTargetEditor(mesh, onInfluenceChange);
215
217
  const [newName, setNewName] = React.useState('');
216
218
  const handleCreate = () => {
@@ -221,17 +223,17 @@ const NiceMorphTargetEditor = ({ mesh, onInfluenceChange, className = '', tutori
221
223
  createMorphTarget(name);
222
224
  setNewName('');
223
225
  };
224
- return (jsxRuntime.jsxs("div", { className: `nice-morph-target-editor ${className}`, style: { position: 'relative', display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsx("span", { style: { position: 'absolute', top: 0, right: 0, zIndex: 20 }, children: jsxRuntime.jsx(ui.NiceTutorialButton, { steps: ui.resolveTutorial(tutorial, MORPH_TARGET_EDITOR_TOUR) }) }), jsxRuntime.jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' }, children: [jsxRuntime.jsx("strong", { children: "Morph Targets" }), jsxRuntime.jsx("button", { type: "button", onClick: resetAllInfluences, style: { fontSize: 11 }, children: "Reset All" })] }), targets.length === 0 && (jsxRuntime.jsx("div", { style: { opacity: 0.6, fontStyle: 'italic' }, children: "No morph targets found." })), targets.map((target) => (jsxRuntime.jsxs("div", { style: {
226
+ return (jsxRuntime.jsxs("div", { className: `nice-morph-target-editor ${className}`, style: { position: 'relative', display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsx("span", { style: { position: 'absolute', top: 0, right: 0, zIndex: 20 }, children: jsxRuntime.jsx(ui.NiceTutorialButton, { steps: ui.resolveTutorial(tutorial, MORPH_TARGET_EDITOR_TOUR) }) }), jsxRuntime.jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' }, children: [jsxRuntime.jsx("strong", { children: t('morphTargetEditor.morphTargets', 'Morph Targets') }), jsxRuntime.jsx("button", { type: "button", onClick: resetAllInfluences, style: { fontSize: 11 }, children: t('morphTargetEditor.resetAll', 'Reset All') })] }), targets.length === 0 && (jsxRuntime.jsx("div", { style: { opacity: 0.6, fontStyle: 'italic' }, children: t('morphTargetEditor.noMorphTargetsFound', 'No morph targets found.') })), targets.map((target) => (jsxRuntime.jsxs("div", { style: {
225
227
  display: 'grid',
226
228
  gridTemplateColumns: '1fr 50px auto',
227
229
  alignItems: 'center',
228
230
  gap: 6,
229
- }, children: [jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("label", { style: { display: 'block', fontSize: 11, marginBottom: 2 }, children: target.name }), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: target.influence, onChange: (e) => setInfluence(target.index, parseFloat(e.target.value)), style: { width: '100%' } })] }), jsxRuntime.jsx("span", { style: { fontSize: 11, textAlign: 'right' }, children: target.influence.toFixed(2) }), jsxRuntime.jsx("button", { type: "button", onClick: () => deleteMorphTarget(target.index), title: "Delete", "aria-label": "Delete", style: { background: 'none', border: 'none', cursor: 'pointer' }, children: jsxRuntime.jsx(ui.NiceIcon, { name: "trash", size: 14 }) })] }, target.index))), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4, marginTop: 4 }, children: [jsxRuntime.jsx("input", { type: "text", value: newName, onChange: (e) => setNewName(e.target.value), placeholder: "New morph target name\u2026", onKeyDown: (e) => e.key === 'Enter' && handleCreate(), style: {
231
+ }, children: [jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("label", { style: { display: 'block', fontSize: 11, marginBottom: 2 }, children: target.name }), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: target.influence, onChange: (e) => setInfluence(target.index, parseFloat(e.target.value)), style: { width: '100%' } })] }), jsxRuntime.jsx("span", { style: { fontSize: 11, textAlign: 'right' }, children: target.influence.toFixed(2) }), jsxRuntime.jsx("button", { type: "button", onClick: () => deleteMorphTarget(target.index), title: t('morphTargetEditor.delete', 'Delete'), "aria-label": t('morphTargetEditor.delete', 'Delete'), style: { background: 'none', border: 'none', cursor: 'pointer' }, children: jsxRuntime.jsx(ui.NiceIcon, { name: "trash", size: 14 }) })] }, target.index))), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4, marginTop: 4 }, children: [jsxRuntime.jsx("input", { type: "text", value: newName, onChange: (e) => setNewName(e.target.value), placeholder: t('morphTargetEditor.newMorphTargetName', 'New morph target name…'), onKeyDown: (e) => e.key === 'Enter' && handleCreate(), style: {
230
232
  flex: 1,
231
233
  padding: '4px 8px',
232
234
  border: '1px solid var(--nice-border, #e2e8f0)',
233
235
  borderRadius: 4,
234
- } }), jsxRuntime.jsx("button", { type: "button", onClick: handleCreate, children: "+ Add" })] }), animations.length > 0 && (jsxRuntime.jsxs("div", { style: { marginTop: 8 }, children: [jsxRuntime.jsx("strong", { style: { fontSize: 12 }, children: "Animations" }), animations.map((anim) => (jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4, marginTop: 4 }, children: [jsxRuntime.jsx("span", { style: { flex: 1 }, children: anim.name }), jsxRuntime.jsx("button", { type: "button", onClick: () => (isAnimating ? stopAnimation() : startAnimation(anim)), style: { fontSize: 11 }, "aria-label": isAnimating ? 'Stop' : 'Play', children: jsxRuntime.jsx(ui.NiceIcon, { name: isAnimating ? 'stop' : 'play', size: 14 }) }), jsxRuntime.jsx("button", { type: "button", onClick: () => removeAnimation(anim.name), style: { fontSize: 11 }, "aria-label": "Remove animation", children: jsxRuntime.jsx(ui.NiceIcon, { name: "trash", size: 14 }) })] }, anim.name)))] }))] }));
236
+ } }), jsxRuntime.jsx("button", { type: "button", onClick: handleCreate, children: "+ Add" })] }), animations.length > 0 && (jsxRuntime.jsxs("div", { style: { marginTop: 8 }, children: [jsxRuntime.jsx("strong", { style: { fontSize: 12 }, children: t('morphTargetEditor.animations', 'Animations') }), animations.map((anim) => (jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4, marginTop: 4 }, children: [jsxRuntime.jsx("span", { style: { flex: 1 }, children: anim.name }), jsxRuntime.jsx("button", { type: "button", onClick: () => (isAnimating ? stopAnimation() : startAnimation(anim)), style: { fontSize: 11 }, "aria-label": isAnimating ? 'Stop' : 'Play', children: jsxRuntime.jsx(ui.NiceIcon, { name: isAnimating ? 'stop' : 'play', size: 14 }) }), jsxRuntime.jsx("button", { type: "button", onClick: () => removeAnimation(anim.name), style: { fontSize: 11 }, "aria-label": t('morphTargetEditor.removeAnimation', 'Remove animation'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "trash", size: 14 }) })] }, anim.name)))] }))] }));
235
237
  };
236
238
 
237
239
  exports.NiceMorphTargetEditor = NiceMorphTargetEditor;
@@ -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);
@@ -345,6 +346,7 @@ function useOctree(scene, configOverride) {
345
346
  }
346
347
  /* ── Component ── */
347
348
  const NiceOctree = ({ scene, config, showDebug: initialShowDebug, className = '', }) => {
349
+ const { t } = i18n.useNiceTranslation();
348
350
  const octree = useOctree(scene, config);
349
351
  React.useEffect(() => {
350
352
  if (initialShowDebug !== undefined) {
@@ -358,7 +360,7 @@ const NiceOctree = ({ scene, config, showDebug: initialShowDebug, className = ''
358
360
  borderRadius: 4,
359
361
  padding: '6px 12px',
360
362
  cursor: 'pointer',
361
- }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "zap", size: 14 }), " Build Octree"] }), jsxRuntime.jsxs("div", { style: { fontSize: 11, opacity: 0.7 }, children: ["Nodes: ", octree.nodeCount, " | Objects: ", octree.objectCount] }), jsxRuntime.jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: octree.showDebug, onChange: (e) => octree.setShowDebug(e.target.checked) }), "Show debug wireframes"] })] }));
363
+ }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "zap", size: 14 }), " ", t('octree.buildOctree', 'Build Octree')] }), jsxRuntime.jsxs("div", { style: { fontSize: 11, opacity: 0.7 }, children: ["Nodes: ", octree.nodeCount, " | Objects: ", octree.objectCount] }), jsxRuntime.jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: octree.showDebug, onChange: (e) => octree.setShowDebug(e.target.checked) }), t('octree.showDebugWireframes', 'Show debug wireframes')] })] }));
362
364
  };
363
365
 
364
366
  exports.NiceOctree = NiceOctree;
@@ -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);
@@ -282,11 +283,12 @@ function usePhysicsSimulation(scene, onSimulationStep) {
282
283
  }
283
284
  /* ── Component ── */
284
285
  const NicePhysicsSimulation = ({ scene, selectedObject, onSimulationStep, className = '', }) => {
286
+ const { t } = i18n.useNiceTranslation();
285
287
  const { worldConfig, setWorldConfig, bodies, addBody, removeBody, updateBody, getBodyForObject, isRunning, play, pause, step, reset, debugDraw, setDebugDraw, } = usePhysicsSimulation(scene, onSimulationStep);
286
288
  const selectedBody = selectedObject
287
289
  ? getBodyForObject(selectedObject.name || selectedObject.uuid)
288
290
  : undefined;
289
- return (jsxRuntime.jsxs("div", { className: `nice-physics-simulation ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' }, children: [jsxRuntime.jsxs("strong", { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "zap", size: 14 }), " Physics"] }), jsxRuntime.jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 4, fontSize: 11 }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: debugDraw, onChange: (e) => setDebugDraw(e.target.checked) }), "Debug"] })] }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4 }, children: [jsxRuntime.jsx("button", { type: "button", onClick: isRunning ? pause : play, style: {
291
+ return (jsxRuntime.jsxs("div", { className: `nice-physics-simulation ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' }, children: [jsxRuntime.jsxs("strong", { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "zap", size: 14 }), " ", t('physicsSimulation.physics', 'Physics')] }), jsxRuntime.jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 4, fontSize: 11 }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: debugDraw, onChange: (e) => setDebugDraw(e.target.checked) }), t('physicsSimulation.debug', 'Debug')] })] }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4 }, children: [jsxRuntime.jsx("button", { type: "button", onClick: isRunning ? pause : play, style: {
290
292
  flex: 1,
291
293
  background: isRunning ? 'var(--nice-danger, #ef4444)' : 'var(--nice-success, #22c55e)',
292
294
  color: 'var(--nice-color-white, #fff)',
@@ -294,7 +296,7 @@ const NicePhysicsSimulation = ({ scene, selectedObject, onSimulationStep, classN
294
296
  borderRadius: 4,
295
297
  padding: '5px 8px',
296
298
  cursor: 'pointer',
297
- }, children: isRunning ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "pause", size: 14 }), " Pause"] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "play", size: 14 }), " Play"] })) }), jsxRuntime.jsxs("button", { type: "button", onClick: step, disabled: isRunning, style: { padding: '5px 8px', cursor: 'pointer' }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "skip-forward", size: 14 }), " Step"] }), jsxRuntime.jsxs("button", { type: "button", onClick: reset, style: { padding: '5px 8px', cursor: 'pointer' }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "refresh", size: 14 }), " Reset"] })] }), jsxRuntime.jsxs("label", { children: ["Gravity Y: ", worldConfig.gravity.y.toFixed(1), jsxRuntime.jsx("input", { type: "range", min: -20, max: 0, step: 0.1, value: worldConfig.gravity.y, onChange: (e) => setWorldConfig({ gravity: new THREE__namespace.Vector3(0, parseFloat(e.target.value), 0) }), style: { width: '100%' } })] }), selectedObject && (jsxRuntime.jsxs("fieldset", { style: {
299
+ }, children: isRunning ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "pause", size: 14 }), " ", t('physicsSimulation.pause', 'Pause')] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "play", size: 14 }), " ", t('physicsSimulation.play', 'Play')] })) }), jsxRuntime.jsxs("button", { type: "button", onClick: step, disabled: isRunning, style: { padding: '5px 8px', cursor: 'pointer' }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "skip-forward", size: 14 }), " ", t('physicsSimulation.step', 'Step')] }), jsxRuntime.jsxs("button", { type: "button", onClick: reset, style: { padding: '5px 8px', cursor: 'pointer' }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "refresh", size: 14 }), " ", t('physicsSimulation.reset', 'Reset')] })] }), jsxRuntime.jsxs("label", { children: ["Gravity Y: ", worldConfig.gravity.y.toFixed(1), jsxRuntime.jsx("input", { type: "range", min: -20, max: 0, step: 0.1, value: worldConfig.gravity.y, onChange: (e) => setWorldConfig({ gravity: new THREE__namespace.Vector3(0, parseFloat(e.target.value), 0) }), style: { width: '100%' } })] }), selectedObject && (jsxRuntime.jsxs("fieldset", { style: {
298
300
  border: '1px solid var(--nice-border, #e2e8f0)',
299
301
  borderRadius: 6,
300
302
  padding: 6,
@@ -307,7 +309,7 @@ const NicePhysicsSimulation = ({ scene, selectedObject, onSimulationStep, classN
307
309
  borderRadius: 4,
308
310
  padding: '5px 8px',
309
311
  cursor: 'pointer',
310
- }, children: "+ Add Rigid Body" })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("label", { children: ["Type:", jsxRuntime.jsxs("select", { value: selectedBody.bodyType, onChange: (e) => updateBody(selectedBody.id, { bodyType: e.target.value }), style: { marginLeft: 8, width: '100%' }, children: [jsxRuntime.jsx("option", { value: "dynamic", children: "Dynamic" }), jsxRuntime.jsx("option", { value: "static", children: "Static" }), jsxRuntime.jsx("option", { value: "kinematic", children: "Kinematic" })] })] }), jsxRuntime.jsxs("label", { children: ["Shape:", jsxRuntime.jsxs("select", { value: selectedBody.colliderShape, onChange: (e) => updateBody(selectedBody.id, { colliderShape: e.target.value }), style: { marginLeft: 8, width: '100%' }, children: [jsxRuntime.jsx("option", { value: "box", children: "Box" }), jsxRuntime.jsx("option", { value: "sphere", children: "Sphere" }), jsxRuntime.jsx("option", { value: "capsule", children: "Capsule" }), jsxRuntime.jsx("option", { value: "convexHull", children: "Convex Hull" }), jsxRuntime.jsx("option", { value: "trimesh", children: "Trimesh" })] })] }), jsxRuntime.jsxs("label", { children: ["Mass: ", selectedBody.mass.toFixed(1), jsxRuntime.jsx("input", { type: "range", min: 0.1, max: 100, step: 0.1, value: selectedBody.mass, onChange: (e) => updateBody(selectedBody.id, { mass: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Restitution: ", selectedBody.restitution.toFixed(2), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: selectedBody.restitution, onChange: (e) => updateBody(selectedBody.id, { restitution: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Friction: ", selectedBody.friction.toFixed(2), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: selectedBody.friction, onChange: (e) => updateBody(selectedBody.id, { friction: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: selectedBody.isSensor, onChange: (e) => updateBody(selectedBody.id, { isSensor: e.target.checked }) }), "Sensor (trigger only)"] }), jsxRuntime.jsx("button", { type: "button", onClick: () => removeBody(selectedBody.id), style: {
312
+ }, children: "+ Add Rigid Body" })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("label", { children: ["Type:", jsxRuntime.jsxs("select", { value: selectedBody.bodyType, onChange: (e) => updateBody(selectedBody.id, { bodyType: e.target.value }), style: { marginLeft: 8, width: '100%' }, children: [jsxRuntime.jsx("option", { value: "dynamic", children: t('physicsSimulation.dynamic', 'Dynamic') }), jsxRuntime.jsx("option", { value: "static", children: t('physicsSimulation.static', 'Static') }), jsxRuntime.jsx("option", { value: "kinematic", children: t('physicsSimulation.kinematic', 'Kinematic') })] })] }), jsxRuntime.jsxs("label", { children: ["Shape:", jsxRuntime.jsxs("select", { value: selectedBody.colliderShape, onChange: (e) => updateBody(selectedBody.id, { colliderShape: e.target.value }), style: { marginLeft: 8, width: '100%' }, children: [jsxRuntime.jsx("option", { value: "box", children: "Box" }), jsxRuntime.jsx("option", { value: "sphere", children: t('physicsSimulation.sphere', 'Sphere') }), jsxRuntime.jsx("option", { value: "capsule", children: t('physicsSimulation.capsule', 'Capsule') }), jsxRuntime.jsx("option", { value: "convexHull", children: t('physicsSimulation.convexHull', 'Convex Hull') }), jsxRuntime.jsx("option", { value: "trimesh", children: t('physicsSimulation.trimesh', 'Trimesh') })] })] }), jsxRuntime.jsxs("label", { children: ["Mass: ", selectedBody.mass.toFixed(1), jsxRuntime.jsx("input", { type: "range", min: 0.1, max: 100, step: 0.1, value: selectedBody.mass, onChange: (e) => updateBody(selectedBody.id, { mass: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Restitution: ", selectedBody.restitution.toFixed(2), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: selectedBody.restitution, onChange: (e) => updateBody(selectedBody.id, { restitution: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Friction: ", selectedBody.friction.toFixed(2), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: selectedBody.friction, onChange: (e) => updateBody(selectedBody.id, { friction: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: selectedBody.isSensor, onChange: (e) => updateBody(selectedBody.id, { isSensor: e.target.checked }) }), "Sensor (trigger only)"] }), jsxRuntime.jsx("button", { type: "button", onClick: () => removeBody(selectedBody.id), style: {
311
313
  background: 'var(--nice-danger, #ef4444)',
312
314
  color: 'var(--nice-text-on-primary, #fff)',
313
315
  border: 'none',
@@ -315,7 +317,7 @@ const NicePhysicsSimulation = ({ scene, selectedObject, onSimulationStep, classN
315
317
  padding: '4px 8px',
316
318
  cursor: 'pointer',
317
319
  fontSize: 11,
318
- }, children: "Remove Rigid Body" })] }))] })), jsxRuntime.jsxs("div", { style: { fontSize: 11, opacity: 0.7 }, children: ["Total bodies: ", bodies.length] })] }));
320
+ }, children: t('physicsSimulation.removeRigidBody', 'Remove Rigid Body') })] }))] })), jsxRuntime.jsxs("div", { style: { fontSize: 11, opacity: 0.7 }, children: ["Total bodies: ", bodies.length] })] }));
319
321
  };
320
322
 
321
323
  exports.NicePhysicsSimulation = NicePhysicsSimulation;
@@ -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);
@@ -313,21 +314,22 @@ function useProceduralGeometry(scene, onGenerated) {
313
314
  }
314
315
  /* ── Component ── */
315
316
  const NiceProceduralGeometry = ({ scene, onGenerated, className = '', }) => {
317
+ const { t } = i18n.useNiceTranslation();
316
318
  const { type, setType, treeParams, setTreeParams, rockParams, setRockParams, buildingParams, setBuildingParams, gearParams, setGearParams, generate, } = useProceduralGeometry(scene, onGenerated);
317
- return (jsxRuntime.jsxs("div", { className: `nice-procedural-geometry ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsx("strong", { children: "Procedural Geometry" }), jsxRuntime.jsxs("select", { value: type, onChange: (e) => setType(e.target.value), style: { width: '100%', padding: '4px 8px' }, children: [jsxRuntime.jsx("option", { value: "tree", children: "Tree" }), jsxRuntime.jsx("option", { value: "rock", children: "Rock" }), jsxRuntime.jsx("option", { value: "building", children: "Building" }), jsxRuntime.jsx("option", { value: "pipe", children: "Pipe" }), jsxRuntime.jsx("option", { value: "gear", children: "Gear" })] }), type === 'tree' && (jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsxs("label", { children: ["Height: ", treeParams.trunkHeight.toFixed(1), jsxRuntime.jsx("input", { type: "range", min: 1, max: 15, step: 0.5, value: treeParams.trunkHeight, onChange: (e) => setTreeParams({ ...treeParams, trunkHeight: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Branches: ", treeParams.branches, jsxRuntime.jsx("input", { type: "range", min: 1, max: 6, value: treeParams.branches, onChange: (e) => setTreeParams({ ...treeParams, branches: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Depth: ", treeParams.depth, jsxRuntime.jsx("input", { type: "range", min: 1, max: 5, value: treeParams.depth, onChange: (e) => setTreeParams({ ...treeParams, depth: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Angle: ", treeParams.branchAngle, "\u00B0", jsxRuntime.jsx("input", { type: "range", min: 10, max: 80, value: treeParams.branchAngle, onChange: (e) => setTreeParams({ ...treeParams, branchAngle: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Seed: ", treeParams.seed, jsxRuntime.jsx("input", { type: "range", min: 1, max: 999, value: treeParams.seed, onChange: (e) => setTreeParams({ ...treeParams, seed: parseInt(e.target.value, 10) }), style: { width: '100%' } })] })] })), type === 'rock' && (jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsxs("label", { children: ["Radius: ", rockParams.radius.toFixed(1), jsxRuntime.jsx("input", { type: "range", min: 0.2, max: 5, step: 0.1, value: rockParams.radius, onChange: (e) => setRockParams({ ...rockParams, radius: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Detail: ", rockParams.detail, jsxRuntime.jsx("input", { type: "range", min: 1, max: 5, value: rockParams.detail, onChange: (e) => setRockParams({ ...rockParams, detail: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Noise: ", rockParams.noiseAmount.toFixed(2), jsxRuntime.jsx("input", { type: "range", min: 0, max: 0.5, step: 0.01, value: rockParams.noiseAmount, onChange: (e) => setRockParams({ ...rockParams, noiseAmount: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Seed: ", rockParams.seed, jsxRuntime.jsx("input", { type: "range", min: 1, max: 999, value: rockParams.seed, onChange: (e) => setRockParams({ ...rockParams, seed: parseInt(e.target.value, 10) }), style: { width: '100%' } })] })] })), type === 'building' && (jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsxs("label", { children: ["Width: ", buildingParams.width, jsxRuntime.jsx("input", { type: "range", min: 4, max: 20, step: 1, value: buildingParams.width, onChange: (e) => setBuildingParams({ ...buildingParams, width: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Floors: ", buildingParams.floors, jsxRuntime.jsx("input", { type: "range", min: 1, max: 20, value: buildingParams.floors, onChange: (e) => setBuildingParams({ ...buildingParams, floors: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Windows/Floor: ", buildingParams.windowsPerFloor, jsxRuntime.jsx("input", { type: "range", min: 1, max: 10, value: buildingParams.windowsPerFloor, onChange: (e) => setBuildingParams({
319
+ return (jsxRuntime.jsxs("div", { className: `nice-procedural-geometry ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsx("strong", { children: t('proceduralGeometry.proceduralGeometry', 'Procedural Geometry') }), jsxRuntime.jsxs("select", { value: type, onChange: (e) => setType(e.target.value), style: { width: '100%', padding: '4px 8px' }, children: [jsxRuntime.jsx("option", { value: "tree", children: t('proceduralGeometry.tree', 'Tree') }), jsxRuntime.jsx("option", { value: "rock", children: t('proceduralGeometry.rock', 'Rock') }), jsxRuntime.jsx("option", { value: "building", children: t('proceduralGeometry.building', 'Building') }), jsxRuntime.jsx("option", { value: "pipe", children: t('proceduralGeometry.pipe', 'Pipe') }), jsxRuntime.jsx("option", { value: "gear", children: t('proceduralGeometry.gear', 'Gear') })] }), type === 'tree' && (jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsxs("label", { children: ["Height: ", treeParams.trunkHeight.toFixed(1), jsxRuntime.jsx("input", { type: "range", min: 1, max: 15, step: 0.5, value: treeParams.trunkHeight, onChange: (e) => setTreeParams({ ...treeParams, trunkHeight: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Branches: ", treeParams.branches, jsxRuntime.jsx("input", { type: "range", min: 1, max: 6, value: treeParams.branches, onChange: (e) => setTreeParams({ ...treeParams, branches: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Depth: ", treeParams.depth, jsxRuntime.jsx("input", { type: "range", min: 1, max: 5, value: treeParams.depth, onChange: (e) => setTreeParams({ ...treeParams, depth: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Angle: ", treeParams.branchAngle, "\u00B0", jsxRuntime.jsx("input", { type: "range", min: 10, max: 80, value: treeParams.branchAngle, onChange: (e) => setTreeParams({ ...treeParams, branchAngle: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Seed: ", treeParams.seed, jsxRuntime.jsx("input", { type: "range", min: 1, max: 999, value: treeParams.seed, onChange: (e) => setTreeParams({ ...treeParams, seed: parseInt(e.target.value, 10) }), style: { width: '100%' } })] })] })), type === 'rock' && (jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsxs("label", { children: ["Radius: ", rockParams.radius.toFixed(1), jsxRuntime.jsx("input", { type: "range", min: 0.2, max: 5, step: 0.1, value: rockParams.radius, onChange: (e) => setRockParams({ ...rockParams, radius: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Detail: ", rockParams.detail, jsxRuntime.jsx("input", { type: "range", min: 1, max: 5, value: rockParams.detail, onChange: (e) => setRockParams({ ...rockParams, detail: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Noise: ", rockParams.noiseAmount.toFixed(2), jsxRuntime.jsx("input", { type: "range", min: 0, max: 0.5, step: 0.01, value: rockParams.noiseAmount, onChange: (e) => setRockParams({ ...rockParams, noiseAmount: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Seed: ", rockParams.seed, jsxRuntime.jsx("input", { type: "range", min: 1, max: 999, value: rockParams.seed, onChange: (e) => setRockParams({ ...rockParams, seed: parseInt(e.target.value, 10) }), style: { width: '100%' } })] })] })), type === 'building' && (jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsxs("label", { children: ["Width: ", buildingParams.width, jsxRuntime.jsx("input", { type: "range", min: 4, max: 20, step: 1, value: buildingParams.width, onChange: (e) => setBuildingParams({ ...buildingParams, width: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Floors: ", buildingParams.floors, jsxRuntime.jsx("input", { type: "range", min: 1, max: 20, value: buildingParams.floors, onChange: (e) => setBuildingParams({ ...buildingParams, floors: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Windows/Floor: ", buildingParams.windowsPerFloor, jsxRuntime.jsx("input", { type: "range", min: 1, max: 10, value: buildingParams.windowsPerFloor, onChange: (e) => setBuildingParams({
318
320
  ...buildingParams,
319
321
  windowsPerFloor: parseInt(e.target.value, 10),
320
322
  }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Roof:", jsxRuntime.jsxs("select", { value: buildingParams.roofStyle, onChange: (e) => setBuildingParams({
321
323
  ...buildingParams,
322
324
  roofStyle: e.target.value,
323
- }), style: { marginLeft: 8 }, children: [jsxRuntime.jsx("option", { value: "flat", children: "Flat" }), jsxRuntime.jsx("option", { value: "gable", children: "Gable" }), jsxRuntime.jsx("option", { value: "hip", children: "Hip" })] })] })] })), type === 'gear' && (jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsxs("label", { children: ["Outer Radius: ", gearParams.outerRadius.toFixed(1), jsxRuntime.jsx("input", { type: "range", min: 0.5, max: 5, step: 0.1, value: gearParams.outerRadius, onChange: (e) => setGearParams({ ...gearParams, outerRadius: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Teeth: ", gearParams.teeth, jsxRuntime.jsx("input", { type: "range", min: 4, max: 30, value: gearParams.teeth, onChange: (e) => setGearParams({ ...gearParams, teeth: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Tooth Depth: ", gearParams.toothDepth.toFixed(2), jsxRuntime.jsx("input", { type: "range", min: 0.05, max: 1, step: 0.05, value: gearParams.toothDepth, onChange: (e) => setGearParams({ ...gearParams, toothDepth: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Thickness: ", gearParams.thickness.toFixed(2), jsxRuntime.jsx("input", { type: "range", min: 0.1, max: 2, step: 0.1, value: gearParams.thickness, onChange: (e) => setGearParams({ ...gearParams, thickness: parseFloat(e.target.value) }), style: { width: '100%' } })] })] })), jsxRuntime.jsxs("button", { type: "button", onClick: generate, style: {
325
+ }), style: { marginLeft: 8 }, children: [jsxRuntime.jsx("option", { value: "flat", children: t('proceduralGeometry.flat', 'Flat') }), jsxRuntime.jsx("option", { value: "gable", children: t('proceduralGeometry.gable', 'Gable') }), jsxRuntime.jsx("option", { value: "hip", children: "Hip" })] })] })] })), type === 'gear' && (jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsxs("label", { children: ["Outer Radius: ", gearParams.outerRadius.toFixed(1), jsxRuntime.jsx("input", { type: "range", min: 0.5, max: 5, step: 0.1, value: gearParams.outerRadius, onChange: (e) => setGearParams({ ...gearParams, outerRadius: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Teeth: ", gearParams.teeth, jsxRuntime.jsx("input", { type: "range", min: 4, max: 30, value: gearParams.teeth, onChange: (e) => setGearParams({ ...gearParams, teeth: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Tooth Depth: ", gearParams.toothDepth.toFixed(2), jsxRuntime.jsx("input", { type: "range", min: 0.05, max: 1, step: 0.05, value: gearParams.toothDepth, onChange: (e) => setGearParams({ ...gearParams, toothDepth: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Thickness: ", gearParams.thickness.toFixed(2), jsxRuntime.jsx("input", { type: "range", min: 0.1, max: 2, step: 0.1, value: gearParams.thickness, onChange: (e) => setGearParams({ ...gearParams, thickness: parseFloat(e.target.value) }), style: { width: '100%' } })] })] })), jsxRuntime.jsxs("button", { type: "button", onClick: generate, style: {
324
326
  background: 'var(--nice-color-accent, #6366f1)',
325
327
  color: 'var(--nice-text-on-primary, #fff)',
326
328
  border: 'none',
327
329
  borderRadius: 4,
328
330
  padding: '6px 12px',
329
331
  cursor: 'pointer',
330
- }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "wand", size: 14 }), " Generate"] })] }));
332
+ }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "wand", size: 14 }), " ", t('proceduralGeometry.generate', 'Generate')] })] }));
331
333
  };
332
334
 
333
335
  exports.NiceProceduralGeometry = NiceProceduralGeometry;
@@ -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);
@@ -340,6 +341,7 @@ function useTerrainEditor(scene, camera, canvas, config = { width: 100, depth: 1
340
341
  }
341
342
  /* ── Component ── */
342
343
  const NiceTerrainEditor = ({ scene, camera, canvas, config: configProp, onTerrainChange, className = '', tutorial, }) => {
344
+ const { t } = i18n.useNiceTranslation();
343
345
  const config = {
344
346
  width: 100,
345
347
  depth: 100,
@@ -350,14 +352,14 @@ const NiceTerrainEditor = ({ scene, camera, canvas, config: configProp, onTerrai
350
352
  const { brush, setBrush, isPainting, startPainting, stopPainting, generateFlat, generateNoise, exportHeightmap, textureLayers, setTextureLayer, activeTextureLayer, setActiveTextureLayer, } = useTerrainEditor(scene, camera, canvas, config, onTerrainChange);
351
353
  const [noiseOctaves, setNoiseOctaves] = React.useState(6);
352
354
  const [noisePersistence, setNoisePersistence] = React.useState(0.5);
353
- return (jsxRuntime.jsxs("div", { className: `nice-terrain-editor ${className}`, style: { position: 'relative', display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsx("span", { style: { position: 'absolute', top: 0, right: 0, zIndex: 20 }, children: jsxRuntime.jsx(ui.NiceTutorialButton, { steps: ui.resolveTutorial(tutorial, TERRAIN_EDITOR_TOUR) }) }), jsxRuntime.jsx("strong", { children: "Terrain Editor" }), jsxRuntime.jsxs("fieldset", { style: {
355
+ return (jsxRuntime.jsxs("div", { className: `nice-terrain-editor ${className}`, style: { position: 'relative', display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsx("span", { style: { position: 'absolute', top: 0, right: 0, zIndex: 20 }, children: jsxRuntime.jsx(ui.NiceTutorialButton, { steps: ui.resolveTutorial(tutorial, TERRAIN_EDITOR_TOUR) }) }), jsxRuntime.jsx("strong", { children: t('terrainEditor.terrainEditor', 'Terrain Editor') }), jsxRuntime.jsxs("fieldset", { style: {
354
356
  border: '1px solid var(--nice-border, #e2e8f0)',
355
357
  borderRadius: 6,
356
358
  padding: 4,
357
359
  display: 'flex',
358
360
  flexWrap: 'wrap',
359
361
  gap: 2,
360
- }, children: [jsxRuntime.jsx("legend", { style: { fontSize: 11 }, children: "Brush" }), ['raise', 'lower', 'flatten', 'smooth', 'noise', 'paint'].map((m) => (jsxRuntime.jsx("button", { type: "button", onClick: () => setBrush({ mode: m }), style: {
362
+ }, children: [jsxRuntime.jsx("legend", { style: { fontSize: 11 }, children: t('terrainEditor.brush', 'Brush') }), ['raise', 'lower', 'flatten', 'smooth', 'noise', 'paint'].map((m) => (jsxRuntime.jsx("button", { type: "button", onClick: () => setBrush({ mode: m }), style: {
361
363
  padding: '3px 6px',
362
364
  fontSize: 11,
363
365
  cursor: 'pointer',
@@ -374,19 +376,19 @@ const NiceTerrainEditor = ({ scene, camera, canvas, config: configProp, onTerrai
374
376
  borderRadius: 4,
375
377
  padding: '6px 12px',
376
378
  cursor: 'pointer',
377
- }, children: isPainting ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "stop", size: 14 }), " Stop Painting"] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "brush", size: 14 }), " Start Painting"] })) }), jsxRuntime.jsxs("fieldset", { style: {
379
+ }, children: isPainting ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "stop", size: 14 }), " ", t('terrainEditor.stopPainting', 'Stop Painting')] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "brush", size: 14 }), " ", t('terrainEditor.startPainting', 'Start Painting')] })) }), jsxRuntime.jsxs("fieldset", { style: {
378
380
  border: '1px solid var(--nice-border, #e2e8f0)',
379
381
  borderRadius: 6,
380
382
  padding: 6,
381
383
  display: 'grid',
382
384
  gap: 4,
383
- }, children: [jsxRuntime.jsx("legend", { style: { fontSize: 11 }, children: "Procedural" }), jsxRuntime.jsxs("label", { children: ["Octaves: ", noiseOctaves, jsxRuntime.jsx("input", { type: "range", min: 1, max: 10, value: noiseOctaves, onChange: (e) => setNoiseOctaves(parseInt(e.target.value, 10)), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Persistence: ", noisePersistence.toFixed(2), jsxRuntime.jsx("input", { type: "range", min: 0.1, max: 1, step: 0.05, value: noisePersistence, onChange: (e) => setNoisePersistence(parseFloat(e.target.value)), style: { width: '100%' } })] }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4 }, children: [jsxRuntime.jsxs("button", { type: "button", onClick: () => generateNoise(noiseOctaves, noisePersistence), style: { flex: 1 }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "mountain", size: 14 }), " Generate Noise"] }), jsxRuntime.jsxs("button", { type: "button", onClick: generateFlat, style: { flex: 1 }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "square", size: 14 }), " Flatten"] })] })] }), brush.mode === 'paint' && (jsxRuntime.jsxs("fieldset", { style: {
385
+ }, children: [jsxRuntime.jsx("legend", { style: { fontSize: 11 }, children: t('terrainEditor.procedural', 'Procedural') }), jsxRuntime.jsxs("label", { children: ["Octaves: ", noiseOctaves, jsxRuntime.jsx("input", { type: "range", min: 1, max: 10, value: noiseOctaves, onChange: (e) => setNoiseOctaves(parseInt(e.target.value, 10)), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Persistence: ", noisePersistence.toFixed(2), jsxRuntime.jsx("input", { type: "range", min: 0.1, max: 1, step: 0.05, value: noisePersistence, onChange: (e) => setNoisePersistence(parseFloat(e.target.value)), style: { width: '100%' } })] }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4 }, children: [jsxRuntime.jsxs("button", { type: "button", onClick: () => generateNoise(noiseOctaves, noisePersistence), style: { flex: 1 }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "mountain", size: 14 }), ' ', t('terrainEditor.generateNoise', 'Generate Noise')] }), jsxRuntime.jsxs("button", { type: "button", onClick: generateFlat, style: { flex: 1 }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "square", size: 14 }), " ", t('terrainEditor.flatten', 'Flatten')] })] })] }), brush.mode === 'paint' && (jsxRuntime.jsxs("fieldset", { style: {
384
386
  border: '1px solid var(--nice-border, #e2e8f0)',
385
387
  borderRadius: 6,
386
388
  padding: 6,
387
389
  display: 'grid',
388
390
  gap: 4,
389
- }, children: [jsxRuntime.jsx("legend", { style: { fontSize: 11 }, children: "Texture Layers" }), textureLayers.map((layer, i) => (jsxRuntime.jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [jsxRuntime.jsx("input", { type: "radio", name: "texLayer", checked: activeTextureLayer === i, onChange: () => setActiveTextureLayer(i) }), jsxRuntime.jsx("input", { type: "text", value: layer.name, onChange: (e) => setTextureLayer(i, { name: e.target.value }), style: {
391
+ }, children: [jsxRuntime.jsx("legend", { style: { fontSize: 11 }, children: t('terrainEditor.textureLayers', 'Texture Layers') }), textureLayers.map((layer, i) => (jsxRuntime.jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [jsxRuntime.jsx("input", { type: "radio", name: "texLayer", checked: activeTextureLayer === i, onChange: () => setActiveTextureLayer(i) }), jsxRuntime.jsx("input", { type: "text", value: layer.name, onChange: (e) => setTextureLayer(i, { name: e.target.value }), style: {
390
392
  flex: 1,
391
393
  padding: '2px 6px',
392
394
  border: '1px solid var(--nice-border, #e2e8f0)',
@@ -399,7 +401,7 @@ const NiceTerrainEditor = ({ scene, camera, canvas, config: configProp, onTerrai
399
401
  link.href = c.toDataURL('image/png');
400
402
  link.click();
401
403
  }
402
- }, style: { fontSize: 11 }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "download", size: 14 }), " Export Heightmap PNG"] })] }));
404
+ }, style: { fontSize: 11 }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "download", size: 14 }), ' ', t('terrainEditor.exportHeightmapPng', 'Export Heightmap PNG')] })] }));
403
405
  };
404
406
 
405
407
  exports.NiceTerrainEditor = NiceTerrainEditor;
@@ -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);
@@ -246,6 +247,7 @@ function updateHeatMapColors(mesh, boneIndex, colors) {
246
247
  }
247
248
  /* ── Component ── */
248
249
  const NiceWeightPainter = ({ skinnedMesh, activeBoneIndex: initialBoneIndex, scene, camera, canvas, onWeightsChange, className = '', }) => {
250
+ const { t } = i18n.useNiceTranslation();
249
251
  const { brush, setBrush, isPainting, activeBoneIndex, setActiveBoneIndex, boneNames, startPainting, stopPainting, normalizeWeights, showHeatMap, setShowHeatMap, } = useWeightPainter(skinnedMesh, scene, camera, canvas, onWeightsChange);
250
252
  React.useEffect(() => {
251
253
  setActiveBoneIndex(initialBoneIndex);
@@ -256,7 +258,7 @@ const NiceWeightPainter = ({ skinnedMesh, activeBoneIndex: initialBoneIndex, sce
256
258
  border: '1px solid var(--nice-border, #e2e8f0)',
257
259
  borderRadius: 6,
258
260
  padding: 4,
259
- }, children: [jsxRuntime.jsx("legend", { style: { fontSize: 11 }, children: "Mode" }), ['add', 'subtract', 'smooth', 'blur'].map((mode) => (jsxRuntime.jsx("button", { type: "button", onClick: () => setBrush({ mode }), style: {
261
+ }, children: [jsxRuntime.jsx("legend", { style: { fontSize: 11 }, children: t('weightPainter.mode', 'Mode') }), ['add', 'subtract', 'smooth', 'blur'].map((mode) => (jsxRuntime.jsx("button", { type: "button", onClick: () => setBrush({ mode }), style: {
260
262
  flex: 1,
261
263
  padding: '4px 6px',
262
264
  background: brush.mode === mode ? 'var(--nice-color-accent, #6366f1)' : 'transparent',
@@ -275,7 +277,7 @@ const NiceWeightPainter = ({ skinnedMesh, activeBoneIndex: initialBoneIndex, sce
275
277
  borderRadius: 4,
276
278
  padding: '6px 12px',
277
279
  cursor: 'pointer',
278
- }, children: isPainting ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "stop", size: 14 }), " Stop Painting"] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "brush", size: 14 }), " Start Painting"] })) }), jsxRuntime.jsxs("button", { type: "button", onClick: normalizeWeights, title: "Normalize all weights", children: [jsxRuntime.jsx(ui.NiceIcon, { name: "scale", size: 14 }), " Normalize"] })] }), jsxRuntime.jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: showHeatMap, onChange: (e) => setShowHeatMap(e.target.checked) }), "Show weight heat map"] })] }));
280
+ }, children: isPainting ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "stop", size: 14 }), " ", t('weightPainter.stopPainting', 'Stop Painting')] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "brush", size: 14 }), ' ', t('weightPainter.startPainting', 'Start Painting')] })) }), jsxRuntime.jsxs("button", { type: "button", onClick: normalizeWeights, title: t('weightPainter.normalizeAllWeights', 'Normalize all weights'), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "scale", size: 14 }), " ", t('weightPainter.normalize', 'Normalize')] })] }), jsxRuntime.jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: showHeatMap, onChange: (e) => setShowHeatMap(e.target.checked) }), t('weightPainter.showWeightHeatMap', 'Show weight heat map')] })] }));
279
281
  };
280
282
 
281
283
  exports.NiceWeightPainter = NiceWeightPainter;
@@ -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);
@@ -280,13 +281,14 @@ function useXRPreview(renderer, scene, camera, onSessionStart, onSessionEnd) {
280
281
  }
281
282
  /* ── Component ── */
282
283
  const NiceXRPreview = ({ renderer, scene, camera, mode: initialMode, onSessionStart, onSessionEnd, className = '', }) => {
284
+ const { t } = i18n.useNiceTranslation();
283
285
  const { isSupported, isSessionActive, mode, setMode, startSession, endSession, controllers, teleportEnabled, setTeleportEnabled, hitTestResult, } = useXRPreview(renderer, scene, camera, onSessionStart, onSessionEnd);
284
286
  React.useEffect(() => {
285
287
  if (initialMode) {
286
288
  setMode(initialMode);
287
289
  }
288
290
  }, [initialMode, setMode]);
289
- return (jsxRuntime.jsxs("div", { className: `nice-xr-preview ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsxs("strong", { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "eye", size: 14 }), " XR Preview"] }), !isSupported && (jsxRuntime.jsx("div", { style: { color: 'var(--nice-danger, #ef4444)', fontSize: 11 }, children: "WebXR is not supported in this browser." })), isSupported && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4 }, children: [jsxRuntime.jsxs("button", { type: "button", onClick: () => setMode('vr'), style: {
291
+ return (jsxRuntime.jsxs("div", { className: `nice-xr-preview ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsxs("strong", { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "eye", size: 14 }), " ", t('xrPreview.xrPreview', 'XR Preview')] }), !isSupported && (jsxRuntime.jsx("div", { style: { color: 'var(--nice-danger, #ef4444)', fontSize: 11 }, children: t('xrPreview.webxrIsNotSupportedIn', 'WebXR is not supported in this browser.') })), isSupported && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4 }, children: [jsxRuntime.jsxs("button", { type: "button", onClick: () => setMode('vr'), style: {
290
292
  flex: 1,
291
293
  padding: '5px 8px',
292
294
  borderRadius: 4,
@@ -311,7 +313,7 @@ const NiceXRPreview = ({ renderer, scene, camera, mode: initialMode, onSessionSt
311
313
  borderRadius: 4,
312
314
  padding: '6px 12px',
313
315
  cursor: 'pointer',
314
- }, children: isSessionActive ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "stop", size: 14 }), " Exit XR"] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "play", size: 14 }), " Enter XR"] })) }), mode === 'vr' && (jsxRuntime.jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: teleportEnabled, onChange: (e) => setTeleportEnabled(e.target.checked) }), "Teleportation"] })), isSessionActive && (jsxRuntime.jsxs("div", { style: { fontSize: 11, opacity: 0.7 }, children: ["Controllers: ", controllers.length, jsxRuntime.jsx("br", {}), mode === 'ar' && hitTestResult && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: ["Hit: (", hitTestResult.x.toFixed(2), ", ", hitTestResult.y.toFixed(2), ",", ' ', hitTestResult.z.toFixed(2), ")"] }))] }))] }))] }));
316
+ }, children: isSessionActive ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "stop", size: 14 }), " ", t('xrPreview.exitXr', 'Exit XR')] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "play", size: 14 }), " ", t('xrPreview.enterXr', 'Enter XR')] })) }), mode === 'vr' && (jsxRuntime.jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: teleportEnabled, onChange: (e) => setTeleportEnabled(e.target.checked) }), t('xrPreview.teleportation', 'Teleportation')] })), isSessionActive && (jsxRuntime.jsxs("div", { style: { fontSize: 11, opacity: 0.7 }, children: ["Controllers: ", controllers.length, jsxRuntime.jsx("br", {}), mode === 'ar' && hitTestResult && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: ["Hit: (", hitTestResult.x.toFixed(2), ", ", hitTestResult.y.toFixed(2), ",", ' ', hitTestResult.z.toFixed(2), ")"] }))] }))] }))] }));
315
317
  };
316
318
 
317
319
  exports.NiceXRPreview = NiceXRPreview;
@@ -23,6 +23,7 @@ var STLLoader_js = require('three/examples/jsm/loaders/STLLoader.js');
23
23
  var SVGLoader_js = require('three/examples/jsm/loaders/SVGLoader.js');
24
24
  var TDSLoader_js = require('three/examples/jsm/loaders/TDSLoader.js');
25
25
  var VTKLoader_js = require('three/examples/jsm/loaders/VTKLoader.js');
26
+ var editorAsk = require('../core/editorAsk.js');
26
27
  var threeCompat = require('../core/threeCompat.js');
27
28
  var modelEditorTypes = require('./modelEditorTypes.js');
28
29
  var modelEditorUtils = require('./modelEditorUtils.js');
@@ -53,7 +54,7 @@ var THREE__namespace = /*#__PURE__*/_interopNamespaceDefault(THREE);
53
54
  *
54
55
  * Extracted from the original ModelEditor.tsx god-component.
55
56
  */
56
- const log = { error: console.error, warn: console.warn, info: console.info };
57
+ const log = { error: console.error, warn: console.warn };
57
58
  /* helpers */
58
59
  let _nid = 0;
59
60
  const nid = () => `n${++_nid}`;
@@ -75,6 +76,7 @@ function findNodeById(nodes, id) {
75
76
  ═══════════════════════════════════════════ */
76
77
  function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
77
78
  var _a;
79
+ const { pendingAsk, ask } = editorAsk.useEditorAsk();
78
80
  /* ── refs ── */
79
81
  const mountRef = React.useRef(null);
80
82
  const rendererRef = React.useRef(null);
@@ -225,10 +227,9 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
225
227
  // goes into the scene is its helper.
226
228
  const tcHelper = tc.getHelper();
227
229
  tcHelper.name = '__transformControls';
228
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
229
- tc.addEventListener('dragging-changed', ((e) => {
230
+ tc.addEventListener('dragging-changed', (e) => {
230
231
  orbit.enabled = !e.value;
231
- }));
232
+ });
232
233
  scene.add(tcHelper);
233
234
  transformRef.current = tc;
234
235
  // Resize handler
@@ -1247,11 +1248,8 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
1247
1248
  }
1248
1249
  });
1249
1250
  }, []);
1250
- const clearScene = React.useCallback(() => {
1251
+ const clearSceneNow = React.useCallback(() => {
1251
1252
  var _a;
1252
- if (!window.confirm('Clear the entire scene? This cannot be undone.')) {
1253
- return;
1254
- }
1255
1253
  const scene = sceneRef.current;
1256
1254
  const toRemove = [];
1257
1255
  scene.traverse((obj) => {
@@ -1283,6 +1281,14 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
1283
1281
  rebuildTree();
1284
1282
  setStatusText('Scene cleared');
1285
1283
  }, [rebuildTree, disposeObject]);
1284
+ // asks first: clearing cannot be undone
1285
+ const clearScene = React.useCallback(() => {
1286
+ void ask('confirm', 'Clear the entire scene? This cannot be undone.').then((answer) => {
1287
+ if (answer !== null) {
1288
+ clearSceneNow();
1289
+ }
1290
+ });
1291
+ }, [ask, clearSceneNow]);
1286
1292
  /* ─────────────────────────────────────────
1287
1293
  Save to Library
1288
1294
  ───────────────────────────────────────── */
@@ -1300,19 +1306,19 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
1300
1306
  const blob = new Blob([result], { type: 'model/gltf-binary' });
1301
1307
  const reader = new FileReader();
1302
1308
  reader.onload = () => {
1303
- const defaultName = 'model.glb';
1304
- const name = window.prompt('Save to library as:', defaultName);
1305
- if (!name || !name.trim()) {
1306
- return;
1307
- }
1308
- onSaveToLibrary(reader.result, name.trim(), 'model/gltf-binary');
1309
+ const dataUrl = reader.result;
1310
+ void ask('prompt', 'Save to library as:', 'model.glb').then((name) => {
1311
+ if (name && name.trim()) {
1312
+ onSaveToLibrary(dataUrl, name.trim(), 'model/gltf-binary');
1313
+ }
1314
+ });
1309
1315
  };
1310
1316
  reader.readAsDataURL(blob);
1311
1317
  }, (err) => {
1312
1318
  log.error('Save to library error:', err);
1313
1319
  setStatusText(`Save error: ${String(err)}`);
1314
1320
  }, { binary: true, animations: allClipsRef.current.map((a) => a.clip) });
1315
- }, [onSaveToLibrary]);
1321
+ }, [onSaveToLibrary, ask]);
1316
1322
  /* ─────────────────────────────────────────
1317
1323
  Add Mesh / Light / Camera / Empty
1318
1324
  ───────────────────────────────────────── */
@@ -1808,6 +1814,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
1808
1814
  bgColor,
1809
1815
  setBgColor,
1810
1816
  statusText,
1817
+ pendingAsk,
1811
1818
  dragOver,
1812
1819
  polyCount,
1813
1820
  meshCount,