@nice2dev/ui-3d 1.3.15 → 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 +9 -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,102 +1,273 @@
1
- import { jsxs, jsx } from 'react/jsx-runtime';
2
- import { NiceTextInput, clickOnActivationKey, NiceColorPicker, NiceSlider, NiceSelect } from '@nice2dev/ui';
3
- import { useState } from 'react';
4
- import * as THREE from 'three';
5
- import { useNiceTranslation } from '../core/i18n.js';
6
- import styles from './ModelEditor.module.css.js';
7
- import { DEG } from './modelEditorTypes.js';
8
-
9
- /* ═══════════════════════════════════════════
10
- Sub-components
11
- ═══════════════════════════════════════════ */
12
- /** Vec3 input row for position/scale */
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { NiceTextInput, clickOnActivationKey, NiceColorPicker, NiceSlider, NiceSelect } from "@nice2dev/ui";
3
+ import { useState } from "react";
4
+ import * as THREE from "three";
5
+ import { useNiceTranslation } from "../core/i18n.js";
6
+ import styles from "./ModelEditor.module.css.js";
7
+ import { DEG } from "./modelEditorTypes.js";
13
8
  const Vec3Row = ({ label, value, onChange }) => {
14
- const [, forceUpdate] = useState(0);
15
- const set = (axis, v) => {
16
- const n = parseFloat(v);
17
- if (!isNaN(n)) {
18
- value[axis] = n;
19
- forceUpdate((x) => x + 1);
20
- onChange();
9
+ const [, forceUpdate] = useState(0);
10
+ const set = (axis, v) => {
11
+ const n = parseFloat(v);
12
+ if (!isNaN(n)) {
13
+ value[axis] = n;
14
+ forceUpdate((x) => x + 1);
15
+ onChange();
16
+ }
17
+ };
18
+ return /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
19
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: label }),
20
+ /* @__PURE__ */ jsxs("div", { className: styles.propVec3, children: [
21
+ /* @__PURE__ */ jsx(
22
+ NiceTextInput,
23
+ {
24
+ className: styles.propVec3X,
25
+ value: value.x.toFixed(3),
26
+ onChange: (val) => set("x", val)
21
27
  }
22
- };
23
- return (jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: label }), jsxs("div", { className: styles.propVec3, children: [jsx(NiceTextInput, { className: styles.propVec3X, value: value.x.toFixed(3), onChange: (val) => set('x', val) }), jsx(NiceTextInput, { className: styles.propVec3Y, value: value.y.toFixed(3), onChange: (val) => set('y', val) }), jsx(NiceTextInput, { className: styles.propVec3Z, value: value.z.toFixed(3), onChange: (val) => set('z', val) })] })] }));
28
+ ),
29
+ /* @__PURE__ */ jsx(
30
+ NiceTextInput,
31
+ {
32
+ className: styles.propVec3Y,
33
+ value: value.y.toFixed(3),
34
+ onChange: (val) => set("y", val)
35
+ }
36
+ ),
37
+ /* @__PURE__ */ jsx(
38
+ NiceTextInput,
39
+ {
40
+ className: styles.propVec3Z,
41
+ value: value.z.toFixed(3),
42
+ onChange: (val) => set("z", val)
43
+ }
44
+ )
45
+ ] })
46
+ ] });
24
47
  };
25
- /** Vec3 input row for rotation (shows degrees) */
26
48
  const Vec3RowDeg = ({ label, value, onChange }) => {
27
- const [, forceUpdate] = useState(0);
28
- const set = (axis, v) => {
29
- const n = parseFloat(v);
30
- if (!isNaN(n)) {
31
- value[axis] = n / DEG;
32
- forceUpdate((x) => x + 1);
33
- onChange();
49
+ const [, forceUpdate] = useState(0);
50
+ const set = (axis, v) => {
51
+ const n = parseFloat(v);
52
+ if (!isNaN(n)) {
53
+ value[axis] = n / DEG;
54
+ forceUpdate((x) => x + 1);
55
+ onChange();
56
+ }
57
+ };
58
+ return /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
59
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: label }),
60
+ /* @__PURE__ */ jsxs("div", { className: styles.propVec3, children: [
61
+ /* @__PURE__ */ jsx(
62
+ NiceTextInput,
63
+ {
64
+ className: styles.propVec3X,
65
+ value: (value.x * DEG).toFixed(1),
66
+ onChange: (val) => set("x", val)
67
+ }
68
+ ),
69
+ /* @__PURE__ */ jsx(
70
+ NiceTextInput,
71
+ {
72
+ className: styles.propVec3Y,
73
+ value: (value.y * DEG).toFixed(1),
74
+ onChange: (val) => set("y", val)
34
75
  }
35
- };
36
- return (jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: label }), jsxs("div", { className: styles.propVec3, children: [jsx(NiceTextInput, { className: styles.propVec3X, value: (value.x * DEG).toFixed(1), onChange: (val) => set('x', val) }), jsx(NiceTextInput, { className: styles.propVec3Y, value: (value.y * DEG).toFixed(1), onChange: (val) => set('y', val) }), jsx(NiceTextInput, { className: styles.propVec3Z, value: (value.z * DEG).toFixed(1), onChange: (val) => set('z', val) })] })] }));
76
+ ),
77
+ /* @__PURE__ */ jsx(
78
+ NiceTextInput,
79
+ {
80
+ className: styles.propVec3Z,
81
+ value: (value.z * DEG).toFixed(1),
82
+ onChange: (val) => set("z", val)
83
+ }
84
+ )
85
+ ] })
86
+ ] });
37
87
  };
38
- /** Material card with editable properties */
39
88
  const MaterialCard = ({ material, active, onClick, onUpdate }) => {
40
- var _a, _b, _c, _d;
41
- const { t } = useNiceTranslation();
42
- const mat = material;
43
- const hasColor = 'color' in mat;
44
- const hasMetalness = 'metalness' in mat;
45
- const hasRoughness = 'roughness' in mat;
46
- const hasEmissive = 'emissive' in mat;
47
- const hasMap = 'map' in mat;
48
- const hasNormalMap = 'normalMap' in mat;
49
- const setColor = (hex) => {
50
- if (hasColor) {
51
- mat.color.set(hex);
52
- mat.needsUpdate = true;
53
- onUpdate();
54
- }
55
- };
56
- const setNum = (prop, val) => {
57
- mat[prop] = val;
58
- mat.needsUpdate = true;
59
- onUpdate();
60
- };
61
- return (jsxs("div", { role: "button", tabIndex: 0, onKeyDown: clickOnActivationKey, className: `${styles.materialCard} ${active ? styles.materialCardActive : ''}`, onClick: onClick, children: [jsx("div", { className: styles.materialHeader, children: jsx("span", { className: styles.materialName, children: mat.name || mat.type }) }), hasColor && (jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorSubComponents.color', 'Color') }), jsx(NiceColorPicker, { value: `#${mat.color.getHexString()}`, onChange: (c) => setColor(c) }), jsxs("span", { style: { fontSize: 10, color: 'var(--nice-text-secondary, #888)' }, children: ["#", mat.color.getHexString()] })] })), hasMetalness && (jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorSubComponents.metal', 'Metal') }), jsx(NiceSlider, { min: 0, max: 1, step: 0.01, value: mat.metalness, onChange: (val) => setNum('metalness', val), style: { flex: 1 } }), jsx("span", { style: {
62
- fontSize: 9,
63
- color: 'var(--nice-text-secondary, #888)',
64
- width: 28,
65
- textAlign: 'right',
66
- }, children: mat.metalness.toFixed(2) })] })), hasRoughness && (jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorSubComponents.rough', 'Rough') }), jsx(NiceSlider, { min: 0, max: 1, step: 0.01, value: mat.roughness, onChange: (val) => setNum('roughness', val), style: { flex: 1 } }), jsx("span", { style: {
67
- fontSize: 9,
68
- color: 'var(--nice-text-secondary, #888)',
69
- width: 28,
70
- textAlign: 'right',
71
- }, children: mat.roughness.toFixed(2) })] })), hasEmissive && mat.emissive && (jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorSubComponents.emissive', 'Emissive') }), jsx(NiceColorPicker, { value: `#${mat.emissive.getHexString()}`, onChange: (c) => {
72
- mat.emissive.set(c);
73
- mat.needsUpdate = true;
74
- onUpdate();
75
- } })] })), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorSubComponents.opacity', 'Opacity') }), jsx(NiceSlider, { min: 0, max: 1, step: 0.01, value: mat.opacity, onChange: (val) => {
76
- mat.opacity = val;
77
- mat.transparent = val < 1;
78
- mat.needsUpdate = true;
79
- onUpdate();
80
- }, style: { flex: 1 } }), jsx("span", { style: {
81
- fontSize: 9,
82
- color: 'var(--nice-text-secondary, #888)',
83
- width: 28,
84
- textAlign: 'right',
85
- }, children: mat.opacity.toFixed(2) })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('modelEditorSubComponents.side', 'Side') }), jsx(NiceSelect, { className: styles.propSelect, value: String(mat.side), onChange: (val) => {
86
- mat.side = parseInt(val);
87
- mat.needsUpdate = true;
88
- onUpdate();
89
- }, options: [
90
- {
91
- value: String(THREE.FrontSide),
92
- label: t('modelEditorSubComponents.front', 'Front'),
93
- },
94
- { value: String(THREE.BackSide), label: t('modelEditorSubComponents.back', 'Back') },
95
- {
96
- value: String(THREE.DoubleSide),
97
- label: t('modelEditorSubComponents.double', 'Double'),
98
- },
99
- ] })] }), hasMap && mat.map && (jsxs("div", { className: styles.textureSlot, children: [jsx("div", { className: styles.textureThumb, children: mat.map.image != null && (jsx("img", { src: (_a = mat.map.image.src) !== null && _a !== void 0 ? _a : '', alt: "diffuse" })) }), jsxs("div", { className: styles.textureInfo, children: [jsx("div", { className: styles.textureName, children: t('modelEditorSubComponents.diffuseMap', 'Diffuse Map') }), jsxs("div", { style: { fontSize: 9, color: 'var(--nice-text-secondary, #666)' }, children: [(_b = mat.map.image) === null || _b === void 0 ? void 0 : _b.width, "\u00D7", (_c = mat.map.image) === null || _c === void 0 ? void 0 : _c.height] })] })] })), hasNormalMap && mat.normalMap && (jsxs("div", { className: styles.textureSlot, children: [jsx("div", { className: styles.textureThumb, children: mat.normalMap.image != null && (jsx("img", { src: (_d = mat.normalMap.image.src) !== null && _d !== void 0 ? _d : '', alt: "normal" })) }), jsx("div", { className: styles.textureInfo, children: jsx("div", { className: styles.textureName, children: t('modelEditorSubComponents.normalMap', 'Normal Map') }) })] }))] }));
89
+ const { t } = useNiceTranslation();
90
+ const mat = material;
91
+ const hasColor = "color" in mat;
92
+ const hasMetalness = "metalness" in mat;
93
+ const hasRoughness = "roughness" in mat;
94
+ const hasEmissive = "emissive" in mat;
95
+ const hasMap = "map" in mat;
96
+ const hasNormalMap = "normalMap" in mat;
97
+ const setColor = (hex) => {
98
+ if (hasColor) {
99
+ mat.color.set(hex);
100
+ mat.needsUpdate = true;
101
+ onUpdate();
102
+ }
103
+ };
104
+ const setNum = (prop, val) => {
105
+ mat[prop] = val;
106
+ mat.needsUpdate = true;
107
+ onUpdate();
108
+ };
109
+ return /* @__PURE__ */ jsxs(
110
+ "div",
111
+ {
112
+ role: "button",
113
+ tabIndex: 0,
114
+ onKeyDown: clickOnActivationKey,
115
+ className: `${styles.materialCard} ${active ? styles.materialCardActive : ""}`,
116
+ onClick,
117
+ children: [
118
+ /* @__PURE__ */ jsx("div", { className: styles.materialHeader, children: /* @__PURE__ */ jsx("span", { className: styles.materialName, children: mat.name || mat.type }) }),
119
+ hasColor && /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
120
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("modelEditorSubComponents.color", "Color") }),
121
+ /* @__PURE__ */ jsx(NiceColorPicker, { value: `#${mat.color.getHexString()}`, onChange: (c) => setColor(c) }),
122
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: 10, color: "var(--nice-text-secondary, #888)" }, children: [
123
+ "#",
124
+ mat.color.getHexString()
125
+ ] })
126
+ ] }),
127
+ hasMetalness && /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
128
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("modelEditorSubComponents.metal", "Metal") }),
129
+ /* @__PURE__ */ jsx(
130
+ NiceSlider,
131
+ {
132
+ min: 0,
133
+ max: 1,
134
+ step: 0.01,
135
+ value: mat.metalness,
136
+ onChange: (val) => setNum("metalness", val),
137
+ style: { flex: 1 }
138
+ }
139
+ ),
140
+ /* @__PURE__ */ jsx(
141
+ "span",
142
+ {
143
+ style: {
144
+ fontSize: 9,
145
+ color: "var(--nice-text-secondary, #888)",
146
+ width: 28,
147
+ textAlign: "right"
148
+ },
149
+ children: mat.metalness.toFixed(2)
150
+ }
151
+ )
152
+ ] }),
153
+ hasRoughness && /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
154
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("modelEditorSubComponents.rough", "Rough") }),
155
+ /* @__PURE__ */ jsx(
156
+ NiceSlider,
157
+ {
158
+ min: 0,
159
+ max: 1,
160
+ step: 0.01,
161
+ value: mat.roughness,
162
+ onChange: (val) => setNum("roughness", val),
163
+ style: { flex: 1 }
164
+ }
165
+ ),
166
+ /* @__PURE__ */ jsx(
167
+ "span",
168
+ {
169
+ style: {
170
+ fontSize: 9,
171
+ color: "var(--nice-text-secondary, #888)",
172
+ width: 28,
173
+ textAlign: "right"
174
+ },
175
+ children: mat.roughness.toFixed(2)
176
+ }
177
+ )
178
+ ] }),
179
+ hasEmissive && mat.emissive && /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
180
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("modelEditorSubComponents.emissive", "Emissive") }),
181
+ /* @__PURE__ */ jsx(
182
+ NiceColorPicker,
183
+ {
184
+ value: `#${mat.emissive.getHexString()}`,
185
+ onChange: (c) => {
186
+ mat.emissive.set(c);
187
+ mat.needsUpdate = true;
188
+ onUpdate();
189
+ }
190
+ }
191
+ )
192
+ ] }),
193
+ /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
194
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("modelEditorSubComponents.opacity", "Opacity") }),
195
+ /* @__PURE__ */ jsx(
196
+ NiceSlider,
197
+ {
198
+ min: 0,
199
+ max: 1,
200
+ step: 0.01,
201
+ value: mat.opacity,
202
+ onChange: (val) => {
203
+ mat.opacity = val;
204
+ mat.transparent = val < 1;
205
+ mat.needsUpdate = true;
206
+ onUpdate();
207
+ },
208
+ style: { flex: 1 }
209
+ }
210
+ ),
211
+ /* @__PURE__ */ jsx(
212
+ "span",
213
+ {
214
+ style: {
215
+ fontSize: 9,
216
+ color: "var(--nice-text-secondary, #888)",
217
+ width: 28,
218
+ textAlign: "right"
219
+ },
220
+ children: mat.opacity.toFixed(2)
221
+ }
222
+ )
223
+ ] }),
224
+ /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
225
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("modelEditorSubComponents.side", "Side") }),
226
+ /* @__PURE__ */ jsx(
227
+ NiceSelect,
228
+ {
229
+ className: styles.propSelect,
230
+ value: String(mat.side),
231
+ onChange: (val) => {
232
+ mat.side = parseInt(val);
233
+ mat.needsUpdate = true;
234
+ onUpdate();
235
+ },
236
+ options: [
237
+ {
238
+ value: String(THREE.FrontSide),
239
+ label: t("modelEditorSubComponents.front", "Front")
240
+ },
241
+ { value: String(THREE.BackSide), label: t("modelEditorSubComponents.back", "Back") },
242
+ {
243
+ value: String(THREE.DoubleSide),
244
+ label: t("modelEditorSubComponents.double", "Double")
245
+ }
246
+ ]
247
+ }
248
+ )
249
+ ] }),
250
+ hasMap && mat.map && /* @__PURE__ */ jsxs("div", { className: styles.textureSlot, children: [
251
+ /* @__PURE__ */ jsx("div", { className: styles.textureThumb, children: mat.map.image != null && /* @__PURE__ */ jsx("img", { src: mat.map.image.src ?? "", alt: "diffuse" }) }),
252
+ /* @__PURE__ */ jsxs("div", { className: styles.textureInfo, children: [
253
+ /* @__PURE__ */ jsx("div", { className: styles.textureName, children: t("modelEditorSubComponents.diffuseMap", "Diffuse Map") }),
254
+ /* @__PURE__ */ jsxs("div", { style: { fontSize: 9, color: "var(--nice-text-secondary, #666)" }, children: [
255
+ mat.map.image?.width,
256
+ "×",
257
+ mat.map.image?.height
258
+ ] })
259
+ ] })
260
+ ] }),
261
+ hasNormalMap && mat.normalMap && /* @__PURE__ */ jsxs("div", { className: styles.textureSlot, children: [
262
+ /* @__PURE__ */ jsx("div", { className: styles.textureThumb, children: mat.normalMap.image != null && /* @__PURE__ */ jsx("img", { src: mat.normalMap.image.src ?? "", alt: "normal" }) }),
263
+ /* @__PURE__ */ jsx("div", { className: styles.textureInfo, children: /* @__PURE__ */ jsx("div", { className: styles.textureName, children: t("modelEditorSubComponents.normalMap", "Normal Map") }) })
264
+ ] })
265
+ ]
266
+ }
267
+ );
268
+ };
269
+ export {
270
+ MaterialCard,
271
+ Vec3Row,
272
+ Vec3RowDeg
100
273
  };
101
-
102
- export { MaterialCard, Vec3Row, Vec3RowDeg };
@@ -1,34 +1,234 @@
1
- import { jsxs, jsx } from 'react/jsx-runtime';
2
- import { NiceIcon, clickOnActivationKey, NiceButton, NiceSlider } from '@nice2dev/ui';
3
- import React from 'react';
4
- import { useNiceTranslation } from '../core/i18n.js';
5
- import styles from './ModelEditor.module.css.js';
6
- import { fmtTime } from './modelEditorTypes.js';
7
-
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { NiceIcon, clickOnActivationKey, NiceButton, NiceSlider } from "@nice2dev/ui";
3
+ import React from "react";
4
+ import { useNiceTranslation } from "../core/i18n.js";
5
+ import styles from "./ModelEditor.module.css.js";
6
+ import { fmtTime } from "./modelEditorTypes.js";
8
7
  const ModelEditorTimeline = /* @__PURE__ */ Object.assign(
9
- /* @__PURE__ */ React.memo(({ api }) => {
8
+ /* @__PURE__ */ React.memo(({ api }) => {
10
9
  const { t } = useNiceTranslation();
11
- const { bottomCollapsed, setBottomCollapsed, animations, activeAnimIdx, isPlaying, animTime, animDuration, animSpeed, setAnimSpeed, loopAnim, setLoopAnim, seekAnim, togglePlay, stopAnim, playClip, mergeInputRef, } = api;
12
- return (jsxs("div", { className: `${styles.bottomPanel} ${bottomCollapsed ? styles.bottomPanelCollapsed : ''}`, children: [jsxs("div", { className: styles.bottomPanelHeader, children: [jsx("span", { role: "presentation", className: styles.bottomToggle, onClick: () => setBottomCollapsed((c) => !c), children: jsx(NiceIcon, { name: bottomCollapsed ? 'chevron-right' : 'chevron-down', size: 14 }) }), jsx("span", { role: "button", tabIndex: 0, "aria-expanded": !bottomCollapsed, onKeyDown: clickOnActivationKey, className: styles.bottomPanelTitle, onClick: () => setBottomCollapsed((c) => !c), children: animations.length !== 1
13
- ? t('modelEditorTimeline.animationClipsMany', 'Animation ({animations} clips)').replace('{animations}', String(animations.length))
14
- : t('modelEditorTimeline.animationClipsOne', 'Animation ({animations} clip)').replace('{animations}', String(animations.length)) }), jsx("div", { className: styles.menuSep }), jsxs("div", { className: styles.transportBar, children: [jsx(NiceButton, { className: styles.transportBtn, variant: "ghost", size: "sm", onClick: () => seekAnim(0), title: t('modelEditorTimeline.goToStart', 'Go to start'), "aria-label": t('modelEditorTimeline.goToStart', 'Go to start'), children: jsx(NiceIcon, { name: "chevron-left", size: 16 }) }), jsx(NiceButton, { className: styles.transportBtn, variant: isPlaying ? 'primary' : 'ghost', size: "sm", onClick: togglePlay, title: t('modelEditorTimeline.playPauseSpace', 'Play/Pause (Space)'), "aria-label": t('modelEditorTimeline.playPauseSpace', 'Play/Pause (Space)'), children: jsx(NiceIcon, { name: isPlaying ? 'pause' : 'play', size: 16 }) }), jsx(NiceButton, { className: styles.transportBtn, variant: "ghost", size: "sm", onClick: stopAnim, title: t('modelEditorTimeline.stop', 'Stop'), "aria-label": t('modelEditorTimeline.stop', 'Stop'), children: jsx(NiceIcon, { name: "stop", size: 16 }) }), jsx(NiceButton, { className: styles.transportBtn, variant: loopAnim ? 'primary' : 'ghost', size: "sm", onClick: () => setLoopAnim((v) => !v), title: t('modelEditorTimeline.loop', 'Loop'), "aria-label": t('modelEditorTimeline.loop', 'Loop'), children: jsx(NiceIcon, { name: "refresh", size: 16 }) })] }), jsxs("span", { className: styles.transportTime, children: [fmtTime(animTime), " / ", fmtTime(animDuration)] }), jsx("div", { className: styles.menuSep }), jsx("label", { className: styles.menuLabel, children: t('modelEditorTimeline.speed', 'Speed') }), jsx(NiceSlider, { min: 0.1, max: 3, step: 0.1, value: animSpeed, onChange: (val) => setAnimSpeed(val), style: { width: 60 } }), jsxs("span", { className: styles.menuLabel, children: [animSpeed.toFixed(1), "\u00D7"] })] }), !bottomCollapsed && (jsxs("div", { className: styles.animListArea, children: [jsxs("div", { className: styles.animList, children: [animations.length === 0 && (jsx("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10, padding: 8 }, children: t('modelEditorTimeline.noAnimationsLoaded', 'No animations loaded') })), animations.map((anim, i) => (jsxs("div", { role: "button", tabIndex: 0, onKeyDown: clickOnActivationKey, className: `${styles.animItem} ${i === activeAnimIdx ? styles.animItemActive : ''}`, onClick: () => playClip(i), children: [jsx("span", { children: anim.name }), jsxs("span", { className: styles.animDuration, children: [anim.duration.toFixed(1), "s"] })] }, `${anim.name}-${i}`))), jsx("div", { style: { padding: '4px 8px' }, children: jsx(NiceButton, { className: styles.btnSecondary, variant: "ghost", size: "sm", style: { width: '100%', fontSize: 10 }, onClick: () => { var _a; return (_a = mergeInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: t('modelEditorTimeline.addAnimationFile', '+ Add Animation File') }) })] }), jsxs("div", { className: styles.timelineArea, children: [jsxs("div", { className: styles.timelineRuler, children: [animDuration > 0 &&
15
- Array.from({ length: Math.ceil(animDuration) + 1 }, (_, i) => {
16
- const pct = (i / animDuration) * 100;
17
- return (jsxs(React.Fragment, { children: [jsx("div", { className: styles.rulerTick, style: { left: `${pct}%` } }), jsxs("span", { className: styles.rulerLabel, style: { left: `${pct}%` }, children: [i, "s"] })] }, i));
18
- }), animDuration > 0 && (jsx("div", { className: styles.playhead, style: {
19
- left: `${(animTime / animDuration) * 100}%`,
20
- height: '100%',
21
- }, children: jsx("div", { className: styles.playheadHandle }) }))] }), jsx("div", { role: "presentation", className: styles.timelineTracks, style: { height: 140, cursor: 'pointer' }, onClick: (e) => {
22
- if (animDuration <= 0) {
23
- return;
24
- }
25
- const rect = e.currentTarget.getBoundingClientRect();
26
- const pct = (e.clientX - rect.left) / rect.width;
27
- seekAnim(Math.max(0, Math.min(animDuration, pct * animDuration)));
28
- }, children: animDuration > 0 && (jsx("div", { className: styles.playhead, style: {
29
- left: `${(animTime / animDuration) * 100}%`,
30
- height: '100%',
31
- } })) })] })] }))] }));
32
- }), { displayName: 'ModelEditorTimeline' });
33
-
34
- export { ModelEditorTimeline as default };
10
+ const {
11
+ bottomCollapsed,
12
+ setBottomCollapsed,
13
+ animations,
14
+ activeAnimIdx,
15
+ isPlaying,
16
+ animTime,
17
+ animDuration,
18
+ animSpeed,
19
+ setAnimSpeed,
20
+ loopAnim,
21
+ setLoopAnim,
22
+ seekAnim,
23
+ togglePlay,
24
+ stopAnim,
25
+ playClip,
26
+ mergeInputRef
27
+ } = api;
28
+ return /* @__PURE__ */ jsxs(
29
+ "div",
30
+ {
31
+ className: `${styles.bottomPanel} ${bottomCollapsed ? styles.bottomPanelCollapsed : ""}`,
32
+ children: [
33
+ /* @__PURE__ */ jsxs("div", { className: styles.bottomPanelHeader, children: [
34
+ /* @__PURE__ */ jsx(
35
+ "span",
36
+ {
37
+ role: "presentation",
38
+ className: styles.bottomToggle,
39
+ onClick: () => setBottomCollapsed((c) => !c),
40
+ children: /* @__PURE__ */ jsx(NiceIcon, { name: bottomCollapsed ? "chevron-right" : "chevron-down", size: 14 })
41
+ }
42
+ ),
43
+ /* @__PURE__ */ jsx(
44
+ "span",
45
+ {
46
+ role: "button",
47
+ tabIndex: 0,
48
+ "aria-expanded": !bottomCollapsed,
49
+ onKeyDown: clickOnActivationKey,
50
+ className: styles.bottomPanelTitle,
51
+ onClick: () => setBottomCollapsed((c) => !c),
52
+ children: animations.length !== 1 ? t(
53
+ "modelEditorTimeline.animationClipsMany",
54
+ "Animation ({animations} clips)"
55
+ ).replace("{animations}", String(animations.length)) : t("modelEditorTimeline.animationClipsOne", "Animation ({animations} clip)").replace(
56
+ "{animations}",
57
+ String(animations.length)
58
+ )
59
+ }
60
+ ),
61
+ /* @__PURE__ */ jsx("div", { className: styles.menuSep }),
62
+ /* @__PURE__ */ jsxs("div", { className: styles.transportBar, children: [
63
+ /* @__PURE__ */ jsx(
64
+ NiceButton,
65
+ {
66
+ className: styles.transportBtn,
67
+ variant: "ghost",
68
+ size: "sm",
69
+ onClick: () => seekAnim(0),
70
+ title: t("modelEditorTimeline.goToStart", "Go to start"),
71
+ "aria-label": t("modelEditorTimeline.goToStart", "Go to start"),
72
+ children: /* @__PURE__ */ jsx(NiceIcon, { name: "chevron-left", size: 16 })
73
+ }
74
+ ),
75
+ /* @__PURE__ */ jsx(
76
+ NiceButton,
77
+ {
78
+ className: styles.transportBtn,
79
+ variant: isPlaying ? "primary" : "ghost",
80
+ size: "sm",
81
+ onClick: togglePlay,
82
+ title: t("modelEditorTimeline.playPauseSpace", "Play/Pause (Space)"),
83
+ "aria-label": t("modelEditorTimeline.playPauseSpace", "Play/Pause (Space)"),
84
+ children: /* @__PURE__ */ jsx(NiceIcon, { name: isPlaying ? "pause" : "play", size: 16 })
85
+ }
86
+ ),
87
+ /* @__PURE__ */ jsx(
88
+ NiceButton,
89
+ {
90
+ className: styles.transportBtn,
91
+ variant: "ghost",
92
+ size: "sm",
93
+ onClick: stopAnim,
94
+ title: t("modelEditorTimeline.stop", "Stop"),
95
+ "aria-label": t("modelEditorTimeline.stop", "Stop"),
96
+ children: /* @__PURE__ */ jsx(NiceIcon, { name: "stop", size: 16 })
97
+ }
98
+ ),
99
+ /* @__PURE__ */ jsx(
100
+ NiceButton,
101
+ {
102
+ className: styles.transportBtn,
103
+ variant: loopAnim ? "primary" : "ghost",
104
+ size: "sm",
105
+ onClick: () => setLoopAnim((v) => !v),
106
+ title: t("modelEditorTimeline.loop", "Loop"),
107
+ "aria-label": t("modelEditorTimeline.loop", "Loop"),
108
+ children: /* @__PURE__ */ jsx(NiceIcon, { name: "refresh", size: 16 })
109
+ }
110
+ )
111
+ ] }),
112
+ /* @__PURE__ */ jsxs("span", { className: styles.transportTime, children: [
113
+ fmtTime(animTime),
114
+ " / ",
115
+ fmtTime(animDuration)
116
+ ] }),
117
+ /* @__PURE__ */ jsx("div", { className: styles.menuSep }),
118
+ /* @__PURE__ */ jsx("label", { className: styles.menuLabel, children: t("modelEditorTimeline.speed", "Speed") }),
119
+ /* @__PURE__ */ jsx(
120
+ NiceSlider,
121
+ {
122
+ min: 0.1,
123
+ max: 3,
124
+ step: 0.1,
125
+ value: animSpeed,
126
+ onChange: (val) => setAnimSpeed(val),
127
+ style: { width: 60 }
128
+ }
129
+ ),
130
+ /* @__PURE__ */ jsxs("span", { className: styles.menuLabel, children: [
131
+ animSpeed.toFixed(1),
132
+ "×"
133
+ ] })
134
+ ] }),
135
+ !bottomCollapsed && /* @__PURE__ */ jsxs("div", { className: styles.animListArea, children: [
136
+ /* @__PURE__ */ jsxs("div", { className: styles.animList, children: [
137
+ animations.length === 0 && /* @__PURE__ */ jsx(
138
+ "div",
139
+ {
140
+ style: { color: "var(--nice-text-secondary, #666)", fontSize: 10, padding: 8 },
141
+ children: t("modelEditorTimeline.noAnimationsLoaded", "No animations loaded")
142
+ }
143
+ ),
144
+ animations.map((anim, i) => /* @__PURE__ */ jsxs(
145
+ "div",
146
+ {
147
+ role: "button",
148
+ tabIndex: 0,
149
+ onKeyDown: clickOnActivationKey,
150
+ className: `${styles.animItem} ${i === activeAnimIdx ? styles.animItemActive : ""}`,
151
+ onClick: () => playClip(i),
152
+ children: [
153
+ /* @__PURE__ */ jsx("span", { children: anim.name }),
154
+ /* @__PURE__ */ jsxs("span", { className: styles.animDuration, children: [
155
+ anim.duration.toFixed(1),
156
+ "s"
157
+ ] })
158
+ ]
159
+ },
160
+ `${anim.name}-${i}`
161
+ )),
162
+ /* @__PURE__ */ jsx("div", { style: { padding: "4px 8px" }, children: /* @__PURE__ */ jsx(
163
+ NiceButton,
164
+ {
165
+ className: styles.btnSecondary,
166
+ variant: "ghost",
167
+ size: "sm",
168
+ style: { width: "100%", fontSize: 10 },
169
+ onClick: () => mergeInputRef.current?.click(),
170
+ children: t("modelEditorTimeline.addAnimationFile", "+ Add Animation File")
171
+ }
172
+ ) })
173
+ ] }),
174
+ /* @__PURE__ */ jsxs("div", { className: styles.timelineArea, children: [
175
+ /* @__PURE__ */ jsxs("div", { className: styles.timelineRuler, children: [
176
+ animDuration > 0 && Array.from({ length: Math.ceil(animDuration) + 1 }, (_, i) => {
177
+ const pct = i / animDuration * 100;
178
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
179
+ /* @__PURE__ */ jsx("div", { className: styles.rulerTick, style: { left: `${pct}%` } }),
180
+ /* @__PURE__ */ jsxs("span", { className: styles.rulerLabel, style: { left: `${pct}%` }, children: [
181
+ i,
182
+ "s"
183
+ ] })
184
+ ] }, i);
185
+ }),
186
+ animDuration > 0 && /* @__PURE__ */ jsx(
187
+ "div",
188
+ {
189
+ className: styles.playhead,
190
+ style: {
191
+ left: `${animTime / animDuration * 100}%`,
192
+ height: "100%"
193
+ },
194
+ children: /* @__PURE__ */ jsx("div", { className: styles.playheadHandle })
195
+ }
196
+ )
197
+ ] }),
198
+ /* @__PURE__ */ jsx(
199
+ "div",
200
+ {
201
+ role: "presentation",
202
+ className: styles.timelineTracks,
203
+ style: { height: 140, cursor: "pointer" },
204
+ onClick: (e) => {
205
+ if (animDuration <= 0) {
206
+ return;
207
+ }
208
+ const rect = e.currentTarget.getBoundingClientRect();
209
+ const pct = (e.clientX - rect.left) / rect.width;
210
+ seekAnim(Math.max(0, Math.min(animDuration, pct * animDuration)));
211
+ },
212
+ children: animDuration > 0 && /* @__PURE__ */ jsx(
213
+ "div",
214
+ {
215
+ className: styles.playhead,
216
+ style: {
217
+ left: `${animTime / animDuration * 100}%`,
218
+ height: "100%"
219
+ }
220
+ }
221
+ )
222
+ }
223
+ )
224
+ ] })
225
+ ] })
226
+ ]
227
+ }
228
+ );
229
+ }),
230
+ { displayName: "ModelEditorTimeline" }
231
+ );
232
+ export {
233
+ ModelEditorTimeline as default
234
+ };