@nice2dev/ui-3d 1.2.1 → 1.2.2
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.
- package/CHANGELOG.md +39 -0
- package/dist/cjs/material/NiceMaterialEditor.js +1 -0
- package/dist/cjs/model/ModelEditor.js +7 -1
- package/dist/cjs/model/ModelEditorMenuBar.js +5 -5
- package/dist/cjs/model/ModelEditorRightPanel.js +13 -9
- package/dist/cjs/model/ModelEditorSubComponents.js +9 -3
- package/dist/cjs/model/ModelEditorTimeline.js +3 -1
- package/dist/cjs/model/ModelEditorToolbar.js +1 -1
- package/dist/cjs/model/ModelEditorViewport.js +5 -2
- package/dist/cjs/model/ModelViewer.js +4 -4
- package/dist/cjs/model/NiceArmatureEditor.js +4 -4
- package/dist/cjs/model/NiceMorphTargetEditor.js +3 -1
- package/dist/cjs/model/NiceOctree.js +7 -4
- package/dist/cjs/model/NicePhysicsSimulation.js +4 -4
- package/dist/cjs/model/NiceProceduralGeometry.js +3 -3
- package/dist/cjs/model/NiceTerrainEditor.js +2 -2
- package/dist/cjs/model/NiceWeightPainter.js +2 -2
- package/dist/cjs/model/NiceXRPreview.js +6 -3
- package/dist/cjs/model/useModelEditor.js +92 -73
- package/dist/cjs/model/useModelViewer.js +13 -6
- package/dist/cjs/particle/NiceParticleEditor.js +10 -3
- package/dist/cjs/rendering/NiceCascadedShadows.js +1 -1
- package/dist/cjs/rendering/NiceRenderExport.js +2 -2
- package/dist/cjs/rendering/NiceSSAO.js +2 -1
- package/dist/cjs/rendering/NiceSSR.js +2 -1
- package/dist/cjs/rendering/NiceWebGPURenderer.js +2 -1
- package/dist/cjs/uv/NiceUVEditor.js +3 -3
- package/dist/esm/material/NiceMaterialEditor.js +1 -0
- package/dist/esm/model/ModelEditor.js +7 -1
- package/dist/esm/model/ModelEditorMenuBar.js +5 -5
- package/dist/esm/model/ModelEditorRightPanel.js +13 -9
- package/dist/esm/model/ModelEditorSubComponents.js +9 -3
- package/dist/esm/model/ModelEditorTimeline.js +3 -1
- package/dist/esm/model/ModelEditorToolbar.js +1 -1
- package/dist/esm/model/ModelEditorViewport.js +5 -2
- package/dist/esm/model/ModelViewer.js +4 -4
- package/dist/esm/model/NiceArmatureEditor.js +4 -4
- package/dist/esm/model/NiceMorphTargetEditor.js +3 -1
- package/dist/esm/model/NiceOctree.js +7 -4
- package/dist/esm/model/NicePhysicsSimulation.js +4 -4
- package/dist/esm/model/NiceProceduralGeometry.js +3 -3
- package/dist/esm/model/NiceTerrainEditor.js +2 -2
- package/dist/esm/model/NiceWeightPainter.js +2 -2
- package/dist/esm/model/NiceXRPreview.js +6 -3
- package/dist/esm/model/useModelEditor.js +92 -73
- package/dist/esm/model/useModelViewer.js +13 -6
- package/dist/esm/particle/NiceParticleEditor.js +10 -3
- package/dist/esm/rendering/NiceCascadedShadows.js +1 -1
- package/dist/esm/rendering/NiceRenderExport.js +2 -2
- package/dist/esm/rendering/NiceSSAO.js +2 -1
- package/dist/esm/rendering/NiceSSR.js +2 -1
- package/dist/esm/rendering/NiceWebGPURenderer.js +2 -1
- package/dist/esm/uv/NiceUVEditor.js +3 -3
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# @nice2dev/ui-3d — Changelog
|
|
2
2
|
|
|
3
|
+
## 1.2.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 06eb0d2: 1.2.2 — library texts through the translator, a translator in the packages that had none, and the
|
|
8
|
+
end of the hook-dependency warnings. Nothing removed or renamed. Upgrade notes:
|
|
9
|
+
`documentation/omniverk/OMNIVERK_UPGRADE_1.2.2.md`.
|
|
10
|
+
|
|
11
|
+
**Translations.** 2,825 new keys in the global dictionary (en, pl, de, fr, es, uk), 80 in the local
|
|
12
|
+
dictionaries of ui-math, ui-diagrams and ui-presentations: column headers, option labels, texts in
|
|
13
|
+
conditions and templates, messages given to setters, labels returned by helpers, Polish texts that
|
|
14
|
+
were the English default. Relative dates through `Intl.RelativeTimeFormat`, plural forms through
|
|
15
|
+
`…One` / `…Many` keys. ui-science, ui-platform, ui-mobile, auth, licensing, templates, social and
|
|
16
|
+
the icon tools translate through `useNiceTranslation` (new peer `@nice2dev/ui-core ^1.2.0`);
|
|
17
|
+
ui-security reads the global translator when its own provider is absent.
|
|
18
|
+
|
|
19
|
+
**Additions.** `labelKey` on ui-mobile `TabItem` and `SwipeAction` (the presets set it);
|
|
20
|
+
`registerWebAuthn` / `authenticateWebAuthn` take the WebAuthn options `NiceWebAuthnButton` used to
|
|
21
|
+
drop (defaults unchanged); `NiceKeyboardHint` shows `descriptionKey`. Eight icons in
|
|
22
|
+
`@nice2dev/icons` — `NtdPoint`, `NtdSegment`, `NtdPerpendicular`, `NtdParallel`, `NtdMidpoint`,
|
|
23
|
+
`NtdAngleBisector`, `NtdGroup`, `NtdUngroup` — and the same names as `NiceIcon` built-ins.
|
|
24
|
+
|
|
25
|
+
**Behaviour changes, agreed.** ui-math, ui-diagrams and ui-presentations follow the app's global
|
|
26
|
+
`NiceI18nProvider` language when their own provider is absent (they stayed English);
|
|
27
|
+
`NicePresentationEditor` without `lang` takes the surrounding language. Icons drawn as glyphs are
|
|
28
|
+
Nice icons: the `NiceGeometry` tools, Align / Group / Ungroup / Layout of the diagram toolbar, the
|
|
29
|
+
presentation grid toggle, zoom and "Add Track" of `NiceDMXTimeline`. icons-gaming and
|
|
30
|
+
icons-filetypes name an icon by its `title` and are decorative without one, like `createIcon`
|
|
31
|
+
(screen readers read "GiHeart" or "DOCX file icon"); new optional `title` on the gaming icon props.
|
|
32
|
+
|
|
33
|
+
**Hook dependencies (161 → 0).** Fixed along the way: `NiceLowPowerMode` re-rendered forever from
|
|
34
|
+
mount and overflowed the stack on wake; `GenericPlayer` / `NiceGenericPlayerStage` restarted the
|
|
35
|
+
track on a volume change; `NiceDMXTimeline` called `onPlay` on every frame and missed cues added
|
|
36
|
+
during playback; `NiceConflictResolution` `autoResolve` never resolved; `NiceMAM` selected by the
|
|
37
|
+
previous filter; `NiceRulesEngine` "Run All" kept only the last result; `NiceCodeEditor` saved with
|
|
38
|
+
the first render's `onSave`; a consumer's cursor handler switched off `NiceCodeEditorAI`
|
|
39
|
+
completion; `useRadioStream` lost a volume set before the first play; `NiceGameEditor` shortcuts,
|
|
40
|
+
`useVoiceControl` continuous sessions, `NiceDeviceTrust` with a host storing the score.
|
|
41
|
+
|
|
3
42
|
## 1.2.1
|
|
4
43
|
|
|
5
44
|
### Patch Changes
|
|
@@ -364,6 +364,7 @@ function useMaterialEditor(initialGraph) {
|
|
|
364
364
|
}, 300);
|
|
365
365
|
return () => clearTimeout(timeout);
|
|
366
366
|
}
|
|
367
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- compiling only stops a second compile; as a dependency its true -> false flip would compile the same graph again, forever
|
|
367
368
|
}, [state.graph, state.autoCompile]);
|
|
368
369
|
return { state, dispatch };
|
|
369
370
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
var jsxRuntime = require('react/jsx-runtime');
|
|
4
4
|
var ui = require('@nice2dev/ui');
|
|
5
5
|
var editorAsk = require('../core/editorAsk.js');
|
|
6
|
+
var i18n = require('../core/i18n.js');
|
|
6
7
|
var ModelEditor_module = require('./ModelEditor.module.css.js');
|
|
7
8
|
var ModelEditorLeftPanel = require('./ModelEditorLeftPanel.js');
|
|
8
9
|
var ModelEditorMenuBar = require('./ModelEditorMenuBar.js');
|
|
@@ -15,6 +16,7 @@ var useModelEditor = require('./useModelEditor.js');
|
|
|
15
16
|
|
|
16
17
|
const ACCEPT = /* @__PURE__ */ modelEditorTypes.SUPPORTED_EXTENSIONS.map((e) => `.${e}`).join(',');
|
|
17
18
|
const NiceModelEditor = ({ className, onSaveToLibrary, onAIPoseVideo, }) => {
|
|
19
|
+
const { t } = i18n.useNiceTranslation();
|
|
18
20
|
const api = useModelEditor.useModelEditor({ onSaveToLibrary, onAIPoseVideo });
|
|
19
21
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("input", { ref: api.fileInputRef, type: "file", accept: ACCEPT, style: { display: 'none' }, onChange: (e) => {
|
|
20
22
|
var _a;
|
|
@@ -30,7 +32,11 @@ const NiceModelEditor = ({ className, onSaveToLibrary, onAIPoseVideo, }) => {
|
|
|
30
32
|
api.mergeAnimationFBX(f);
|
|
31
33
|
}
|
|
32
34
|
e.target.value = '';
|
|
33
|
-
} }), jsxRuntime.jsx("input", { ref: api.videoInputRef, type: "file", accept: "video/*", style: { display: 'none' }, onChange: api.handleAIVideo }), jsxRuntime.jsx(editorAsk.EditorAskDialog, { ask: api.pendingAsk }), jsxRuntime.jsx(ui.NiceAppShell, { className: `${ModelEditor_module.root} ${className !== null && className !== void 0 ? className : ''}`, flush: true, menubar: jsxRuntime.jsx(ModelEditorMenuBar, { api: api, hasOnSaveToLibrary: !!onSaveToLibrary }), sidebar: jsxRuntime.jsxs("div", { style: { display: 'flex', minHeight: 0 }, children: [jsxRuntime.jsx(ModelEditorLeftPanel, { api: api }), jsxRuntime.jsx(ModelEditorToolbar, { api: api })] }), rightSidebar: jsxRuntime.jsx(ModelEditorRightPanel, { api: api }), bottomPanel: jsxRuntime.jsx(ModelEditorTimeline, { api: api }), statusBar: jsxRuntime.jsxs("div", { className: ModelEditor_module.statusBar, children: [jsxRuntime.jsx("span", { children: api.statusText }), jsxRuntime.
|
|
35
|
+
} }), jsxRuntime.jsx("input", { ref: api.videoInputRef, type: "file", accept: "video/*", style: { display: 'none' }, onChange: api.handleAIVideo }), jsxRuntime.jsx(editorAsk.EditorAskDialog, { ask: api.pendingAsk }), jsxRuntime.jsx(ui.NiceAppShell, { className: `${ModelEditor_module.root} ${className !== null && className !== void 0 ? className : ''}`, flush: true, menubar: jsxRuntime.jsx(ModelEditorMenuBar, { api: api, hasOnSaveToLibrary: !!onSaveToLibrary }), sidebar: jsxRuntime.jsxs("div", { style: { display: 'flex', minHeight: 0 }, children: [jsxRuntime.jsx(ModelEditorLeftPanel, { api: api }), jsxRuntime.jsx(ModelEditorToolbar, { api: api })] }), rightSidebar: jsxRuntime.jsx(ModelEditorRightPanel, { api: api }), bottomPanel: jsxRuntime.jsx(ModelEditorTimeline, { api: api }), statusBar: jsxRuntime.jsxs("div", { className: ModelEditor_module.statusBar, children: [jsxRuntime.jsx("span", { children: api.statusText }), jsxRuntime.jsx("span", { style: { marginLeft: 'auto' }, children: t('modelEditor.trianglesMeshesBonesAnimS', '{polyCount} triangles · {meshCount} meshes · {boneCount} bones · {animations} anim(s)')
|
|
36
|
+
.replace('{polyCount}', String(api.polyCount.toLocaleString()))
|
|
37
|
+
.replace('{meshCount}', String(api.meshCount))
|
|
38
|
+
.replace('{boneCount}', String(api.boneCount))
|
|
39
|
+
.replace('{animations}', String(api.animations.length)) })] }), children: jsxRuntime.jsx(ModelEditorViewport, { api: api }) })] }));
|
|
34
40
|
};
|
|
35
41
|
|
|
36
42
|
exports.NiceModelEditor = NiceModelEditor;
|
|
@@ -11,10 +11,10 @@ const ModelEditorMenuBar = /* @__PURE__ */ Object.assign(
|
|
|
11
11
|
const { t } = i18n.useNiceTranslation();
|
|
12
12
|
const { editorMode, setEditorMode, fileInputRef, mergeInputRef, addMenuOpen, setAddMenuOpen, addPrimitive, addSceneLight, addCameraObject, addEmpty, selectedNode, duplicateSelected, deleteSelected, exportGLTF, exportOBJ, exportSTL, exportPLY, exportUSDZ, saveToLibrary, clearScene, showGrid, setShowGrid, showAxes, setShowAxes, wireframe, setWireframe, showSkeleton, setShowSkeleton, showLightHelpers, setShowLightHelpers, bgColor, setBgColor, } = api;
|
|
13
13
|
return (jsxRuntime.jsxs("div", { className: ModelEditor_module.menuBar, children: [jsxRuntime.jsx(ui.NiceSelect, { className: ModelEditor_module.modeSelect, value: editorMode, onChange: (val) => setEditorMode(val), options: [
|
|
14
|
-
{ value: 'object', label: 'Object Mode' },
|
|
15
|
-
{ value: 'edit', label: 'Edit Mode' },
|
|
16
|
-
{ value: 'pose', label: 'Pose Mode' },
|
|
17
|
-
] }), jsxRuntime.jsx("div", { className: ModelEditor_module.menuSep }), jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "folder", size: 14 }), " ", t('modelEditorMenuBar.open', 'Open')] }), jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: () => { var _a; return (_a = mergeInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, title: t('modelEditorMenuBar.mergeAnimationsFromFbxGltf', 'Merge animations from FBX/GLTF onto current model'), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "plus", size: 14 }), " ", t('modelEditorMenuBar.mergeAnim', 'Merge Anim')] }), jsxRuntime.jsx("div", { className: ModelEditor_module.menuSep }), jsxRuntime.jsxs("div", { className: ModelEditor_module.menuDropdownWrap, children: [jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: () => setAddMenuOpen(addMenuOpen ? null : 'mesh'), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "plus", size: 14 }), " Add
|
|
14
|
+
{ value: 'object', label: t('modelEditorMenuBar.objectMode', 'Object Mode') },
|
|
15
|
+
{ value: 'edit', label: t('modelEditorMenuBar.editMode', 'Edit Mode') },
|
|
16
|
+
{ value: 'pose', label: t('modelEditorMenuBar.poseMode', 'Pose Mode') },
|
|
17
|
+
] }), jsxRuntime.jsx("div", { className: ModelEditor_module.menuSep }), jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "folder", size: 14 }), " ", t('modelEditorMenuBar.open', 'Open')] }), jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: () => { var _a; return (_a = mergeInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, title: t('modelEditorMenuBar.mergeAnimationsFromFbxGltf', 'Merge animations from FBX/GLTF onto current model'), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "plus", size: 14 }), " ", t('modelEditorMenuBar.mergeAnim', 'Merge Anim')] }), jsxRuntime.jsx("div", { className: ModelEditor_module.menuSep }), jsxRuntime.jsxs("div", { className: ModelEditor_module.menuDropdownWrap, children: [jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: () => setAddMenuOpen(addMenuOpen ? null : 'mesh'), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "plus", size: 14 }), " ", t('modelEditorMenuBar.add', 'Add'), ' ', jsxRuntime.jsx(ui.NiceIcon, { name: "chevron-down", size: 14 })] }), addMenuOpen && (jsxRuntime.jsxs("div", { className: ModelEditor_module.dropdownMenu, children: [jsxRuntime.jsx("div", { className: ModelEditor_module.dropdownTitle, children: t('modelEditorMenuBar.mesh', 'Mesh') }), [
|
|
18
18
|
'cube',
|
|
19
19
|
'sphere',
|
|
20
20
|
'cylinder',
|
|
@@ -28,7 +28,7 @@ const ModelEditorMenuBar = /* @__PURE__ */ Object.assign(
|
|
|
28
28
|
'octahedron',
|
|
29
29
|
'tetrahedron',
|
|
30
30
|
'torusKnot',
|
|
31
|
-
].map((t) => (jsxRuntime.jsx("div", { className: ModelEditor_module.dropdownItem, onClick: () => addPrimitive(t), children: t.charAt(0).toUpperCase() + t.slice(1) }, t))), jsxRuntime.jsx("div", { className: ModelEditor_module.dropdownSep }), jsxRuntime.jsx("div", { className: ModelEditor_module.dropdownTitle, children: t('modelEditorMenuBar.light', 'Light') }), ['point', 'spot', 'directional', 'hemisphere'].map((t) => (jsxRuntime.jsxs("div", { className: ModelEditor_module.dropdownItem, onClick: () => addSceneLight(t), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "lightbulb", size: 14 }), " ", t.charAt(0).toUpperCase() + t.slice(1)] }, t))), jsxRuntime.jsx("div", { className: ModelEditor_module.dropdownSep }), jsxRuntime.jsx("div", { className: ModelEditor_module.dropdownTitle, children: t('modelEditorMenuBar.other', 'Other') }), jsxRuntime.jsxs("div", { className: ModelEditor_module.dropdownItem, onClick: addCameraObject, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "camera", size: 14 }), " ", t('modelEditorMenuBar.camera', 'Camera')] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.dropdownItem, onClick: () => addEmpty('arrows'), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "move", size: 14 }),
|
|
31
|
+
].map((t) => (jsxRuntime.jsx("div", { className: ModelEditor_module.dropdownItem, onClick: () => addPrimitive(t), children: t.charAt(0).toUpperCase() + t.slice(1) }, t))), jsxRuntime.jsx("div", { className: ModelEditor_module.dropdownSep }), jsxRuntime.jsx("div", { className: ModelEditor_module.dropdownTitle, children: t('modelEditorMenuBar.light', 'Light') }), ['point', 'spot', 'directional', 'hemisphere'].map((t) => (jsxRuntime.jsxs("div", { className: ModelEditor_module.dropdownItem, onClick: () => addSceneLight(t), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "lightbulb", size: 14 }), " ", t.charAt(0).toUpperCase() + t.slice(1)] }, t))), jsxRuntime.jsx("div", { className: ModelEditor_module.dropdownSep }), jsxRuntime.jsx("div", { className: ModelEditor_module.dropdownTitle, children: t('modelEditorMenuBar.other', 'Other') }), jsxRuntime.jsxs("div", { className: ModelEditor_module.dropdownItem, onClick: addCameraObject, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "camera", size: 14 }), " ", t('modelEditorMenuBar.camera', 'Camera')] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.dropdownItem, onClick: () => addEmpty('arrows'), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "move", size: 14 }), ' ', t('modelEditorMenuBar.emptyArrows', 'Empty (Arrows)')] })] }))] }), jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: duplicateSelected, disabled: !selectedNode, title: t('modelEditorMenuBar.duplicateSelectedShiftD', 'Duplicate selected (Shift+D)'), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "clipboard", size: 14 }), " ", t('modelEditorMenuBar.duplicate', 'Duplicate')] }), jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: deleteSelected, disabled: !selectedNode, title: t('modelEditorMenuBar.deleteSelectedDelete', 'Delete selected (Delete)'), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "close", size: 14 }), " ", t('modelEditorMenuBar.delete', 'Delete')] }), jsxRuntime.jsx("div", { className: ModelEditor_module.menuSep }), jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: () => exportGLTF(true), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "save", size: 14 }), " ", t('modelEditorMenuBar.exportGlb', 'Export GLB')] }), jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: () => exportGLTF(false), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "file", size: 14 }), " ", t('modelEditorMenuBar.exportGltf', 'Export GLTF')] }), jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: exportOBJ, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "file", size: 14 }), " ", t('modelEditorMenuBar.exportObj', 'Export OBJ')] }), jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: () => exportSTL(true), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "file", size: 14 }), " ", t('modelEditorMenuBar.exportStl', 'Export STL')] }), jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: () => exportPLY(true), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "file", size: 14 }), " ", t('modelEditorMenuBar.exportPly', 'Export PLY')] }), jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: exportUSDZ, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "file", size: 14 }), " ", t('modelEditorMenuBar.exportUsdz', 'Export USDZ')] }), hasOnSaveToLibrary && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: ModelEditor_module.menuSep }), jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: saveToLibrary, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "box", size: 14 }), ' ', t('modelEditorMenuBar.saveToLibrary', 'Save to Library')] })] })), jsxRuntime.jsx("div", { className: ModelEditor_module.menuSep }), jsxRuntime.jsxs(ui.NiceButton, { className: ModelEditor_module.menuBtn, variant: "ghost", size: "sm", onClick: clearScene, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "trash", size: 14 }), " ", t('modelEditorMenuBar.clear', 'Clear')] }), jsxRuntime.jsx("div", { className: ModelEditor_module.menuSep }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showGrid, onChange: () => setShowGrid((v) => !v), label: t('modelEditorMenuBar.grid', 'Grid') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showAxes, onChange: () => setShowAxes((v) => !v), label: t('modelEditorMenuBar.axes', 'Axes') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: wireframe, onChange: () => setWireframe((v) => !v), label: t('modelEditorMenuBar.wire', 'Wire') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showSkeleton, onChange: () => setShowSkeleton((v) => !v), label: t('modelEditorMenuBar.skel', 'Skel') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showLightHelpers, onChange: () => setShowLightHelpers((v) => !v), label: t('modelEditorMenuBar.helpers', 'Helpers') }), jsxRuntime.jsxs("div", { className: ModelEditor_module.menuRight, children: [jsxRuntime.jsx("label", { className: ModelEditor_module.menuLabel, children: t('modelEditorMenuBar.bg', 'BG') }), jsxRuntime.jsx(ui.NiceColorPicker, { value: bgColor, onChange: (c) => setBgColor(c) })] })] }));
|
|
32
32
|
}), { displayName: 'ModelEditorMenuBar' });
|
|
33
33
|
|
|
34
34
|
module.exports = ModelEditorMenuBar;
|
|
@@ -68,7 +68,7 @@ const ModelEditorRightPanel = /* @__PURE__ */ Object.assign(
|
|
|
68
68
|
}, children: [(selectedNode.object.angle * modelEditorTypes.DEG).toFixed(0), "\u00B0"] })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.penumbra', 'Penumbra') }), jsxRuntime.jsx(ui.NiceSlider, { min: 0, max: 1, step: 0.01, value: selectedNode.object.penumbra, onChange: (val) => {
|
|
69
69
|
selectedNode.object.penumbra = val;
|
|
70
70
|
setMatRefresh((n) => n + 1);
|
|
71
|
-
}, style: { flex: 1 } })] })] }))] })] })), selectedNode && selectedNode.object instanceof THREE__namespace.PerspectiveCamera && (jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "camera", size: 14 }), " ", t('modelEditorRightPanel.camera', 'Camera')] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelContent, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children:
|
|
71
|
+
}, style: { flex: 1 } })] })] }))] })] })), selectedNode && selectedNode.object instanceof THREE__namespace.PerspectiveCamera && (jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "camera", size: 14 }), " ", t('modelEditorRightPanel.camera', 'Camera')] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelContent, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.fov', 'FOV') }), jsxRuntime.jsx(ui.NiceSlider, { min: 10, max: 120, step: 1, value: selectedNode.object.fov, onChange: (val) => {
|
|
72
72
|
const cam = selectedNode.object;
|
|
73
73
|
cam.fov = val;
|
|
74
74
|
cam.updateProjectionMatrix();
|
|
@@ -83,7 +83,7 @@ const ModelEditorRightPanel = /* @__PURE__ */ Object.assign(
|
|
|
83
83
|
cam.near = val !== null && val !== void 0 ? val : 0.1;
|
|
84
84
|
cam.updateProjectionMatrix();
|
|
85
85
|
setMatRefresh((n) => n + 1);
|
|
86
|
-
}, style: { width: 60 } })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children:
|
|
86
|
+
}, style: { width: 60 } })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.far', 'Far') }), jsxRuntime.jsx(ui.NiceNumberInput, { className: ModelEditor_module.propInput, step: 1, value: selectedNode.object.far, onChange: (val) => {
|
|
87
87
|
const cam = selectedNode.object;
|
|
88
88
|
cam.far = val !== null && val !== void 0 ? val : 1000;
|
|
89
89
|
cam.updateProjectionMatrix();
|
|
@@ -92,25 +92,29 @@ const ModelEditorRightPanel = /* @__PURE__ */ Object.assign(
|
|
|
92
92
|
fontSize: 10,
|
|
93
93
|
color: 'var(--nice-text-secondary, #888)',
|
|
94
94
|
margin: '0 0 6px',
|
|
95
|
-
}, children:
|
|
95
|
+
}, children: t('modelEditorRightPanel.uploadAVideoAndAi', 'Upload a video and AI will extract 3D pose data to create an animation clip for the current model.') }), jsxRuntime.jsx("div", { className: ModelEditor_module.aiBtnRow, children: jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.aiBtn, variant: "ghost", size: "sm", disabled: aiBusy || !rootObjectRef.current, onClick: () => { var _a; return (_a = videoInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: aiBusy
|
|
96
|
+
? t('modelEditorRightPanel.processing', 'Processing...')
|
|
97
|
+
: t('modelEditorRightPanel.uploadVideo', 'Upload Video') }) }), aiStatus && jsxRuntime.jsx("div", { className: ModelEditor_module.aiStatus, children: aiStatus })] }) })] })] })), propTab === 'material' && (jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "palette", size: 14 }), ' ', t('modelEditorRightPanel.materials', 'Materials')] }), jsxRuntime.jsx("div", { className: ModelEditor_module.panelContent, children: selectedMaterials.length > 0 ? (selectedMaterials.map((mat, i) => (jsxRuntime.jsx(ModelEditorSubComponents.MaterialCard, { material: mat, active: i === selMaterialIdx, onClick: () => setSelMaterialIdx(i), onUpdate: () => setMatRefresh((n) => n + 1) }, i)))) : (jsxRuntime.jsx("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10 }, children: selectedNode
|
|
98
|
+
? t('modelEditorRightPanel.noMaterialsOnThisObject', 'No materials on this object')
|
|
99
|
+
: t('modelEditorRightPanel.selectAMeshToInspect', 'Select a mesh to inspect') })) })] })), propTab === 'world' && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "globe", size: 14 }), ' ', t('modelEditorRightPanel.environment', 'Environment')] }), jsxRuntime.jsx("div", { className: ModelEditor_module.panelContent, children: jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.background', 'Background') }), jsxRuntime.jsx(ui.NiceColorPicker, { value: bgColor, onChange: (c) => setBgColor(c) }), jsxRuntime.jsx("span", { style: { fontSize: 9, color: 'var(--nice-text-secondary, #888)' }, children: bgColor })] }) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "cloud", size: 14 }), " ", t('modelEditorRightPanel.fog', 'Fog')] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelContent, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.enable', 'Enable') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: fogEnabled, onChange: () => setFogEnabled((v) => !v) })] }), fogEnabled && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.color', 'Color') }), jsxRuntime.jsx(ui.NiceColorPicker, { value: fogColor, onChange: (c) => setFogColor(c) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.near', 'Near') }), jsxRuntime.jsx(ui.NiceNumberInput, { className: ModelEditor_module.propInput, value: fogNear, onChange: (val) => setFogNear(val !== null && val !== void 0 ? val : 1), style: { width: 60 } })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.far', 'Far') }), jsxRuntime.jsx(ui.NiceNumberInput, { className: ModelEditor_module.propInput, value: fogFar, onChange: (val) => setFogFar(val !== null && val !== void 0 ? val : 100), style: { width: 60 } })] })] }))] })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "lightbulb", size: 14 }), ' ', t('modelEditorRightPanel.sceneLights', 'Scene Lights')] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelContent, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.showHelpers', 'Show Helpers') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showLightHelpers, onChange: () => setShowLightHelpers((v) => !v) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.grid', 'Grid') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showGrid, onChange: () => setShowGrid((v) => !v) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.axes', 'Axes') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showAxes, onChange: () => setShowAxes((v) => !v) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.skeleton', 'Skeleton') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showSkeleton, onChange: () => setShowSkeleton((v) => !v) })] })] })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "grid", size: 14 }), " ", t('modelEditorRightPanel.snapping', 'Snapping')] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelContent, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.snap', 'Snap') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: snapEnabled, onChange: () => setSnapEnabled((v) => !v) })] }), snapEnabled && (jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.gridSize', 'Grid Size') }), jsxRuntime.jsx(ui.NiceNumberInput, { className: ModelEditor_module.propInput, step: 0.25, min: 0.1, value: snapGrid, onChange: (val) => setSnapGrid(val !== null && val !== void 0 ? val : 1), style: { width: 60 } })] }))] })] })] })), propTab === 'modifiers' && (jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "settings", size: 14 }), ' ', t('modelEditorRightPanel.modifiers', 'Modifiers')] }), jsxRuntime.jsx("div", { className: ModelEditor_module.panelContent, children: selectedNode && selectedNode.object instanceof THREE__namespace.Mesh ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.btnSecondary, variant: "ghost", size: "sm", style: { width: '100%', marginBottom: 4 }, onClick: () => {
|
|
96
100
|
const mesh = selectedNode.object;
|
|
97
101
|
const geo = mesh.geometry;
|
|
98
102
|
const pos = geo.attributes.position;
|
|
99
103
|
if (pos) {
|
|
100
104
|
const wireGeo = new THREE__namespace.WireframeGeometry(geo);
|
|
101
105
|
mesh.geometry = wireGeo;
|
|
102
|
-
setStatusText('Applied Wireframe modifier');
|
|
106
|
+
setStatusText(t('modelEditorRightPanel.appliedWireframeModifier', 'Applied Wireframe modifier'));
|
|
103
107
|
rebuildTree();
|
|
104
108
|
}
|
|
105
|
-
}, children:
|
|
109
|
+
}, children: t('modelEditorRightPanel.wireframeModifier', '+ Wireframe Modifier') }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.btnSecondary, variant: "ghost", size: "sm", style: { width: '100%', marginBottom: 4 }, onClick: () => {
|
|
106
110
|
const mesh = selectedNode.object;
|
|
107
111
|
mesh.geometry.computeVertexNormals();
|
|
108
|
-
setStatusText('Recomputed normals');
|
|
112
|
+
setStatusText(t('modelEditorRightPanel.recomputedNormals', 'Recomputed normals'));
|
|
109
113
|
}, children: t('modelEditorRightPanel.recomputeNormals', 'Recompute Normals') }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.btnSecondary, variant: "ghost", size: "sm", style: { width: '100%', marginBottom: 4 }, onClick: () => {
|
|
110
114
|
const mesh = selectedNode.object;
|
|
111
115
|
const geo = mesh.geometry;
|
|
112
116
|
geo.center();
|
|
113
|
-
setStatusText('Centered geometry');
|
|
117
|
+
setStatusText(t('modelEditorRightPanel.centeredGeometry', 'Centered geometry'));
|
|
114
118
|
setMatRefresh((n) => n + 1);
|
|
115
119
|
}, children: t('modelEditorRightPanel.centerGeometry', 'Center Geometry') }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.btnSecondary, variant: "ghost", size: "sm", style: { width: '100%', marginBottom: 4 }, onClick: () => {
|
|
116
120
|
const mesh = selectedNode.object;
|
|
@@ -125,9 +129,9 @@ const ModelEditorRightPanel = /* @__PURE__ */ Object.assign(
|
|
|
125
129
|
}
|
|
126
130
|
idx.needsUpdate = true;
|
|
127
131
|
geo.computeVertexNormals();
|
|
128
|
-
setStatusText('Flipped normals');
|
|
132
|
+
setStatusText(t('modelEditorRightPanel.flippedNormals', 'Flipped normals'));
|
|
129
133
|
}
|
|
130
|
-
}, children: t('modelEditorRightPanel.flipNormals', 'Flip Normals') })] })) : (jsxRuntime.jsx("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10 }, children: t('modelEditorRightPanel.selectAMeshToApply', 'Select a mesh to apply modifiers') })) })] })), propTab === 'physics' && (jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "zap", size: 14 }),
|
|
134
|
+
}, children: t('modelEditorRightPanel.flipNormals', 'Flip Normals') })] })) : (jsxRuntime.jsx("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10 }, children: t('modelEditorRightPanel.selectAMeshToApply', 'Select a mesh to apply modifiers') })) })] })), propTab === 'physics' && (jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "zap", size: 14 }), ' ', t('modelEditorRightPanel.physicsInfo', 'Physics (Info)')] }), jsxRuntime.jsx("div", { className: ModelEditor_module.panelContent, children: jsxRuntime.jsxs("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10, lineHeight: 1.5 }, children: [t('modelEditorRightPanel.physicsSimulationPreviewIsNot', 'Physics simulation preview is not yet available.'), jsxRuntime.jsx("br", {}), jsxRuntime.jsx("br", {}), t('modelEditorRightPanel.plannedFeatures', 'Planned features:'), jsxRuntime.jsx("br", {}), t('modelEditorRightPanel.rigidBodySimulation', '• Rigid body simulation'), jsxRuntime.jsx("br", {}), t('modelEditorRightPanel.softBodyDynamics', '• Soft body dynamics'), jsxRuntime.jsx("br", {}), t('modelEditorRightPanel.clothSimulation', '• Cloth simulation'), jsxRuntime.jsx("br", {}), t('modelEditorRightPanel.particleSystems', '• Particle systems')] }) })] }))] }));
|
|
131
135
|
}), { displayName: 'ModelEditorRightPanel' });
|
|
132
136
|
|
|
133
137
|
module.exports = ModelEditorRightPanel;
|
|
@@ -108,9 +108,15 @@ const MaterialCard = ({ material, active, onClick, onUpdate }) => {
|
|
|
108
108
|
mat.needsUpdate = true;
|
|
109
109
|
onUpdate();
|
|
110
110
|
}, options: [
|
|
111
|
-
{
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
{
|
|
112
|
+
value: String(THREE__namespace.FrontSide),
|
|
113
|
+
label: t('modelEditorSubComponents.front', 'Front'),
|
|
114
|
+
},
|
|
115
|
+
{ value: String(THREE__namespace.BackSide), label: t('modelEditorSubComponents.back', 'Back') },
|
|
116
|
+
{
|
|
117
|
+
value: String(THREE__namespace.DoubleSide),
|
|
118
|
+
label: t('modelEditorSubComponents.double', 'Double'),
|
|
119
|
+
},
|
|
114
120
|
] })] }), hasMap && mat.map && (jsxRuntime.jsxs("div", { className: ModelEditor_module.textureSlot, children: [jsxRuntime.jsx("div", { className: ModelEditor_module.textureThumb, children: mat.map.image != null && (jsxRuntime.jsx("img", { src: (_a = mat.map.image.src) !== null && _a !== void 0 ? _a : '', alt: "diffuse" })) }), jsxRuntime.jsxs("div", { className: ModelEditor_module.textureInfo, children: [jsxRuntime.jsx("div", { className: ModelEditor_module.textureName, children: t('modelEditorSubComponents.diffuseMap', 'Diffuse Map') }), jsxRuntime.jsxs("div", { style: { fontSize: 9, color: 'var(--nice-text-secondary, #666)' }, children: [(_b = mat.map.image) === null || _b === void 0 ? void 0 : _b.width, "\u00D7", (_c = mat.map.image) === null || _c === void 0 ? void 0 : _c.height] })] })] })), hasNormalMap && mat.normalMap && (jsxRuntime.jsxs("div", { className: ModelEditor_module.textureSlot, children: [jsxRuntime.jsx("div", { className: ModelEditor_module.textureThumb, children: mat.normalMap.image != null && (jsxRuntime.jsx("img", { src: (_d = mat.normalMap.image.src) !== null && _d !== void 0 ? _d : '', alt: "normal" })) }), jsxRuntime.jsx("div", { className: ModelEditor_module.textureInfo, children: jsxRuntime.jsx("div", { className: ModelEditor_module.textureName, children: t('modelEditorSubComponents.normalMap', 'Normal Map') }) })] }))] }));
|
|
115
121
|
};
|
|
116
122
|
|
|
@@ -11,7 +11,9 @@ const ModelEditorTimeline = /* @__PURE__ */ Object.assign(
|
|
|
11
11
|
/* @__PURE__ */ React.memo(({ api }) => {
|
|
12
12
|
const { t } = i18n.useNiceTranslation();
|
|
13
13
|
const { bottomCollapsed, setBottomCollapsed, animations, activeAnimIdx, isPlaying, animTime, animDuration, animSpeed, setAnimSpeed, loopAnim, setLoopAnim, seekAnim, togglePlay, stopAnim, playClip, mergeInputRef, } = api;
|
|
14
|
-
return (jsxRuntime.jsxs("div", { className: `${ModelEditor_module.bottomPanel} ${bottomCollapsed ? ModelEditor_module.bottomPanelCollapsed : ''}`, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.bottomPanelHeader, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.bottomToggle, onClick: () => setBottomCollapsed((c) => !c), children: jsxRuntime.jsx(ui.NiceIcon, { name: bottomCollapsed ? 'chevron-right' : 'chevron-down', size: 14 }) }), jsxRuntime.
|
|
14
|
+
return (jsxRuntime.jsxs("div", { className: `${ModelEditor_module.bottomPanel} ${bottomCollapsed ? ModelEditor_module.bottomPanelCollapsed : ''}`, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.bottomPanelHeader, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.bottomToggle, onClick: () => setBottomCollapsed((c) => !c), children: jsxRuntime.jsx(ui.NiceIcon, { name: bottomCollapsed ? 'chevron-right' : 'chevron-down', size: 14 }) }), jsxRuntime.jsx("span", { className: ModelEditor_module.bottomPanelTitle, onClick: () => setBottomCollapsed((c) => !c), children: animations.length !== 1
|
|
15
|
+
? t('modelEditorTimeline.animationClipsMany', 'Animation ({animations} clips)').replace('{animations}', String(animations.length))
|
|
16
|
+
: t('modelEditorTimeline.animationClipsOne', 'Animation ({animations} clip)').replace('{animations}', String(animations.length)) }), jsxRuntime.jsx("div", { className: ModelEditor_module.menuSep }), jsxRuntime.jsxs("div", { className: ModelEditor_module.transportBar, children: [jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.transportBtn, variant: "ghost", size: "sm", onClick: () => seekAnim(0), title: t('modelEditorTimeline.goToStart', 'Go to start'), "aria-label": t('modelEditorTimeline.goToStart', 'Go to start'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "chevron-left", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.transportBtn, variant: isPlaying ? 'primary' : 'ghost', size: "sm", onClick: togglePlay, title: t('modelEditorTimeline.playPauseSpace', 'Play/Pause (Space)'), "aria-label": t('modelEditorTimeline.playPauseSpace', 'Play/Pause (Space)'), children: jsxRuntime.jsx(ui.NiceIcon, { name: isPlaying ? 'pause' : 'play', size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.transportBtn, variant: "ghost", size: "sm", onClick: stopAnim, title: t('modelEditorTimeline.stop', 'Stop'), "aria-label": t('modelEditorTimeline.stop', 'Stop'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "stop", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.transportBtn, variant: loopAnim ? 'primary' : 'ghost', size: "sm", onClick: () => setLoopAnim((v) => !v), title: t('modelEditorTimeline.loop', 'Loop'), "aria-label": t('modelEditorTimeline.loop', 'Loop'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "refresh", size: 16 }) })] }), jsxRuntime.jsxs("span", { className: ModelEditor_module.transportTime, children: [modelEditorTypes.fmtTime(animTime), " / ", modelEditorTypes.fmtTime(animDuration)] }), jsxRuntime.jsx("div", { className: ModelEditor_module.menuSep }), jsxRuntime.jsx("label", { className: ModelEditor_module.menuLabel, children: t('modelEditorTimeline.speed', 'Speed') }), jsxRuntime.jsx(ui.NiceSlider, { min: 0.1, max: 3, step: 0.1, value: animSpeed, onChange: (val) => setAnimSpeed(val), style: { width: 60 } }), jsxRuntime.jsxs("span", { className: ModelEditor_module.menuLabel, children: [animSpeed.toFixed(1), "\u00D7"] })] }), !bottomCollapsed && (jsxRuntime.jsxs("div", { className: ModelEditor_module.animListArea, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.animList, children: [animations.length === 0 && (jsxRuntime.jsx("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10, padding: 8 }, children: t('modelEditorTimeline.noAnimationsLoaded', 'No animations loaded') })), animations.map((anim, i) => (jsxRuntime.jsxs("div", { className: `${ModelEditor_module.animItem} ${i === activeAnimIdx ? ModelEditor_module.animItemActive : ''}`, onClick: () => playClip(i), children: [jsxRuntime.jsx("span", { children: anim.name }), jsxRuntime.jsxs("span", { className: ModelEditor_module.animDuration, children: [anim.duration.toFixed(1), "s"] })] }, `${anim.name}-${i}`))), jsxRuntime.jsx("div", { style: { padding: '4px 8px' }, children: jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.btnSecondary, variant: "ghost", size: "sm", style: { width: '100%', fontSize: 10 }, onClick: () => { var _a; return (_a = mergeInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: t('modelEditorTimeline.addAnimationFile', '+ Add Animation File') }) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.timelineArea, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.timelineRuler, children: [animDuration > 0 &&
|
|
15
17
|
Array.from({ length: Math.ceil(animDuration) + 1 }, (_, i) => {
|
|
16
18
|
const pct = (i / animDuration) * 100;
|
|
17
19
|
return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx("div", { className: ModelEditor_module.rulerTick, style: { left: `${pct}%` } }), jsxRuntime.jsxs("span", { className: ModelEditor_module.rulerLabel, style: { left: `${pct}%` }, children: [i, "s"] })] }, i));
|
|
@@ -10,7 +10,7 @@ const ModelEditorToolbar = /* @__PURE__ */ Object.assign(
|
|
|
10
10
|
/* @__PURE__ */ React.memo(({ api }) => {
|
|
11
11
|
const { t } = i18n.useNiceTranslation();
|
|
12
12
|
const { transformMode, setTransformMode, snapEnabled, setSnapEnabled, gizmoSpace, setGizmoSpace, rootObjectRef, focusOnObject, setCameraPreset, deleteSelected, duplicateSelected, selectedNode, } = api;
|
|
13
|
-
return (jsxRuntime.jsxs("div", { className: ModelEditor_module.toolbar, children: [jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: transformMode === 'translate' ? 'primary' : 'ghost', size: "sm", onClick: () => setTransformMode('translate'), title: t('modelEditorToolbar.translateG', 'Translate (G)'), "aria-label": t('modelEditorToolbar.translateG', 'Translate (G)'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "move", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: transformMode === 'rotate' ? 'primary' : 'ghost', size: "sm", onClick: () => setTransformMode('rotate'), title: t('modelEditorToolbar.rotateR', 'Rotate (R)'), "aria-label": t('modelEditorToolbar.rotateR', 'Rotate (R)'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "refresh", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: transformMode === 'scale' ? 'primary' : 'ghost', size: "sm", onClick: () => setTransformMode('scale'), title: t('modelEditorToolbar.scaleS', 'Scale (S)'), "aria-label": t('modelEditorToolbar.scaleS', 'Scale (S)'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "hexagon", size: 16 }) }), jsxRuntime.jsx("div", { className: ModelEditor_module.toolSep }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: snapEnabled ? 'primary' : 'ghost', size: "sm", onClick: () => setSnapEnabled((v) => !v), title:
|
|
13
|
+
return (jsxRuntime.jsxs("div", { className: ModelEditor_module.toolbar, children: [jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: transformMode === 'translate' ? 'primary' : 'ghost', size: "sm", onClick: () => setTransformMode('translate'), title: t('modelEditorToolbar.translateG', 'Translate (G)'), "aria-label": t('modelEditorToolbar.translateG', 'Translate (G)'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "move", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: transformMode === 'rotate' ? 'primary' : 'ghost', size: "sm", onClick: () => setTransformMode('rotate'), title: t('modelEditorToolbar.rotateR', 'Rotate (R)'), "aria-label": t('modelEditorToolbar.rotateR', 'Rotate (R)'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "refresh", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: transformMode === 'scale' ? 'primary' : 'ghost', size: "sm", onClick: () => setTransformMode('scale'), title: t('modelEditorToolbar.scaleS', 'Scale (S)'), "aria-label": t('modelEditorToolbar.scaleS', 'Scale (S)'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "hexagon", size: 16 }) }), jsxRuntime.jsx("div", { className: ModelEditor_module.toolSep }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: snapEnabled ? 'primary' : 'ghost', size: "sm", onClick: () => setSnapEnabled((v) => !v), title: t('modelEditorToolbar.snapToGridState', 'Snap to Grid ({state})').replace('{state}', snapEnabled ? t('modelEditorToolbar.on', 'ON') : t('modelEditorToolbar.off', 'OFF')), "aria-label": t('modelEditorToolbar.snapToGrid', 'Snap to Grid'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "grid", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: () => setGizmoSpace((s) => (s === 'local' ? 'world' : 'local')), title: t('modelEditorToolbar.orientation', 'Orientation: {gizmoSpace}').replace('{gizmoSpace}', String(gizmoSpace)), "aria-label": t('modelEditorToolbar.orientation', 'Orientation: {gizmoSpace}').replace('{gizmoSpace}', String(gizmoSpace)), children: jsxRuntime.jsx(ui.NiceIcon, { name: gizmoSpace === 'local' ? 'target' : 'globe', size: 16 }) }), jsxRuntime.jsx("div", { className: ModelEditor_module.toolSep }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: () => rootObjectRef.current && focusOnObject(rootObjectRef.current), title: t('modelEditorToolbar.focusF', 'Focus (F)'), "aria-label": t('modelEditorToolbar.focusF', 'Focus (F)'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "target", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: () => setCameraPreset('front'), title: t('modelEditorToolbar.frontViewNumpad1', 'Front view (Numpad 1)'), children: "1" }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: () => setCameraPreset('right'), title: t('modelEditorToolbar.rightViewNumpad3', 'Right view (Numpad 3)'), children: "3" }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: () => setCameraPreset('top'), title: t('modelEditorToolbar.topViewNumpad7', 'Top view (Numpad 7)'), children: "7" }), jsxRuntime.jsx("div", { className: ModelEditor_module.toolSep }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: deleteSelected, title: t('modelEditorToolbar.deleteSelectedDel', 'Delete selected (Del)'), "aria-label": t('modelEditorToolbar.deleteSelectedDel', 'Delete selected (Del)'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "trash", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: duplicateSelected, title: t('modelEditorToolbar.duplicateShiftD', 'Duplicate (Shift+D)'), disabled: !selectedNode, "aria-label": t('modelEditorToolbar.duplicateShiftD', 'Duplicate (Shift+D)'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "clipboard", size: 16 }) })] }));
|
|
14
14
|
}), { displayName: 'ModelEditorToolbar' });
|
|
15
15
|
|
|
16
16
|
module.exports = ModelEditorToolbar;
|
|
@@ -18,7 +18,10 @@ const ModelEditorViewport = /* @__PURE__ */ Object.assign(
|
|
|
18
18
|
/* @__PURE__ */ React.memo(({ api }) => {
|
|
19
19
|
const { t } = i18n.useNiceTranslation();
|
|
20
20
|
const { mountRef, dragOver, handleDragOver, handleDragLeave, handleDrop, handleViewportClick, handleContextMenu, editorMode, polyCount, meshCount, boneCount, shadingMode, setShadingMode, gizmoSpace, setGizmoSpace, contextMenu, closeContextMenu, addPrimitive, addSceneLight, duplicateSelected, deleteSelected, selectedNode, rootObjectRef, focusOnObject, snapEnabled, setSnapEnabled, } = api;
|
|
21
|
-
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { ref: mountRef, className: `${ModelEditor_module.viewportContainer} ${dragOver ? ModelEditor_module.dragOver : ''}`, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, onClick: handleViewportClick, onContextMenu: handleContextMenu, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.viewportOverlay, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.viewportBadge, children: t(...MODE_BADGE[editorMode]) }), jsxRuntime.
|
|
21
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { ref: mountRef, className: `${ModelEditor_module.viewportContainer} ${dragOver ? ModelEditor_module.dragOver : ''}`, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, onClick: handleViewportClick, onContextMenu: handleContextMenu, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.viewportOverlay, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.viewportBadge, children: t(...MODE_BADGE[editorMode]) }), jsxRuntime.jsx("span", { className: ModelEditor_module.viewportBadge, children: t('modelEditorViewport.trisMeshesBones', '{polyCount} tris · {meshCount} meshes · {boneCount} bones')
|
|
22
|
+
.replace('{polyCount}', String(polyCount.toLocaleString()))
|
|
23
|
+
.replace('{meshCount}', String(meshCount))
|
|
24
|
+
.replace('{boneCount}', String(boneCount)) })] }), jsxRuntime.jsx("div", { className: ModelEditor_module.viewportShadingBar, children: ['wireframe', 'solid', 'material', 'rendered'].map((m) => (jsxRuntime.jsx("button", { className: `${ModelEditor_module.shadingBtn} ${shadingMode === m ? ModelEditor_module.shadingBtnActive : ''}`, onClick: (e) => {
|
|
22
25
|
e.stopPropagation();
|
|
23
26
|
setShadingMode(m);
|
|
24
27
|
}, title: m.charAt(0).toUpperCase() + m.slice(1), "aria-label": m.charAt(0).toUpperCase() + m.slice(1), children: jsxRuntime.jsx(ui.NiceIcon, { name: m === 'wireframe'
|
|
@@ -30,7 +33,7 @@ const ModelEditorViewport = /* @__PURE__ */ Object.assign(
|
|
|
30
33
|
: 'lightbulb', size: 16 }) }, m))) }), jsxRuntime.jsx("div", { className: ModelEditor_module.viewportGizmoInfo, children: jsxRuntime.jsx("button", { className: ModelEditor_module.shadingBtn, onClick: (e) => {
|
|
31
34
|
e.stopPropagation();
|
|
32
35
|
setGizmoSpace((s) => (s === 'local' ? 'world' : 'local'));
|
|
33
|
-
}, title:
|
|
36
|
+
}, title: t('modelEditorViewport.orientation', 'Orientation: {gizmoSpace}').replace('{gizmoSpace}', String(gizmoSpace)), "aria-label": t('modelEditorViewport.orientation', 'Orientation: {gizmoSpace}').replace('{gizmoSpace}', String(gizmoSpace)), children: gizmoSpace === 'local' ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "target", size: 14 }), " ", t('modelEditorViewport.local', 'Local')] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "globe", size: 14 }), " ", t('modelEditorViewport.world', 'World')] })) }) }), dragOver && (jsxRuntime.jsx("div", { className: ModelEditor_module.viewportDropZone, children: jsxRuntime.jsx("span", { children: t('modelEditorViewport.drop3dFilesHere', 'Drop 3D files here') }) }))] }), contextMenu && (jsxRuntime.jsxs("div", { className: ModelEditor_module.contextMenu, style: { top: contextMenu.y, left: contextMenu.x }, onClick: closeContextMenu, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.contextItem, onClick: () => addPrimitive('cube'), children: [jsxRuntime.jsx("span", { children: t('modelEditorViewport.addCube', 'Add Cube') }), jsxRuntime.jsx("span", { className: ModelEditor_module.contextKey, children: "Shift+A" })] }), jsxRuntime.jsx("div", { className: ModelEditor_module.contextItem, onClick: () => addPrimitive('sphere'), children: jsxRuntime.jsx("span", { children: t('modelEditorViewport.addSphere', 'Add Sphere') }) }), jsxRuntime.jsx("div", { className: ModelEditor_module.contextItem, onClick: () => addSceneLight('point'), children: jsxRuntime.jsx("span", { children: t('modelEditorViewport.addPointLight', 'Add Point Light') }) }), jsxRuntime.jsx("div", { className: ModelEditor_module.contextSep }), jsxRuntime.jsxs("div", { className: ModelEditor_module.contextItem, onClick: duplicateSelected, style: !selectedNode ? { opacity: 0.4, pointerEvents: 'none' } : {}, children: [jsxRuntime.jsx("span", { children: t('modelEditorViewport.duplicate', 'Duplicate') }), jsxRuntime.jsx("span", { className: ModelEditor_module.contextKey, children: "Shift+D" })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.contextItem, onClick: deleteSelected, style: !selectedNode ? { opacity: 0.4, pointerEvents: 'none' } : {}, children: [jsxRuntime.jsx("span", { children: t('modelEditorViewport.delete', 'Delete') }), jsxRuntime.jsx("span", { className: ModelEditor_module.contextKey, children: "Del" })] }), jsxRuntime.jsx("div", { className: ModelEditor_module.contextSep }), jsxRuntime.jsxs("div", { className: ModelEditor_module.contextItem, onClick: () => {
|
|
34
37
|
if (rootObjectRef.current) {
|
|
35
38
|
focusOnObject(rootObjectRef.current);
|
|
36
39
|
}
|
|
@@ -7,6 +7,7 @@ var i18n = require('../core/i18n.js');
|
|
|
7
7
|
var ModelViewer_module = require('./ModelViewer.module.css.js');
|
|
8
8
|
var useModelViewer = require('./useModelViewer.js');
|
|
9
9
|
|
|
10
|
+
const NAV_MODES = ['orbit', 'fps', 'fly', 'walkthrough'];
|
|
10
11
|
const NAV_META = {
|
|
11
12
|
orbit: { icon: 'globe', label: 'Orbit' },
|
|
12
13
|
fps: { icon: 'user', label: 'FPS' },
|
|
@@ -40,10 +41,9 @@ const NiceModelViewer = ({ className, showToolbar = true, showStatusBar = true,
|
|
|
40
41
|
}
|
|
41
42
|
}, [api, onFileDrop]);
|
|
42
43
|
/* ── nav cycle ── */
|
|
43
|
-
const navModes = ['orbit', 'fps', 'fly', 'walkthrough'];
|
|
44
44
|
const cycleNav = React.useCallback(() => {
|
|
45
|
-
const idx =
|
|
46
|
-
api.setNavigationMode(
|
|
45
|
+
const idx = NAV_MODES.indexOf(api.navigationMode);
|
|
46
|
+
api.setNavigationMode(NAV_MODES[(idx + 1) % NAV_MODES.length]);
|
|
47
47
|
}, [api]);
|
|
48
48
|
return (jsxRuntime.jsxs("div", { className: `${ModelViewer_module.root} ${className !== null && className !== void 0 ? className : ''}`, children: [showToolbar && (jsxRuntime.jsxs("div", { className: ModelViewer_module.toolbar, children: [jsxRuntime.jsxs(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: "ghost", size: "sm", title: t('modelViewer.openFile', 'Open file…'), onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "folder", size: 14 }), " ", t('modelViewer.open', 'Open')] }), jsxRuntime.jsx("input", { ref: fileInputRef, type: "file", accept: ".fbx,.glb,.gltf,.obj,.dae,.stl,.ply", style: { display: 'none' }, onChange: (e) => {
|
|
49
49
|
var _a;
|
|
@@ -62,7 +62,7 @@ const NiceModelViewer = ({ className, showToolbar = true, showStatusBar = true,
|
|
|
62
62
|
a.href = url;
|
|
63
63
|
a.download = 'screenshot.png';
|
|
64
64
|
a.click();
|
|
65
|
-
}, title: t('modelViewer.screenshot', 'Screenshot'), "aria-label": t('modelViewer.screenshot', 'Screenshot'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "camera", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: "ghost", size: "sm", onClick: api.toggleFullscreen, title: t('modelViewer.fullscreen', 'Fullscreen'), "aria-label": t('modelViewer.fullscreen', 'Fullscreen'), children: jsxRuntime.jsx(ui.NiceIcon, { name: api.isFullscreen ? 'collapse' : 'expand', size: 16 }) }), toolbarExtra, showInfoPanel && (jsxRuntime.jsx("div", { className: ModelViewer_module.toolRight, children: jsxRuntime.jsx(ui.NiceButton, { className: `${ModelViewer_module.toolBtn} ${panelOpen ? ModelViewer_module.active : ''}`, variant: panelOpen ? 'primary' : 'ghost', size: "sm", onClick: () => setPanelOpen(!panelOpen), title: t('modelViewer.infoPanel', 'Info panel'), "aria-label": t('modelViewer.infoPanel', 'Info panel'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "info", size: 16 }) }) }))] })), jsxRuntime.jsxs("div", { className: ModelViewer_module.viewport, ref: api.mountRef, tabIndex: 0, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, children: [api.loading && (jsxRuntime.jsxs("div", { className: ModelViewer_module.loadingOverlay, children: [jsxRuntime.jsx("span", { className: ModelViewer_module.loadingText, children:
|
|
65
|
+
}, title: t('modelViewer.screenshot', 'Screenshot'), "aria-label": t('modelViewer.screenshot', 'Screenshot'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "camera", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: "ghost", size: "sm", onClick: api.toggleFullscreen, title: t('modelViewer.fullscreen', 'Fullscreen'), "aria-label": t('modelViewer.fullscreen', 'Fullscreen'), children: jsxRuntime.jsx(ui.NiceIcon, { name: api.isFullscreen ? 'collapse' : 'expand', size: 16 }) }), toolbarExtra, showInfoPanel && (jsxRuntime.jsx("div", { className: ModelViewer_module.toolRight, children: jsxRuntime.jsx(ui.NiceButton, { className: `${ModelViewer_module.toolBtn} ${panelOpen ? ModelViewer_module.active : ''}`, variant: panelOpen ? 'primary' : 'ghost', size: "sm", onClick: () => setPanelOpen(!panelOpen), title: t('modelViewer.infoPanel', 'Info panel'), "aria-label": t('modelViewer.infoPanel', 'Info panel'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "info", size: 16 }) }) }))] })), jsxRuntime.jsxs("div", { className: ModelViewer_module.viewport, ref: api.mountRef, tabIndex: 0, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, children: [api.loading && (jsxRuntime.jsxs("div", { className: ModelViewer_module.loadingOverlay, children: [jsxRuntime.jsx("span", { className: ModelViewer_module.loadingText, children: t('modelViewer.loadingModel', 'Loading model…') }), jsxRuntime.jsx("div", { className: ModelViewer_module.loadingBar, children: jsxRuntime.jsx("div", { className: ModelViewer_module.loadingBarFill, style: { width: `${(api.loadProgress * 100).toFixed(0)}%` } }) })] })), api.error && (jsxRuntime.jsxs("div", { className: ModelViewer_module.errorOverlay, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "warning", size: 28 }), jsxRuntime.jsx("span", { style: { marginTop: 8 }, children: api.error })] })), dragOver && (jsxRuntime.jsx("div", { className: ModelViewer_module.dropZone, children: t('modelViewer.drop3dModelFileHere', 'Drop 3D model file here') }))] }), jsxRuntime.jsxs("div", { className: `${ModelViewer_module.sidePanel} ${!panelOpen ? ModelViewer_module.hidden : ''}`, children: [jsxRuntime.jsxs("div", { className: ModelViewer_module.panelSection, children: [jsxRuntime.jsx("div", { className: ModelViewer_module.panelTitle, children: t('modelViewer.modelInfo', 'Model Info') }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: t('modelViewer.triangles', 'Triangles') }), jsxRuntime.jsx("span", { children: api.polyCount.toLocaleString() })] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: t('modelViewer.meshes', 'Meshes') }), jsxRuntime.jsx("span", { children: api.meshCount })] }), api.dimensions && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: t('modelViewer.width', 'Width') }), jsxRuntime.jsx("span", { children: api.dimensions.width })] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: t('modelViewer.height', 'Height') }), jsxRuntime.jsx("span", { children: api.dimensions.height })] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: t('modelViewer.depth', 'Depth') }), jsxRuntime.jsx("span", { children: api.dimensions.depth })] })] }))] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelSection, children: [jsxRuntime.jsx("div", { className: ModelViewer_module.panelTitle, children: t('modelViewer.navigation', 'Navigation') }), NAV_MODES.map((m) => (jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: api.navigationMode === m ? 'primary' : 'ghost', size: "sm", onClick: () => api.setNavigationMode(m), children: jsxRuntime.jsx(NavLabel, { mode: m }) }, m)))] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelSection, children: [jsxRuntime.jsx("div", { className: ModelViewer_module.panelTitle, children: t('modelViewer.autoRotate', 'Auto Rotate') }), jsxRuntime.jsx(ui.NiceSlider, { min: 0, max: 10, step: 0.5, value: api.autoRotateSpeed, onChange: (val) => api.setAutoRotateSpeed(val), style: { width: '100%' } })] }), api.animations.length > 0 && (jsxRuntime.jsxs("div", { className: ModelViewer_module.panelSection, children: [jsxRuntime.jsx("div", { className: ModelViewer_module.panelTitle, children: t('modelViewer.animations', 'Animations') }), api.animations.map((name, i) => (jsxRuntime.jsxs(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: api.activeAnimIdx === i ? 'primary' : 'ghost', size: "sm", onClick: () => api.playAnimation(i), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "play", size: 14 }), " ", name] }, i)))] }))] }), jsxRuntime.jsxs("div", { className: `${ModelViewer_module.animBar} ${api.animations.length === 0 ? ModelViewer_module.hidden : ''}`, children: [jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: "ghost", size: "sm", onClick: api.togglePlay, "aria-label": t('modelViewer.playPause', 'Play/Pause'), children: jsxRuntime.jsx(ui.NiceIcon, { name: api.isPlaying ? 'pause' : 'play', size: 16 }) }), jsxRuntime.jsx(ui.NiceSelect, { className: ModelViewer_module.animSelect, value: String(api.activeAnimIdx), onChange: (val) => api.playAnimation(parseInt(val)), options: api.animations.map((n, i) => ({ value: String(i), label: n })) }), jsxRuntime.jsx(ui.NiceSlider, { className: ModelViewer_module.animSlider, min: 0, max: api.animDuration || 1, step: 0.01, value: api.animTime, onChange: (val) => api.seekAnim(val) }), jsxRuntime.jsxs("span", { className: ModelViewer_module.toolLabel, children: [api.animTime.toFixed(1), "s / ", api.animDuration.toFixed(1), "s"] })] }), showStatusBar && (jsxRuntime.jsxs("div", { className: ModelViewer_module.statusBar, children: [jsxRuntime.jsx("span", { children: api.statusText }), jsxRuntime.jsxs("div", { className: ModelViewer_module.statusRight, children: [jsxRuntime.jsx("span", { children: jsxRuntime.jsx(NavLabel, { mode: api.navigationMode }) }), api.dimensions && (jsxRuntime.jsxs("span", { children: [api.dimensions.width, "\u00D7", api.dimensions.height, "\u00D7", api.dimensions.depth] }))] })] })), children === null || children === void 0 ? void 0 : children(api)] }));
|
|
66
66
|
};
|
|
67
67
|
|
|
68
68
|
exports.NiceModelViewer = NiceModelViewer;
|
|
@@ -265,22 +265,22 @@ const NiceArmatureEditor = ({ skeleton, skinnedMesh: _skinnedMesh, scene, camera
|
|
|
265
265
|
} }), jsxRuntime.jsx("button", { type: "button", disabled: !newBoneName.trim(), onClick: () => {
|
|
266
266
|
addBone(selectedBoneId, newBoneName.trim());
|
|
267
267
|
setNewBoneName('');
|
|
268
|
-
}, children:
|
|
268
|
+
}, children: t('armatureEditor.add', '+ Add') }), jsxRuntime.jsx("button", { type: "button", onClick: () => removeBone(selectedBoneId), "aria-label": t('armatureEditor.removeBone', 'Remove bone'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "trash", size: 14 }) })] })), selectedBone && (jsxRuntime.jsxs("div", { style: {
|
|
269
269
|
display: 'grid',
|
|
270
270
|
gap: 4,
|
|
271
271
|
fontSize: 12,
|
|
272
272
|
border: '1px solid var(--nice-border, #e2e8f0)',
|
|
273
273
|
borderRadius: 6,
|
|
274
274
|
padding: 8,
|
|
275
|
-
}, children: [jsxRuntime.jsx("strong", { children: selectedBone.name }), jsxRuntime.
|
|
275
|
+
}, children: [jsxRuntime.jsx("strong", { children: selectedBone.name }), jsxRuntime.jsx("label", { children: t('armatureEditor.rotationX', 'Rotation X: {x}°').replace('{x}', String((selectedBone.localRotation.x * (180 / Math.PI)).toFixed(1))) }), jsxRuntime.jsx("input", { type: "range", min: -180, max: 180, value: selectedBone.localRotation.x * (180 / Math.PI), onChange: (e) => {
|
|
276
276
|
const rot = selectedBone.localRotation.clone();
|
|
277
277
|
rot.x = parseFloat(e.target.value) * (Math.PI / 180);
|
|
278
278
|
setBoneRotation(selectedBone.id, rot);
|
|
279
|
-
} }), jsxRuntime.
|
|
279
|
+
} }), jsxRuntime.jsx("label", { children: t('armatureEditor.rotationY', 'Rotation Y: {y}°').replace('{y}', String((selectedBone.localRotation.y * (180 / Math.PI)).toFixed(1))) }), jsxRuntime.jsx("input", { type: "range", min: -180, max: 180, value: selectedBone.localRotation.y * (180 / Math.PI), onChange: (e) => {
|
|
280
280
|
const rot = selectedBone.localRotation.clone();
|
|
281
281
|
rot.y = parseFloat(e.target.value) * (Math.PI / 180);
|
|
282
282
|
setBoneRotation(selectedBone.id, rot);
|
|
283
|
-
} }), jsxRuntime.
|
|
283
|
+
} }), jsxRuntime.jsx("label", { children: t('armatureEditor.rotationZ', 'Rotation Z: {z}°').replace('{z}', String((selectedBone.localRotation.z * (180 / Math.PI)).toFixed(1))) }), jsxRuntime.jsx("input", { type: "range", min: -180, max: 180, value: selectedBone.localRotation.z * (180 / Math.PI), onChange: (e) => {
|
|
284
284
|
const rot = selectedBone.localRotation.clone();
|
|
285
285
|
rot.z = parseFloat(e.target.value) * (Math.PI / 180);
|
|
286
286
|
setBoneRotation(selectedBone.id, rot);
|
|
@@ -233,7 +233,9 @@ const NiceMorphTargetEditor = ({ mesh, onInfluenceChange, className = '', tutori
|
|
|
233
233
|
padding: '4px 8px',
|
|
234
234
|
border: '1px solid var(--nice-border, #e2e8f0)',
|
|
235
235
|
borderRadius: 4,
|
|
236
|
-
} }), jsxRuntime.jsx("button", { type: "button", onClick: handleCreate, children:
|
|
236
|
+
} }), jsxRuntime.jsx("button", { type: "button", onClick: handleCreate, children: t('morphTargetEditor.add', '+ 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
|
|
237
|
+
? t('morphTargetEditor.stop', 'Stop')
|
|
238
|
+
: t('morphTargetEditor.play', '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)))] }))] }));
|
|
237
239
|
};
|
|
238
240
|
|
|
239
241
|
exports.NiceMorphTargetEditor = NiceMorphTargetEditor;
|
|
@@ -348,19 +348,22 @@ function useOctree(scene, configOverride) {
|
|
|
348
348
|
const NiceOctree = ({ scene, config, showDebug: initialShowDebug, className = '', }) => {
|
|
349
349
|
const { t } = i18n.useNiceTranslation();
|
|
350
350
|
const octree = useOctree(scene, config);
|
|
351
|
+
const { setShowDebug } = octree;
|
|
351
352
|
React.useEffect(() => {
|
|
352
353
|
if (initialShowDebug !== undefined) {
|
|
353
|
-
|
|
354
|
+
setShowDebug(initialShowDebug);
|
|
354
355
|
}
|
|
355
|
-
}, [initialShowDebug]);
|
|
356
|
-
return (jsxRuntime.jsxs("div", { className: `nice-octree ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsxs("strong", { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "tree", size: 14 }),
|
|
356
|
+
}, [initialShowDebug, setShowDebug]);
|
|
357
|
+
return (jsxRuntime.jsxs("div", { className: `nice-octree ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsxs("strong", { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "tree", size: 14 }), ' ', t('octree.spatialIndexOctree', 'Spatial Index (Octree)')] }), jsxRuntime.jsxs("button", { type: "button", onClick: octree.build, style: {
|
|
357
358
|
background: 'var(--nice-color-accent, #6366f1)',
|
|
358
359
|
color: 'var(--nice-text-on-primary, #fff)',
|
|
359
360
|
border: 'none',
|
|
360
361
|
borderRadius: 4,
|
|
361
362
|
padding: '6px 12px',
|
|
362
363
|
cursor: 'pointer',
|
|
363
|
-
}, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "zap", size: 14 }), " ", t('octree.buildOctree', 'Build Octree')] }), jsxRuntime.
|
|
364
|
+
}, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "zap", size: 14 }), " ", t('octree.buildOctree', 'Build Octree')] }), jsxRuntime.jsx("div", { style: { fontSize: 11, opacity: 0.7 }, children: t('octree.nodesObjects', 'Nodes: {nodeCount} · Objects: {objectCount}')
|
|
365
|
+
.replace('{nodeCount}', String(octree.nodeCount))
|
|
366
|
+
.replace('{objectCount}', String(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')] })] }));
|
|
364
367
|
};
|
|
365
368
|
|
|
366
369
|
exports.NiceOctree = NiceOctree;
|