@nice2dev/ui-3d 1.1.10 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/CHANGELOG.md +173 -0
  2. package/README.md +11 -15
  3. package/dist/cjs/core/editorAsk.js +48 -0
  4. package/dist/cjs/core/i18n.js +7 -3
  5. package/dist/cjs/core/threeCompat.js +52 -0
  6. package/dist/cjs/dance/DanceScoreEngine.js +7 -0
  7. package/dist/cjs/dance/PoseDetector.js +116 -64
  8. package/dist/cjs/index.js +0 -3
  9. package/dist/cjs/material/NiceMaterialEditor.js +7 -3
  10. package/dist/cjs/material/materialEditorTypes.js +1 -1
  11. package/dist/cjs/material/materialNodeDefinitions.js +14 -11
  12. package/dist/cjs/model/ModelEditor.js +3 -5
  13. package/dist/cjs/model/ModelEditorLeftPanel.js +10 -4
  14. package/dist/cjs/model/ModelEditorMenuBar.js +7 -5
  15. package/dist/cjs/model/ModelEditorRightPanel.js +18 -16
  16. package/dist/cjs/model/ModelEditorSubComponents.js +8 -6
  17. package/dist/cjs/model/ModelEditorTimeline.js +6 -4
  18. package/dist/cjs/model/ModelEditorToolbar.js +6 -4
  19. package/dist/cjs/model/ModelEditorViewport.js +16 -6
  20. package/dist/cjs/model/ModelViewer.js +6 -7
  21. package/dist/cjs/model/NiceArmatureEditor.js +8 -6
  22. package/dist/cjs/model/NiceMorphTargetEditor.js +5 -3
  23. package/dist/cjs/model/NiceOctree.js +3 -1
  24. package/dist/cjs/model/NicePhysicsSimulation.js +7 -5
  25. package/dist/cjs/model/NiceProceduralGeometry.js +5 -3
  26. package/dist/cjs/model/NiceTerrainEditor.js +10 -8
  27. package/dist/cjs/model/NiceWeightPainter.js +6 -4
  28. package/dist/cjs/model/NiceXRPreview.js +7 -5
  29. package/dist/cjs/model/useModelEditor.js +30 -18
  30. package/dist/cjs/model/useModelViewer.js +13 -9
  31. package/dist/cjs/nice2dev-ui-3d.css +1 -1
  32. package/dist/cjs/particle/NiceParticleEditor.js +14 -9
  33. package/dist/cjs/rendering/NiceCascadedShadows.js +3 -1
  34. package/dist/cjs/rendering/NiceRenderExport.js +11 -5
  35. package/dist/cjs/rendering/NiceSSAO.js +3 -1
  36. package/dist/cjs/rendering/NiceSSR.js +3 -1
  37. package/dist/cjs/rendering/NiceWebGPURenderer.js +7 -4
  38. package/dist/cjs/uv/NiceUVEditor.js +16 -12
  39. package/dist/cjs/uv/uvEditorTypes.js +1 -0
  40. package/dist/esm/core/editorAsk.js +45 -0
  41. package/dist/esm/core/i18n.js +8 -4
  42. package/dist/esm/core/threeCompat.js +30 -0
  43. package/dist/esm/dance/DanceScoreEngine.js +7 -0
  44. package/dist/esm/dance/PoseDetector.js +118 -65
  45. package/dist/esm/index.js +3 -3
  46. package/dist/esm/material/NiceMaterialEditor.js +7 -3
  47. package/dist/esm/material/materialEditorTypes.js +1 -1
  48. package/dist/esm/material/materialNodeDefinitions.js +14 -11
  49. package/dist/esm/model/ModelEditor.js +4 -5
  50. package/dist/esm/model/ModelEditorLeftPanel.js +10 -4
  51. package/dist/esm/model/ModelEditorMenuBar.js +7 -5
  52. package/dist/esm/model/ModelEditorRightPanel.js +18 -16
  53. package/dist/esm/model/ModelEditorSubComponents.js +8 -6
  54. package/dist/esm/model/ModelEditorTimeline.js +6 -4
  55. package/dist/esm/model/ModelEditorToolbar.js +6 -4
  56. package/dist/esm/model/ModelEditorViewport.js +16 -6
  57. package/dist/esm/model/ModelViewer.js +7 -7
  58. package/dist/esm/model/NiceArmatureEditor.js +9 -7
  59. package/dist/esm/model/NiceMorphTargetEditor.js +5 -3
  60. package/dist/esm/model/NiceOctree.js +3 -1
  61. package/dist/esm/model/NicePhysicsSimulation.js +7 -5
  62. package/dist/esm/model/NiceProceduralGeometry.js +5 -3
  63. package/dist/esm/model/NiceTerrainEditor.js +10 -8
  64. package/dist/esm/model/NiceWeightPainter.js +6 -4
  65. package/dist/esm/model/NiceXRPreview.js +7 -5
  66. package/dist/esm/model/useModelEditor.js +30 -18
  67. package/dist/esm/model/useModelViewer.js +13 -9
  68. package/dist/esm/nice2dev-ui-3d.css +1 -1
  69. package/dist/esm/particle/NiceParticleEditor.js +14 -9
  70. package/dist/esm/rendering/NiceCascadedShadows.js +3 -1
  71. package/dist/esm/rendering/NiceRenderExport.js +11 -5
  72. package/dist/esm/rendering/NiceSSAO.js +3 -1
  73. package/dist/esm/rendering/NiceSSR.js +3 -1
  74. package/dist/esm/rendering/NiceWebGPURenderer.js +7 -4
  75. package/dist/esm/uv/NiceUVEditor.js +16 -12
  76. package/dist/esm/uv/uvEditorTypes.js +1 -0
  77. package/dist/types/core/editorAsk.d.ts +26 -0
  78. package/dist/types/core/i18n.d.ts +1 -17
  79. package/dist/types/core/threeCompat.d.ts +14 -0
  80. package/dist/types/dance/DanceScoreEngine.d.ts +0 -6
  81. package/dist/types/dance/PoseDetector.d.ts +14 -13
  82. package/dist/types/index.d.ts +4 -4
  83. package/dist/types/model/ModelEditor.d.ts +0 -4
  84. package/dist/types/model/ModelViewer.d.ts +0 -4
  85. package/dist/types/model/useModelEditor.d.ts +3 -0
  86. package/package.json +3 -3
@@ -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,19 +283,20 @@ 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
- color: 'var(--nice-text-on-primary, #fff)',
294
+ color: 'var(--nice-color-white, #fff)',
293
295
  border: 'none',
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,43 +352,43 @@ 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',
364
366
  borderRadius: 4,
365
367
  border: '1px solid var(--nice-border, #e2e8f0)',
366
368
  background: brush.mode === m ? 'var(--nice-color-accent, #6366f1)' : 'transparent',
367
- color: brush.mode === m ? 'var(--nice-text-on-primary, #fff)' : 'inherit',
369
+ color: brush.mode === m ? 'var(--nice-color-white, #fff)' : 'inherit',
368
370
  }, children: m }, m)))] }), jsxRuntime.jsxs("label", { children: ["Radius: ", brush.radius.toFixed(1), jsxRuntime.jsx("input", { type: "range", min: 0.5, max: 20, step: 0.5, value: brush.radius, onChange: (e) => setBrush({ radius: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Strength: ", brush.strength.toFixed(2), jsxRuntime.jsx("input", { type: "range", min: 0.01, max: 1, step: 0.01, value: brush.strength, onChange: (e) => setBrush({ strength: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsx("button", { type: "button", onClick: isPainting ? stopPainting : startPainting, style: {
369
371
  background: isPainting
370
372
  ? 'var(--nice-danger, #ef4444)'
371
373
  : 'var(--nice-color-accent, #6366f1)',
372
- color: 'var(--nice-text-on-primary, #fff)',
374
+ color: 'var(--nice-color-white, #fff)',
373
375
  border: 'none',
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,11 +258,11 @@ 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',
263
- color: brush.mode === mode ? 'var(--nice-text-on-primary, #fff)' : 'inherit',
265
+ color: brush.mode === mode ? 'var(--nice-color-white, #fff)' : 'inherit',
264
266
  border: '1px solid var(--nice-border, #e2e8f0)',
265
267
  borderRadius: 4,
266
268
  cursor: 'pointer',
@@ -270,12 +272,12 @@ const NiceWeightPainter = ({ skinnedMesh, activeBoneIndex: initialBoneIndex, sce
270
272
  background: isPainting
271
273
  ? 'var(--nice-danger, #ef4444)'
272
274
  : 'var(--nice-color-accent, #6366f1)',
273
- color: 'var(--nice-text-on-primary, #fff)',
275
+ color: 'var(--nice-color-white, #fff)',
274
276
  border: 'none',
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,19 +281,20 @@ 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,
293
295
  cursor: 'pointer',
294
296
  background: mode === 'vr' ? 'var(--nice-color-accent, #6366f1)' : 'transparent',
295
- color: mode === 'vr' ? 'var(--nice-text-on-primary, #fff)' : 'inherit',
297
+ color: mode === 'vr' ? 'var(--nice-color-white, #fff)' : 'inherit',
296
298
  border: '1px solid var(--nice-border, #e2e8f0)',
297
299
  }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "eye", size: 14 }), " VR"] }), jsxRuntime.jsxs("button", { type: "button", onClick: () => setMode('ar'), style: {
298
300
  flex: 1,
@@ -300,18 +302,18 @@ const NiceXRPreview = ({ renderer, scene, camera, mode: initialMode, onSessionSt
300
302
  borderRadius: 4,
301
303
  cursor: 'pointer',
302
304
  background: mode === 'ar' ? 'var(--nice-color-accent, #6366f1)' : 'transparent',
303
- color: mode === 'ar' ? 'var(--nice-text-on-primary, #fff)' : 'inherit',
305
+ color: mode === 'ar' ? 'var(--nice-color-white, #fff)' : 'inherit',
304
306
  border: '1px solid var(--nice-border, #e2e8f0)',
305
307
  }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "window", size: 14 }), " AR"] })] }), jsxRuntime.jsx("button", { type: "button", onClick: isSessionActive ? endSession : startSession, style: {
306
308
  background: isSessionActive
307
309
  ? 'var(--nice-danger, #ef4444)'
308
310
  : 'var(--nice-success, #22c55e)',
309
- color: 'var(--nice-text-on-primary, #fff)',
311
+ color: 'var(--nice-color-white, #fff)',
310
312
  border: 'none',
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,8 @@ 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');
27
+ var threeCompat = require('../core/threeCompat.js');
26
28
  var modelEditorTypes = require('./modelEditorTypes.js');
27
29
  var modelEditorUtils = require('./modelEditorUtils.js');
28
30
 
@@ -52,7 +54,7 @@ var THREE__namespace = /*#__PURE__*/_interopNamespaceDefault(THREE);
52
54
  *
53
55
  * Extracted from the original ModelEditor.tsx god-component.
54
56
  */
55
- const log = { error: console.error, warn: console.warn, info: console.info };
57
+ const log = { error: console.error, warn: console.warn };
56
58
  /* helpers */
57
59
  let _nid = 0;
58
60
  const nid = () => `n${++_nid}`;
@@ -73,6 +75,8 @@ function findNodeById(nodes, id) {
73
75
  Hook
74
76
  ═══════════════════════════════════════════ */
75
77
  function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
78
+ var _a;
79
+ const { pendingAsk, ask } = editorAsk.useEditorAsk();
76
80
  /* ── refs ── */
77
81
  const mountRef = React.useRef(null);
78
82
  const rendererRef = React.useRef(null);
@@ -80,7 +84,9 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
80
84
  const cameraRef = React.useRef(new THREE__namespace.PerspectiveCamera(50, 1, 0.01, 10000));
81
85
  const orbitRef = React.useRef(null);
82
86
  const transformRef = React.useRef(null);
83
- const clockRef = React.useRef(new THREE__namespace.Clock());
87
+ // Created once: useRef's argument runs on every render, and three r183 warns per Clock.
88
+ const clockRef = React.useRef(null);
89
+ (_a = clockRef.current) !== null && _a !== void 0 ? _a : (clockRef.current = threeCompat.createFrameClock());
84
90
  const mixerRef = React.useRef(null);
85
91
  const rafRef = React.useRef(0);
86
92
  const fileInputRef = React.useRef(null);
@@ -160,7 +166,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
160
166
  const renderer = new THREE__namespace.WebGLRenderer({ antialias: true, alpha: false });
161
167
  renderer.setPixelRatio(window.devicePixelRatio);
162
168
  renderer.shadowMap.enabled = true;
163
- renderer.shadowMap.type = THREE__namespace.PCFSoftShadowMap;
169
+ renderer.shadowMap.type = threeCompat.SOFT_SHADOW_MAP;
164
170
  renderer.toneMapping = THREE__namespace.ACESFilmicToneMapping;
165
171
  renderer.toneMappingExposure = 1.2;
166
172
  renderer.outputColorSpace = THREE__namespace.SRGBColorSpace;
@@ -221,10 +227,9 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
221
227
  // goes into the scene is its helper.
222
228
  const tcHelper = tc.getHelper();
223
229
  tcHelper.name = '__transformControls';
224
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
225
- tc.addEventListener('dragging-changed', ((e) => {
230
+ tc.addEventListener('dragging-changed', (e) => {
226
231
  orbit.enabled = !e.value;
227
- }));
232
+ });
228
233
  scene.add(tcHelper);
229
234
  transformRef.current = tc;
230
235
  // Resize handler
@@ -243,8 +248,9 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
243
248
  onResize();
244
249
  // Animation loop
245
250
  const animate = () => {
251
+ var _a, _b;
246
252
  rafRef.current = requestAnimationFrame(animate);
247
- const dt = clockRef.current.getDelta();
253
+ const dt = (_b = (_a = clockRef.current) === null || _a === void 0 ? void 0 : _a.getDelta()) !== null && _b !== void 0 ? _b : 0;
248
254
  orbit.update();
249
255
  // Update mixer
250
256
  if (mixerRef.current && isPlayingRef.current) {
@@ -1242,11 +1248,8 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
1242
1248
  }
1243
1249
  });
1244
1250
  }, []);
1245
- const clearScene = React.useCallback(() => {
1251
+ const clearSceneNow = React.useCallback(() => {
1246
1252
  var _a;
1247
- if (!window.confirm('Clear the entire scene? This cannot be undone.')) {
1248
- return;
1249
- }
1250
1253
  const scene = sceneRef.current;
1251
1254
  const toRemove = [];
1252
1255
  scene.traverse((obj) => {
@@ -1278,6 +1281,14 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
1278
1281
  rebuildTree();
1279
1282
  setStatusText('Scene cleared');
1280
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]);
1281
1292
  /* ─────────────────────────────────────────
1282
1293
  Save to Library
1283
1294
  ───────────────────────────────────────── */
@@ -1295,19 +1306,19 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
1295
1306
  const blob = new Blob([result], { type: 'model/gltf-binary' });
1296
1307
  const reader = new FileReader();
1297
1308
  reader.onload = () => {
1298
- const defaultName = 'model.glb';
1299
- const name = window.prompt('Save to library as:', defaultName);
1300
- if (!name || !name.trim()) {
1301
- return;
1302
- }
1303
- 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
+ });
1304
1315
  };
1305
1316
  reader.readAsDataURL(blob);
1306
1317
  }, (err) => {
1307
1318
  log.error('Save to library error:', err);
1308
1319
  setStatusText(`Save error: ${String(err)}`);
1309
1320
  }, { binary: true, animations: allClipsRef.current.map((a) => a.clip) });
1310
- }, [onSaveToLibrary]);
1321
+ }, [onSaveToLibrary, ask]);
1311
1322
  /* ─────────────────────────────────────────
1312
1323
  Add Mesh / Light / Camera / Empty
1313
1324
  ───────────────────────────────────────── */
@@ -1803,6 +1814,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
1803
1814
  bgColor,
1804
1815
  setBgColor,
1805
1816
  statusText,
1817
+ pendingAsk,
1806
1818
  dragOver,
1807
1819
  polyCount,
1808
1820
  meshCount,
@@ -10,6 +10,7 @@ var GLTFLoader_js = require('three/examples/jsm/loaders/GLTFLoader.js');
10
10
  var OBJLoader_js = require('three/examples/jsm/loaders/OBJLoader.js');
11
11
  var PLYLoader_js = require('three/examples/jsm/loaders/PLYLoader.js');
12
12
  var STLLoader_js = require('three/examples/jsm/loaders/STLLoader.js');
13
+ var threeCompat = require('../core/threeCompat.js');
13
14
 
14
15
  function _interopNamespaceDefault(e) {
15
16
  var n = Object.create(null);
@@ -60,6 +61,7 @@ const TONE_MAP = {
60
61
  Hook
61
62
  ═══════════════════════════════════════════ */
62
63
  function useModelViewer(props = {}) {
64
+ var _a;
63
65
  const { src, format, navigationMode: initNavMode = 'orbit', quality = 'high', autoRotateSpeed: initAutoRotate = 0, backgroundColor = 'var(--nice-bg, #1a1a2e)', showGrid: initShowGrid = true, shadows = true, gamepad = false, cameraPresets: _cameraPresets = [], annotations: _annotations = [], hotspots: _hotspots = [], floorPlans: _floorPlans = [], onHotspotClick: _onHotspotClick, onAnnotationClick: _onAnnotationClick, onLoadProgress, onLoad, onError, clippingPlanes = [], measureMode: initMeasureMode = false, measurements: _measurements = [], onMeasure: _onMeasure, minDistance = 0.1, maxDistance = 1000, fov = 50, vr: _vr = false, walkSpeed = 5, ambientIntensity = 0.5, directionalIntensity = 0.8, antiAlias = true, toneMapping = 'aces', exposure = 1, } = props;
64
66
  /* ── refs ── */
65
67
  const mountRef = React.useRef(null);
@@ -67,7 +69,9 @@ function useModelViewer(props = {}) {
67
69
  const sceneRef = React.useRef(new THREE__namespace.Scene());
68
70
  const cameraRef = React.useRef(new THREE__namespace.PerspectiveCamera(fov, 1, 0.01, 10000));
69
71
  const orbitRef = React.useRef(null);
70
- const clockRef = React.useRef(new THREE__namespace.Clock());
72
+ // Created once: useRef's argument runs on every render, and three r183 warns per Clock.
73
+ const clockRef = React.useRef(null);
74
+ (_a = clockRef.current) !== null && _a !== void 0 ? _a : (clockRef.current = threeCompat.createFrameClock());
71
75
  const mixerRef = React.useRef(null);
72
76
  const rafRef = React.useRef(0);
73
77
  const rootRef = React.useRef(null);
@@ -123,7 +127,7 @@ function useModelViewer(props = {}) {
123
127
  renderer.outputColorSpace = THREE__namespace.SRGBColorSpace;
124
128
  if (shadows) {
125
129
  renderer.shadowMap.enabled = true;
126
- renderer.shadowMap.type = THREE__namespace.PCFSoftShadowMap;
130
+ renderer.shadowMap.type = threeCompat.SOFT_SHADOW_MAP;
127
131
  }
128
132
  mount.appendChild(renderer.domElement);
129
133
  rendererRef.current = renderer;
@@ -177,20 +181,20 @@ function useModelViewer(props = {}) {
177
181
  ro.observe(mount);
178
182
  // Animation loop
179
183
  const animate = () => {
180
- var _a, _b, _c, _d, _e;
184
+ var _a, _b, _c, _d, _e, _f, _g;
181
185
  if (disposeRef.current) {
182
186
  return;
183
187
  }
184
188
  rafRef.current = requestAnimationFrame(animate);
185
- const dt = clockRef.current.getDelta();
189
+ const dt = (_b = (_a = clockRef.current) === null || _a === void 0 ? void 0 : _a.getDelta()) !== null && _b !== void 0 ? _b : 0;
186
190
  // Gamepad FPS-style nav
187
191
  if (gamepad && navMode === 'fps') {
188
- const pads = (_a = navigator.getGamepads) === null || _a === void 0 ? void 0 : _a.call(navigator);
189
- const gp = (_b = pads === null || pads === void 0 ? void 0 : pads[0]) !== null && _b !== void 0 ? _b : null;
192
+ const pads = (_c = navigator.getGamepads) === null || _c === void 0 ? void 0 : _c.call(navigator);
193
+ const gp = (_d = pads === null || pads === void 0 ? void 0 : pads[0]) !== null && _d !== void 0 ? _d : null;
190
194
  if (gp) {
191
- const lx = (_c = gp.axes[0]) !== null && _c !== void 0 ? _c : 0;
192
- const ly = (_d = gp.axes[1]) !== null && _d !== void 0 ? _d : 0;
193
- const rx = (_e = gp.axes[2]) !== null && _e !== void 0 ? _e : 0;
195
+ const lx = (_e = gp.axes[0]) !== null && _e !== void 0 ? _e : 0;
196
+ const ly = (_f = gp.axes[1]) !== null && _f !== void 0 ? _f : 0;
197
+ const rx = (_g = gp.axes[2]) !== null && _g !== void 0 ? _g : 0;
194
198
  const fwd = new THREE__namespace.Vector3();
195
199
  camera.getWorldDirection(fwd);
196
200
  fwd.y = 0;