@nice2dev/ui-3d 1.3.14 → 1.3.16

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 (108) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/cjs/collaborative/collaborativeScene.js +160 -206
  3. package/dist/cjs/core/editorAsk.js +51 -41
  4. package/dist/cjs/core/i18n.js +13 -14
  5. package/dist/cjs/core/threeCompat.js +29 -42
  6. package/dist/cjs/dance/DanceBridge.js +148 -177
  7. package/dist/cjs/dance/DanceScoreEngine.js +209 -221
  8. package/dist/cjs/dance/PoseDetector.js +302 -275
  9. package/dist/cjs/index.js +110 -113
  10. package/dist/cjs/material/MaterialEditor.module.css.js +242 -5
  11. package/dist/cjs/material/NiceMaterialEditor.js +1213 -757
  12. package/dist/cjs/material/materialEditorTypes.js +35 -49
  13. package/dist/cjs/material/materialEditorUtils.js +684 -747
  14. package/dist/cjs/material/materialNodeDefinitions.js +1411 -1459
  15. package/dist/cjs/model/ModelEditor.js +90 -40
  16. package/dist/cjs/model/ModelEditor.module.css.js +257 -5
  17. package/dist/cjs/model/ModelEditorLeftPanel.js +126 -47
  18. package/dist/cjs/model/ModelEditorMenuBar.js +359 -32
  19. package/dist/cjs/model/ModelEditorRightPanel.js +647 -131
  20. package/dist/cjs/model/ModelEditorSubComponents.js +277 -112
  21. package/dist/cjs/model/ModelEditorTimeline.js +232 -34
  22. package/dist/cjs/model/ModelEditorToolbar.js +170 -14
  23. package/dist/cjs/model/ModelEditorViewport.js +231 -39
  24. package/dist/cjs/model/ModelViewer.js +388 -63
  25. package/dist/cjs/model/ModelViewer.module.css.js +77 -5
  26. package/dist/cjs/model/NiceArmatureEditor.js +473 -274
  27. package/dist/cjs/model/NiceMorphTargetEditor.js +333 -227
  28. package/dist/cjs/model/NiceOctree.js +372 -320
  29. package/dist/cjs/model/NicePhysicsSimulation.js +528 -298
  30. package/dist/cjs/model/NiceProceduralGeometry.js +673 -300
  31. package/dist/cjs/model/NiceTerrainEditor.js +595 -385
  32. package/dist/cjs/model/NiceWeightPainter.js +384 -261
  33. package/dist/cjs/model/NiceXRPreview.js +385 -308
  34. package/dist/cjs/model/cadModeUtils.js +103 -106
  35. package/dist/cjs/model/editorShortcuts.js +171 -194
  36. package/dist/cjs/model/modelEditorTypes.js +100 -118
  37. package/dist/cjs/model/modelEditorUtils.js +1061 -1146
  38. package/dist/cjs/model/simsModeUtils.js +1023 -1053
  39. package/dist/cjs/model/useModelEditor.js +1977 -1933
  40. package/dist/cjs/model/useModelViewer.js +633 -665
  41. package/dist/cjs/nice2dev-ui-3d.css +1 -1
  42. package/dist/cjs/particle/NiceParticleEditor.js +1186 -571
  43. package/dist/cjs/particle/ParticleEditor.module.css.js +122 -5
  44. package/dist/cjs/particle/particleEditorTypes.js +62 -70
  45. package/dist/cjs/particle/particleEditorUtils.js +998 -1013
  46. package/dist/cjs/rendering/NiceCascadedShadows.js +381 -259
  47. package/dist/cjs/rendering/NiceRenderExport.js +424 -337
  48. package/dist/cjs/rendering/NiceSSAO.js +454 -337
  49. package/dist/cjs/rendering/NiceSSR.js +387 -261
  50. package/dist/cjs/rendering/NiceWebGPURenderer.js +359 -230
  51. package/dist/cjs/uv/NiceUVEditor.js +787 -550
  52. package/dist/cjs/uv/UVEditor.module.css.js +53 -5
  53. package/dist/cjs/uv/uvEditorTypes.js +59 -76
  54. package/dist/cjs/uv/uvEditorUtils.js +501 -575
  55. package/dist/esm/collaborative/collaborativeScene.js +163 -205
  56. package/dist/esm/core/editorAsk.js +53 -40
  57. package/dist/esm/core/i18n.js +15 -13
  58. package/dist/esm/core/threeCompat.js +18 -26
  59. package/dist/esm/dance/DanceBridge.js +151 -176
  60. package/dist/esm/dance/DanceScoreEngine.js +211 -220
  61. package/dist/esm/dance/PoseDetector.js +304 -274
  62. package/dist/esm/index.js +258 -36
  63. package/dist/esm/material/MaterialEditor.module.css.js +242 -3
  64. package/dist/esm/material/NiceMaterialEditor.js +1215 -756
  65. package/dist/esm/material/materialEditorTypes.js +45 -48
  66. package/dist/esm/material/materialEditorUtils.js +713 -746
  67. package/dist/esm/material/materialNodeDefinitions.js +1415 -1458
  68. package/dist/esm/model/ModelEditor.js +91 -39
  69. package/dist/esm/model/ModelEditor.module.css.js +257 -3
  70. package/dist/esm/model/ModelEditorLeftPanel.js +126 -45
  71. package/dist/esm/model/ModelEditorMenuBar.js +359 -30
  72. package/dist/esm/model/ModelEditorRightPanel.js +634 -114
  73. package/dist/esm/model/ModelEditorSubComponents.js +265 -94
  74. package/dist/esm/model/ModelEditorTimeline.js +232 -32
  75. package/dist/esm/model/ModelEditorToolbar.js +170 -12
  76. package/dist/esm/model/ModelEditorViewport.js +231 -37
  77. package/dist/esm/model/ModelViewer.js +389 -62
  78. package/dist/esm/model/ModelViewer.module.css.js +77 -3
  79. package/dist/esm/model/NiceArmatureEditor.js +462 -258
  80. package/dist/esm/model/NiceMorphTargetEditor.js +322 -211
  81. package/dist/esm/model/NiceOctree.js +361 -304
  82. package/dist/esm/model/NicePhysicsSimulation.js +517 -282
  83. package/dist/esm/model/NiceProceduralGeometry.js +667 -284
  84. package/dist/esm/model/NiceTerrainEditor.js +584 -369
  85. package/dist/esm/model/NiceWeightPainter.js +373 -245
  86. package/dist/esm/model/NiceXRPreview.js +374 -292
  87. package/dist/esm/model/cadModeUtils.js +95 -88
  88. package/dist/esm/model/editorShortcuts.js +172 -193
  89. package/dist/esm/model/modelEditorTypes.js +90 -100
  90. package/dist/esm/model/modelEditorUtils.js +1056 -1128
  91. package/dist/esm/model/simsModeUtils.js +1021 -1035
  92. package/dist/esm/model/useModelEditor.js +1946 -1920
  93. package/dist/esm/model/useModelViewer.js +619 -647
  94. package/dist/esm/nice2dev-ui-3d.css +1 -1
  95. package/dist/esm/particle/NiceParticleEditor.js +1188 -570
  96. package/dist/esm/particle/ParticleEditor.module.css.js +122 -3
  97. package/dist/esm/particle/particleEditorTypes.js +69 -69
  98. package/dist/esm/particle/particleEditorUtils.js +1027 -1012
  99. package/dist/esm/rendering/NiceCascadedShadows.js +370 -243
  100. package/dist/esm/rendering/NiceRenderExport.js +413 -321
  101. package/dist/esm/rendering/NiceSSAO.js +450 -328
  102. package/dist/esm/rendering/NiceSSR.js +383 -252
  103. package/dist/esm/rendering/NiceWebGPURenderer.js +326 -214
  104. package/dist/esm/uv/NiceUVEditor.js +788 -549
  105. package/dist/esm/uv/UVEditor.module.css.js +53 -3
  106. package/dist/esm/uv/uvEditorTypes.js +68 -75
  107. package/dist/esm/uv/uvEditorUtils.js +515 -557
  108. package/package.json +7 -12
@@ -1,66 +1,393 @@
1
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
- import { NiceButton, NiceIcon, NiceSelect, NiceSlider } from '@nice2dev/ui';
3
- import { useState, useRef, useCallback } from 'react';
4
- import { useNiceTranslation } from '../core/i18n.js';
5
- import styles from './ModelViewer.module.css.js';
6
- import { useModelViewer } from './useModelViewer.js';
7
-
8
- const NAV_MODES = ['orbit', 'fps', 'fly', 'walkthrough'];
1
+ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
+ import { NiceButton, NiceIcon, NiceSelect, NiceSlider } from "@nice2dev/ui";
3
+ import { useState, useRef, useCallback } from "react";
4
+ import { useNiceTranslation } from "../core/i18n.js";
5
+ import styles from "./ModelViewer.module.css.js";
6
+ import { useModelViewer } from "./useModelViewer.js";
7
+ const NAV_MODES = ["orbit", "fps", "fly", "walkthrough"];
9
8
  const NAV_META = {
10
- orbit: { icon: 'globe', label: 'Orbit' },
11
- fps: { icon: 'user', label: 'FPS' },
12
- fly: { icon: 'rocket', label: 'Fly' },
13
- walkthrough: { icon: 'home', label: 'Walk' },
9
+ orbit: { icon: "globe", label: "Orbit" },
10
+ fps: { icon: "user", label: "FPS" },
11
+ fly: { icon: "rocket", label: "Fly" },
12
+ walkthrough: { icon: "home", label: "Walk" }
14
13
  };
15
- const NavLabel = ({ mode }) => (jsxs(Fragment, { children: [jsx(NiceIcon, { name: NAV_META[mode].icon, size: 14 }), " ", NAV_META[mode].label] }));
16
- const NiceModelViewer = ({ className, showToolbar = true, showStatusBar = true, showInfoPanel = true, toolbarExtra, onFileDrop, children, ...viewerProps }) => {
17
- const { t } = useNiceTranslation();
18
- const api = useModelViewer(viewerProps);
19
- const [panelOpen, setPanelOpen] = useState(false);
20
- const [dragOver, setDragOver] = useState(false);
21
- const fileInputRef = useRef(null);
22
- /* ── drag & drop ── */
23
- const handleDragOver = useCallback((e) => {
24
- e.preventDefault();
25
- setDragOver(true);
26
- }, []);
27
- const handleDragLeave = useCallback((e) => {
28
- e.preventDefault();
29
- setDragOver(false);
30
- }, []);
31
- const handleDrop = useCallback((e) => {
32
- var _a;
33
- e.preventDefault();
34
- setDragOver(false);
35
- const file = (_a = e.dataTransfer.files) === null || _a === void 0 ? void 0 : _a[0];
36
- if (file) {
37
- onFileDrop === null || onFileDrop === void 0 ? void 0 : onFileDrop(file);
38
- api.loadFile(file);
14
+ const NavLabel = ({ mode }) => /* @__PURE__ */ jsxs(Fragment, { children: [
15
+ /* @__PURE__ */ jsx(NiceIcon, { name: NAV_META[mode].icon, size: 14 }),
16
+ " ",
17
+ NAV_META[mode].label
18
+ ] });
19
+ const NiceModelViewer = ({
20
+ className,
21
+ showToolbar = true,
22
+ showStatusBar = true,
23
+ showInfoPanel = true,
24
+ toolbarExtra,
25
+ onFileDrop,
26
+ children,
27
+ ...viewerProps
28
+ }) => {
29
+ const { t } = useNiceTranslation();
30
+ const api = useModelViewer(viewerProps);
31
+ const [panelOpen, setPanelOpen] = useState(false);
32
+ const [dragOver, setDragOver] = useState(false);
33
+ const fileInputRef = useRef(null);
34
+ const handleDragOver = useCallback((e) => {
35
+ e.preventDefault();
36
+ setDragOver(true);
37
+ }, []);
38
+ const handleDragLeave = useCallback((e) => {
39
+ e.preventDefault();
40
+ setDragOver(false);
41
+ }, []);
42
+ const handleDrop = useCallback(
43
+ (e) => {
44
+ e.preventDefault();
45
+ setDragOver(false);
46
+ const file = e.dataTransfer.files?.[0];
47
+ if (file) {
48
+ onFileDrop?.(file);
49
+ api.loadFile(file);
50
+ }
51
+ },
52
+ [api, onFileDrop]
53
+ );
54
+ const cycleNav = useCallback(() => {
55
+ const idx = NAV_MODES.indexOf(api.navigationMode);
56
+ api.setNavigationMode(NAV_MODES[(idx + 1) % NAV_MODES.length]);
57
+ }, [api]);
58
+ return /* @__PURE__ */ jsxs("div", { className: `${styles.root} ${className ?? ""}`, children: [
59
+ showToolbar && /* @__PURE__ */ jsxs("div", { className: styles.toolbar, children: [
60
+ /* @__PURE__ */ jsxs(
61
+ NiceButton,
62
+ {
63
+ className: styles.toolBtn,
64
+ variant: "ghost",
65
+ size: "sm",
66
+ title: t("modelViewer.openFile", "Open file…"),
67
+ onClick: () => fileInputRef.current?.click(),
68
+ children: [
69
+ /* @__PURE__ */ jsx(NiceIcon, { name: "folder", size: 14 }),
70
+ " ",
71
+ t("modelViewer.open", "Open")
72
+ ]
39
73
  }
40
- }, [api, onFileDrop]);
41
- /* ── nav cycle ── */
42
- const cycleNav = useCallback(() => {
43
- const idx = NAV_MODES.indexOf(api.navigationMode);
44
- api.setNavigationMode(NAV_MODES[(idx + 1) % NAV_MODES.length]);
45
- }, [api]);
46
- return (jsxs("div", { className: `${styles.root} ${className !== null && className !== void 0 ? className : ''}`, children: [showToolbar && (jsxs("div", { className: styles.toolbar, children: [jsxs(NiceButton, { className: styles.toolBtn, variant: "ghost", size: "sm", title: t('modelViewer.openFile', 'Open file…'), onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: [jsx(NiceIcon, { name: "folder", size: 14 }), " ", t('modelViewer.open', 'Open')] }), jsx("input", { ref: fileInputRef, type: "file", accept: ".fbx,.glb,.gltf,.obj,.dae,.stl,.ply", style: { display: 'none' }, onChange: (e) => {
47
- var _a;
48
- const f = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
49
- if (f) {
50
- api.loadFile(f);
51
- }
52
- e.target.value = '';
53
- } }), jsx("div", { className: styles.toolSep }), jsx(NiceButton, { className: styles.toolBtn, variant: "ghost", size: "sm", title: t('modelViewer.navigationMode', 'Navigation mode'), onClick: cycleNav, children: jsx(NavLabel, { mode: api.navigationMode }) }), jsx(NiceButton, { className: styles.toolBtn, variant: api.wireframe ? 'primary' : 'ghost', size: "sm", onClick: () => api.setWireframe(!api.wireframe), title: t('modelViewer.wireframe', 'Wireframe'), "aria-label": t('modelViewer.wireframe', 'Wireframe'), children: jsx(NiceIcon, { name: "square", size: 16 }) }), jsx(NiceButton, { className: styles.toolBtn, variant: api.showGrid ? 'primary' : 'ghost', size: "sm", onClick: () => api.setShowGrid(!api.showGrid), title: t('modelViewer.grid', 'Grid'), "aria-label": t('modelViewer.grid', 'Grid'), children: jsx(NiceIcon, { name: "grid", size: 16 }) }), jsx(NiceButton, { className: styles.toolBtn, variant: api.clippingEnabled ? 'primary' : 'ghost', size: "sm", onClick: () => api.setClippingEnabled(!api.clippingEnabled), title: t('modelViewer.clippingPlane', 'Clipping plane'), "aria-label": t('modelViewer.clippingPlane', 'Clipping plane'), children: jsx(NiceIcon, { name: "scissors", size: 16 }) }), api.clippingEnabled && (jsxs(Fragment, { children: [jsx(NiceSelect, { className: styles.animSelect, value: api.clippingAxis, onChange: (val) => api.setClippingAxis(val), options: [
54
- { value: 'x', label: 'X' },
55
- { value: 'y', label: 'Y' },
56
- { value: 'z', label: 'Z' },
57
- ] }), jsx(NiceSlider, { min: -10, max: 10, step: 0.1, value: api.clippingPosition, onChange: (val) => api.setClippingPosition(val), style: { width: 60 } })] })), jsx("div", { className: styles.toolSep }), jsx(NiceButton, { className: styles.toolBtn, variant: "ghost", size: "sm", onClick: api.zoomToFit, title: t('modelViewer.zoomToFit', 'Zoom to fit'), "aria-label": t('modelViewer.zoomToFit', 'Zoom to fit'), children: jsx(NiceIcon, { name: "search", size: 16 }) }), jsx(NiceButton, { className: styles.toolBtn, variant: "ghost", size: "sm", onClick: api.resetCamera, title: t('modelViewer.resetCamera', 'Reset camera'), "aria-label": t('modelViewer.resetCamera', 'Reset camera'), children: jsx(NiceIcon, { name: "home", size: 16 }) }), jsx("div", { className: styles.toolSep }), jsx(NiceButton, { className: styles.toolBtn, variant: "ghost", size: "sm", onClick: () => {
58
- const url = api.screenshot();
59
- const a = document.createElement('a');
60
- a.href = url;
61
- a.download = 'screenshot.png';
62
- a.click();
63
- }, title: t('modelViewer.screenshot', 'Screenshot'), "aria-label": t('modelViewer.screenshot', 'Screenshot'), children: jsx(NiceIcon, { name: "camera", size: 16 }) }), jsx(NiceButton, { className: styles.toolBtn, variant: "ghost", size: "sm", onClick: api.toggleFullscreen, title: t('modelViewer.fullscreen', 'Fullscreen'), "aria-label": t('modelViewer.fullscreen', 'Fullscreen'), children: jsx(NiceIcon, { name: api.isFullscreen ? 'collapse' : 'expand', size: 16 }) }), toolbarExtra, showInfoPanel && (jsx("div", { className: styles.toolRight, children: jsx(NiceButton, { className: `${styles.toolBtn} ${panelOpen ? styles.active : ''}`, variant: panelOpen ? 'primary' : 'ghost', size: "sm", onClick: () => setPanelOpen(!panelOpen), title: t('modelViewer.infoPanel', 'Info panel'), "aria-label": t('modelViewer.infoPanel', 'Info panel'), children: jsx(NiceIcon, { name: "info", size: 16 }) }) }))] })), jsxs("div", { className: styles.viewport, ref: api.mountRef, tabIndex: 0, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, children: [api.loading && (jsxs("div", { className: styles.loadingOverlay, children: [jsx("span", { className: styles.loadingText, children: t('modelViewer.loadingModel', 'Loading model…') }), jsx("div", { className: styles.loadingBar, children: jsx("div", { className: styles.loadingBarFill, style: { width: `${(api.loadProgress * 100).toFixed(0)}%` } }) })] })), api.error && (jsxs("div", { className: styles.errorOverlay, children: [jsx(NiceIcon, { name: "warning", size: 28 }), jsx("span", { style: { marginTop: 8 }, children: api.error })] })), dragOver && (jsx("div", { className: styles.dropZone, children: t('modelViewer.drop3dModelFileHere', 'Drop 3D model file here') }))] }), jsxs("div", { className: `${styles.sidePanel} ${!panelOpen ? styles.hidden : ''}`, children: [jsxs("div", { className: styles.panelSection, children: [jsx("div", { className: styles.panelTitle, children: t('modelViewer.modelInfo', 'Model Info') }), jsxs("div", { className: styles.panelRow, children: [jsx("label", { children: t('modelViewer.triangles', 'Triangles') }), jsx("span", { children: api.polyCount.toLocaleString() })] }), jsxs("div", { className: styles.panelRow, children: [jsx("label", { children: t('modelViewer.meshes', 'Meshes') }), jsx("span", { children: api.meshCount })] }), api.dimensions && (jsxs(Fragment, { children: [jsxs("div", { className: styles.panelRow, children: [jsx("label", { children: t('modelViewer.width', 'Width') }), jsx("span", { children: api.dimensions.width })] }), jsxs("div", { className: styles.panelRow, children: [jsx("label", { children: t('modelViewer.height', 'Height') }), jsx("span", { children: api.dimensions.height })] }), jsxs("div", { className: styles.panelRow, children: [jsx("label", { children: t('modelViewer.depth', 'Depth') }), jsx("span", { children: api.dimensions.depth })] })] }))] }), jsxs("div", { className: styles.panelSection, children: [jsx("div", { className: styles.panelTitle, children: t('modelViewer.navigation', 'Navigation') }), NAV_MODES.map((m) => (jsx(NiceButton, { className: styles.toolBtn, variant: api.navigationMode === m ? 'primary' : 'ghost', size: "sm", onClick: () => api.setNavigationMode(m), children: jsx(NavLabel, { mode: m }) }, m)))] }), jsxs("div", { className: styles.panelSection, children: [jsx("div", { className: styles.panelTitle, children: t('modelViewer.autoRotate', 'Auto Rotate') }), jsx(NiceSlider, { min: 0, max: 10, step: 0.5, value: api.autoRotateSpeed, onChange: (val) => api.setAutoRotateSpeed(val), style: { width: '100%' } })] }), api.animations.length > 0 && (jsxs("div", { className: styles.panelSection, children: [jsx("div", { className: styles.panelTitle, children: t('modelViewer.animations', 'Animations') }), api.animations.map((name, i) => (jsxs(NiceButton, { className: styles.toolBtn, variant: api.activeAnimIdx === i ? 'primary' : 'ghost', size: "sm", onClick: () => api.playAnimation(i), children: [jsx(NiceIcon, { name: "play", size: 14 }), " ", name] }, i)))] }))] }), jsxs("div", { className: `${styles.animBar} ${api.animations.length === 0 ? styles.hidden : ''}`, children: [jsx(NiceButton, { className: styles.toolBtn, variant: "ghost", size: "sm", onClick: api.togglePlay, "aria-label": t('modelViewer.playPause', 'Play/Pause'), children: jsx(NiceIcon, { name: api.isPlaying ? 'pause' : 'play', size: 16 }) }), jsx(NiceSelect, { className: styles.animSelect, value: String(api.activeAnimIdx), onChange: (val) => api.playAnimation(parseInt(val)), options: api.animations.map((n, i) => ({ value: String(i), label: n })) }), jsx(NiceSlider, { className: styles.animSlider, min: 0, max: api.animDuration || 1, step: 0.01, value: api.animTime, onChange: (val) => api.seekAnim(val) }), jsxs("span", { className: styles.toolLabel, children: [api.animTime.toFixed(1), "s / ", api.animDuration.toFixed(1), "s"] })] }), showStatusBar && (jsxs("div", { className: styles.statusBar, children: [jsx("span", { children: api.statusText }), jsxs("div", { className: styles.statusRight, children: [jsx("span", { children: jsx(NavLabel, { mode: api.navigationMode }) }), api.dimensions && (jsxs("span", { children: [api.dimensions.width, "\u00D7", api.dimensions.height, "\u00D7", api.dimensions.depth] }))] })] })), children === null || children === void 0 ? void 0 : children(api)] }));
74
+ ),
75
+ /* @__PURE__ */ jsx(
76
+ "input",
77
+ {
78
+ ref: fileInputRef,
79
+ type: "file",
80
+ accept: ".fbx,.glb,.gltf,.obj,.dae,.stl,.ply",
81
+ style: { display: "none" },
82
+ onChange: (e) => {
83
+ const f = e.target.files?.[0];
84
+ if (f) {
85
+ api.loadFile(f);
86
+ }
87
+ e.target.value = "";
88
+ }
89
+ }
90
+ ),
91
+ /* @__PURE__ */ jsx("div", { className: styles.toolSep }),
92
+ /* @__PURE__ */ jsx(
93
+ NiceButton,
94
+ {
95
+ className: styles.toolBtn,
96
+ variant: "ghost",
97
+ size: "sm",
98
+ title: t("modelViewer.navigationMode", "Navigation mode"),
99
+ onClick: cycleNav,
100
+ children: /* @__PURE__ */ jsx(NavLabel, { mode: api.navigationMode })
101
+ }
102
+ ),
103
+ /* @__PURE__ */ jsx(
104
+ NiceButton,
105
+ {
106
+ className: styles.toolBtn,
107
+ variant: api.wireframe ? "primary" : "ghost",
108
+ size: "sm",
109
+ onClick: () => api.setWireframe(!api.wireframe),
110
+ title: t("modelViewer.wireframe", "Wireframe"),
111
+ "aria-label": t("modelViewer.wireframe", "Wireframe"),
112
+ children: /* @__PURE__ */ jsx(NiceIcon, { name: "square", size: 16 })
113
+ }
114
+ ),
115
+ /* @__PURE__ */ jsx(
116
+ NiceButton,
117
+ {
118
+ className: styles.toolBtn,
119
+ variant: api.showGrid ? "primary" : "ghost",
120
+ size: "sm",
121
+ onClick: () => api.setShowGrid(!api.showGrid),
122
+ title: t("modelViewer.grid", "Grid"),
123
+ "aria-label": t("modelViewer.grid", "Grid"),
124
+ children: /* @__PURE__ */ jsx(NiceIcon, { name: "grid", size: 16 })
125
+ }
126
+ ),
127
+ /* @__PURE__ */ jsx(
128
+ NiceButton,
129
+ {
130
+ className: styles.toolBtn,
131
+ variant: api.clippingEnabled ? "primary" : "ghost",
132
+ size: "sm",
133
+ onClick: () => api.setClippingEnabled(!api.clippingEnabled),
134
+ title: t("modelViewer.clippingPlane", "Clipping plane"),
135
+ "aria-label": t("modelViewer.clippingPlane", "Clipping plane"),
136
+ children: /* @__PURE__ */ jsx(NiceIcon, { name: "scissors", size: 16 })
137
+ }
138
+ ),
139
+ api.clippingEnabled && /* @__PURE__ */ jsxs(Fragment, { children: [
140
+ /* @__PURE__ */ jsx(
141
+ NiceSelect,
142
+ {
143
+ className: styles.animSelect,
144
+ value: api.clippingAxis,
145
+ onChange: (val) => api.setClippingAxis(val),
146
+ options: [
147
+ { value: "x", label: "X" },
148
+ { value: "y", label: "Y" },
149
+ { value: "z", label: "Z" }
150
+ ]
151
+ }
152
+ ),
153
+ /* @__PURE__ */ jsx(
154
+ NiceSlider,
155
+ {
156
+ min: -10,
157
+ max: 10,
158
+ step: 0.1,
159
+ value: api.clippingPosition,
160
+ onChange: (val) => api.setClippingPosition(val),
161
+ style: { width: 60 }
162
+ }
163
+ )
164
+ ] }),
165
+ /* @__PURE__ */ jsx("div", { className: styles.toolSep }),
166
+ /* @__PURE__ */ jsx(
167
+ NiceButton,
168
+ {
169
+ className: styles.toolBtn,
170
+ variant: "ghost",
171
+ size: "sm",
172
+ onClick: api.zoomToFit,
173
+ title: t("modelViewer.zoomToFit", "Zoom to fit"),
174
+ "aria-label": t("modelViewer.zoomToFit", "Zoom to fit"),
175
+ children: /* @__PURE__ */ jsx(NiceIcon, { name: "search", size: 16 })
176
+ }
177
+ ),
178
+ /* @__PURE__ */ jsx(
179
+ NiceButton,
180
+ {
181
+ className: styles.toolBtn,
182
+ variant: "ghost",
183
+ size: "sm",
184
+ onClick: api.resetCamera,
185
+ title: t("modelViewer.resetCamera", "Reset camera"),
186
+ "aria-label": t("modelViewer.resetCamera", "Reset camera"),
187
+ children: /* @__PURE__ */ jsx(NiceIcon, { name: "home", size: 16 })
188
+ }
189
+ ),
190
+ /* @__PURE__ */ jsx("div", { className: styles.toolSep }),
191
+ /* @__PURE__ */ jsx(
192
+ NiceButton,
193
+ {
194
+ className: styles.toolBtn,
195
+ variant: "ghost",
196
+ size: "sm",
197
+ onClick: () => {
198
+ const url = api.screenshot();
199
+ const a = document.createElement("a");
200
+ a.href = url;
201
+ a.download = "screenshot.png";
202
+ a.click();
203
+ },
204
+ title: t("modelViewer.screenshot", "Screenshot"),
205
+ "aria-label": t("modelViewer.screenshot", "Screenshot"),
206
+ children: /* @__PURE__ */ jsx(NiceIcon, { name: "camera", size: 16 })
207
+ }
208
+ ),
209
+ /* @__PURE__ */ jsx(
210
+ NiceButton,
211
+ {
212
+ className: styles.toolBtn,
213
+ variant: "ghost",
214
+ size: "sm",
215
+ onClick: api.toggleFullscreen,
216
+ title: t("modelViewer.fullscreen", "Fullscreen"),
217
+ "aria-label": t("modelViewer.fullscreen", "Fullscreen"),
218
+ children: /* @__PURE__ */ jsx(NiceIcon, { name: api.isFullscreen ? "collapse" : "expand", size: 16 })
219
+ }
220
+ ),
221
+ toolbarExtra,
222
+ showInfoPanel && /* @__PURE__ */ jsx("div", { className: styles.toolRight, children: /* @__PURE__ */ jsx(
223
+ NiceButton,
224
+ {
225
+ className: `${styles.toolBtn} ${panelOpen ? styles.active : ""}`,
226
+ variant: panelOpen ? "primary" : "ghost",
227
+ size: "sm",
228
+ onClick: () => setPanelOpen(!panelOpen),
229
+ title: t("modelViewer.infoPanel", "Info panel"),
230
+ "aria-label": t("modelViewer.infoPanel", "Info panel"),
231
+ children: /* @__PURE__ */ jsx(NiceIcon, { name: "info", size: 16 })
232
+ }
233
+ ) })
234
+ ] }),
235
+ /* @__PURE__ */ jsxs(
236
+ "div",
237
+ {
238
+ className: styles.viewport,
239
+ ref: api.mountRef,
240
+ tabIndex: 0,
241
+ onDragOver: handleDragOver,
242
+ onDragLeave: handleDragLeave,
243
+ onDrop: handleDrop,
244
+ children: [
245
+ api.loading && /* @__PURE__ */ jsxs("div", { className: styles.loadingOverlay, children: [
246
+ /* @__PURE__ */ jsx("span", { className: styles.loadingText, children: t("modelViewer.loadingModel", "Loading model…") }),
247
+ /* @__PURE__ */ jsx("div", { className: styles.loadingBar, children: /* @__PURE__ */ jsx(
248
+ "div",
249
+ {
250
+ className: styles.loadingBarFill,
251
+ style: { width: `${(api.loadProgress * 100).toFixed(0)}%` }
252
+ }
253
+ ) })
254
+ ] }),
255
+ api.error && /* @__PURE__ */ jsxs("div", { className: styles.errorOverlay, children: [
256
+ /* @__PURE__ */ jsx(NiceIcon, { name: "warning", size: 28 }),
257
+ /* @__PURE__ */ jsx("span", { style: { marginTop: 8 }, children: api.error })
258
+ ] }),
259
+ dragOver && /* @__PURE__ */ jsx("div", { className: styles.dropZone, children: t("modelViewer.drop3dModelFileHere", "Drop 3D model file here") })
260
+ ]
261
+ }
262
+ ),
263
+ /* @__PURE__ */ jsxs("div", { className: `${styles.sidePanel} ${!panelOpen ? styles.hidden : ""}`, children: [
264
+ /* @__PURE__ */ jsxs("div", { className: styles.panelSection, children: [
265
+ /* @__PURE__ */ jsx("div", { className: styles.panelTitle, children: t("modelViewer.modelInfo", "Model Info") }),
266
+ /* @__PURE__ */ jsxs("div", { className: styles.panelRow, children: [
267
+ /* @__PURE__ */ jsx("label", { children: t("modelViewer.triangles", "Triangles") }),
268
+ /* @__PURE__ */ jsx("span", { children: api.polyCount.toLocaleString() })
269
+ ] }),
270
+ /* @__PURE__ */ jsxs("div", { className: styles.panelRow, children: [
271
+ /* @__PURE__ */ jsx("label", { children: t("modelViewer.meshes", "Meshes") }),
272
+ /* @__PURE__ */ jsx("span", { children: api.meshCount })
273
+ ] }),
274
+ api.dimensions && /* @__PURE__ */ jsxs(Fragment, { children: [
275
+ /* @__PURE__ */ jsxs("div", { className: styles.panelRow, children: [
276
+ /* @__PURE__ */ jsx("label", { children: t("modelViewer.width", "Width") }),
277
+ /* @__PURE__ */ jsx("span", { children: api.dimensions.width })
278
+ ] }),
279
+ /* @__PURE__ */ jsxs("div", { className: styles.panelRow, children: [
280
+ /* @__PURE__ */ jsx("label", { children: t("modelViewer.height", "Height") }),
281
+ /* @__PURE__ */ jsx("span", { children: api.dimensions.height })
282
+ ] }),
283
+ /* @__PURE__ */ jsxs("div", { className: styles.panelRow, children: [
284
+ /* @__PURE__ */ jsx("label", { children: t("modelViewer.depth", "Depth") }),
285
+ /* @__PURE__ */ jsx("span", { children: api.dimensions.depth })
286
+ ] })
287
+ ] })
288
+ ] }),
289
+ /* @__PURE__ */ jsxs("div", { className: styles.panelSection, children: [
290
+ /* @__PURE__ */ jsx("div", { className: styles.panelTitle, children: t("modelViewer.navigation", "Navigation") }),
291
+ NAV_MODES.map((m) => /* @__PURE__ */ jsx(
292
+ NiceButton,
293
+ {
294
+ className: styles.toolBtn,
295
+ variant: api.navigationMode === m ? "primary" : "ghost",
296
+ size: "sm",
297
+ onClick: () => api.setNavigationMode(m),
298
+ children: /* @__PURE__ */ jsx(NavLabel, { mode: m })
299
+ },
300
+ m
301
+ ))
302
+ ] }),
303
+ /* @__PURE__ */ jsxs("div", { className: styles.panelSection, children: [
304
+ /* @__PURE__ */ jsx("div", { className: styles.panelTitle, children: t("modelViewer.autoRotate", "Auto Rotate") }),
305
+ /* @__PURE__ */ jsx(
306
+ NiceSlider,
307
+ {
308
+ min: 0,
309
+ max: 10,
310
+ step: 0.5,
311
+ value: api.autoRotateSpeed,
312
+ onChange: (val) => api.setAutoRotateSpeed(val),
313
+ style: { width: "100%" }
314
+ }
315
+ )
316
+ ] }),
317
+ api.animations.length > 0 && /* @__PURE__ */ jsxs("div", { className: styles.panelSection, children: [
318
+ /* @__PURE__ */ jsx("div", { className: styles.panelTitle, children: t("modelViewer.animations", "Animations") }),
319
+ api.animations.map((name, i) => /* @__PURE__ */ jsxs(
320
+ NiceButton,
321
+ {
322
+ className: styles.toolBtn,
323
+ variant: api.activeAnimIdx === i ? "primary" : "ghost",
324
+ size: "sm",
325
+ onClick: () => api.playAnimation(i),
326
+ children: [
327
+ /* @__PURE__ */ jsx(NiceIcon, { name: "play", size: 14 }),
328
+ " ",
329
+ name
330
+ ]
331
+ },
332
+ i
333
+ ))
334
+ ] })
335
+ ] }),
336
+ /* @__PURE__ */ jsxs("div", { className: `${styles.animBar} ${api.animations.length === 0 ? styles.hidden : ""}`, children: [
337
+ /* @__PURE__ */ jsx(
338
+ NiceButton,
339
+ {
340
+ className: styles.toolBtn,
341
+ variant: "ghost",
342
+ size: "sm",
343
+ onClick: api.togglePlay,
344
+ "aria-label": t("modelViewer.playPause", "Play/Pause"),
345
+ children: /* @__PURE__ */ jsx(NiceIcon, { name: api.isPlaying ? "pause" : "play", size: 16 })
346
+ }
347
+ ),
348
+ /* @__PURE__ */ jsx(
349
+ NiceSelect,
350
+ {
351
+ className: styles.animSelect,
352
+ value: String(api.activeAnimIdx),
353
+ onChange: (val) => api.playAnimation(parseInt(val)),
354
+ options: api.animations.map((n, i) => ({ value: String(i), label: n }))
355
+ }
356
+ ),
357
+ /* @__PURE__ */ jsx(
358
+ NiceSlider,
359
+ {
360
+ className: styles.animSlider,
361
+ min: 0,
362
+ max: api.animDuration || 1,
363
+ step: 0.01,
364
+ value: api.animTime,
365
+ onChange: (val) => api.seekAnim(val)
366
+ }
367
+ ),
368
+ /* @__PURE__ */ jsxs("span", { className: styles.toolLabel, children: [
369
+ api.animTime.toFixed(1),
370
+ "s / ",
371
+ api.animDuration.toFixed(1),
372
+ "s"
373
+ ] })
374
+ ] }),
375
+ showStatusBar && /* @__PURE__ */ jsxs("div", { className: styles.statusBar, children: [
376
+ /* @__PURE__ */ jsx("span", { children: api.statusText }),
377
+ /* @__PURE__ */ jsxs("div", { className: styles.statusRight, children: [
378
+ /* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsx(NavLabel, { mode: api.navigationMode }) }),
379
+ api.dimensions && /* @__PURE__ */ jsxs("span", { children: [
380
+ api.dimensions.width,
381
+ "×",
382
+ api.dimensions.height,
383
+ "×",
384
+ api.dimensions.depth
385
+ ] })
386
+ ] })
387
+ ] }),
388
+ children?.(api)
389
+ ] });
390
+ };
391
+ export {
392
+ NiceModelViewer
64
393
  };
65
-
66
- export { NiceModelViewer };
@@ -1,3 +1,77 @@
1
- var styles = {"root":"ModelViewer-module_root__6Fbj3","toolbar":"ModelViewer-module_toolbar__EQJRN","toolBtn":"ModelViewer-module_toolBtn__f9833","active":"ModelViewer-module_active__MgURC","toolSep":"ModelViewer-module_toolSep__5zuAK","toolLabel":"ModelViewer-module_toolLabel__PcpJC","toolRight":"ModelViewer-module_toolRight__VSuzD","viewport":"ModelViewer-module_viewport__-1llx","loadingOverlay":"ModelViewer-module_loadingOverlay__Ayyci","loadingBar":"ModelViewer-module_loadingBar__RDqVv","loadingBarFill":"ModelViewer-module_loadingBarFill__zvIoP","loadingText":"ModelViewer-module_loadingText__6OGeA","errorOverlay":"ModelViewer-module_errorOverlay__ASUqN","sidePanel":"ModelViewer-module_sidePanel__-QFkd","hidden":"ModelViewer-module_hidden__yoMuq","panelSection":"ModelViewer-module_panelSection__bH-O3","panelTitle":"ModelViewer-module_panelTitle__xsn3e","panelRow":"ModelViewer-module_panelRow__3QxnD","animBar":"ModelViewer-module_animBar__qcYi5","animSlider":"ModelViewer-module_animSlider__9AQzN","animSelect":"ModelViewer-module_animSelect__LhFWD","statusBar":"ModelViewer-module_statusBar__-3uuA","statusRight":"ModelViewer-module_statusRight__vI51s","dropZone":"ModelViewer-module_dropZone__eNrPo"};
2
-
3
- export { styles as default };
1
+ const root = "_root_bqnwl_5";
2
+ const toolbar = "_toolbar_bqnwl_33";
3
+ const toolBtn = "_toolBtn_bqnwl_55";
4
+ const active = "_active_bqnwl_89";
5
+ const toolSep = "_toolSep_bqnwl_105";
6
+ const toolLabel = "_toolLabel_bqnwl_117";
7
+ const toolRight = "_toolRight_bqnwl_129";
8
+ const viewport = "_viewport_bqnwl_145";
9
+ const loadingOverlay = "_loadingOverlay_bqnwl_189";
10
+ const loadingBar = "_loadingBar_bqnwl_209";
11
+ const loadingBarFill = "_loadingBarFill_bqnwl_225";
12
+ const loadingText = "_loadingText_bqnwl_235";
13
+ const errorOverlay = "_errorOverlay_bqnwl_249";
14
+ const sidePanel = "_sidePanel_bqnwl_275";
15
+ const hidden = "_hidden_bqnwl_307";
16
+ const panelSection = "_panelSection_bqnwl_313";
17
+ const panelTitle = "_panelTitle_bqnwl_323";
18
+ const panelRow = "_panelRow_bqnwl_337";
19
+ const animBar = "_animBar_bqnwl_369";
20
+ const animSlider = "_animSlider_bqnwl_395";
21
+ const animSelect = "_animSelect_bqnwl_427";
22
+ const statusBar = "_statusBar_bqnwl_449";
23
+ const statusRight = "_statusRight_bqnwl_473";
24
+ const dropZone = "_dropZone_bqnwl_533";
25
+ const styles = {
26
+ root,
27
+ toolbar,
28
+ toolBtn,
29
+ active,
30
+ toolSep,
31
+ toolLabel,
32
+ toolRight,
33
+ viewport,
34
+ loadingOverlay,
35
+ loadingBar,
36
+ loadingBarFill,
37
+ loadingText,
38
+ errorOverlay,
39
+ sidePanel,
40
+ hidden,
41
+ panelSection,
42
+ panelTitle,
43
+ panelRow,
44
+ animBar,
45
+ animSlider,
46
+ animSelect,
47
+ statusBar,
48
+ statusRight,
49
+ dropZone
50
+ };
51
+ export {
52
+ active,
53
+ animBar,
54
+ animSelect,
55
+ animSlider,
56
+ styles as default,
57
+ dropZone,
58
+ errorOverlay,
59
+ hidden,
60
+ loadingBar,
61
+ loadingBarFill,
62
+ loadingOverlay,
63
+ loadingText,
64
+ panelRow,
65
+ panelSection,
66
+ panelTitle,
67
+ root,
68
+ sidePanel,
69
+ statusBar,
70
+ statusRight,
71
+ toolBtn,
72
+ toolLabel,
73
+ toolRight,
74
+ toolSep,
75
+ toolbar,
76
+ viewport
77
+ };