@nice2dev/ui-3d 1.0.0 → 1.0.3
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 +115 -1
- package/dist/cjs/collaborative/collaborativeScene.js +210 -0
- package/dist/cjs/collaborative/collaborativeScene.js.map +1 -0
- package/dist/cjs/core/i18n.js +3 -3
- package/dist/cjs/core/i18n.js.map +1 -1
- package/dist/cjs/dance/DanceBridge.js +162 -0
- package/dist/cjs/dance/DanceBridge.js.map +1 -0
- package/dist/cjs/dance/DanceScoreEngine.js +210 -0
- package/dist/cjs/dance/DanceScoreEngine.js.map +1 -0
- package/dist/cjs/dance/PoseDetector.js +199 -0
- package/dist/cjs/dance/PoseDetector.js.map +1 -0
- package/dist/cjs/index.js +254 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/material/MaterialEditor.module.css.js +6 -0
- package/dist/cjs/material/MaterialEditor.module.css.js.map +1 -0
- package/dist/cjs/material/NiceMaterialEditor.js +737 -0
- package/dist/cjs/material/NiceMaterialEditor.js.map +1 -0
- package/dist/cjs/material/materialEditorTypes.js +73 -0
- package/dist/cjs/material/materialEditorTypes.js.map +1 -0
- package/dist/cjs/material/materialEditorUtils.js +841 -0
- package/dist/cjs/material/materialEditorUtils.js.map +1 -0
- package/dist/cjs/material/materialNodeDefinitions.js +1285 -0
- package/dist/cjs/material/materialNodeDefinitions.js.map +1 -0
- package/dist/cjs/model/ModelEditor.js +4 -1
- package/dist/cjs/model/ModelEditor.js.map +1 -1
- package/dist/cjs/model/ModelEditor.module.css.js +1 -1
- package/dist/cjs/model/ModelEditorLeftPanel.js +5 -4
- package/dist/cjs/model/ModelEditorLeftPanel.js.map +1 -1
- package/dist/cjs/model/ModelEditorMenuBar.js +8 -3
- package/dist/cjs/model/ModelEditorMenuBar.js.map +1 -1
- package/dist/cjs/model/ModelEditorRightPanel.js +27 -26
- package/dist/cjs/model/ModelEditorRightPanel.js.map +1 -1
- package/dist/cjs/model/ModelEditorSubComponents.js +20 -16
- package/dist/cjs/model/ModelEditorSubComponents.js.map +1 -1
- package/dist/cjs/model/ModelEditorTimeline.js +5 -4
- package/dist/cjs/model/ModelEditorTimeline.js.map +1 -1
- package/dist/cjs/model/ModelEditorToolbar.js +4 -3
- package/dist/cjs/model/ModelEditorToolbar.js.map +1 -1
- package/dist/cjs/model/ModelEditorViewport.js +2 -2
- package/dist/cjs/model/ModelEditorViewport.js.map +1 -1
- package/dist/cjs/model/ModelViewer.js +68 -0
- package/dist/cjs/model/ModelViewer.js.map +1 -0
- package/dist/cjs/model/ModelViewer.module.css.js +6 -0
- package/dist/cjs/model/ModelViewer.module.css.js.map +1 -0
- package/dist/cjs/model/NiceArmatureEditor.js +255 -0
- package/dist/cjs/model/NiceArmatureEditor.js.map +1 -0
- package/dist/cjs/model/NiceMorphTargetEditor.js +206 -0
- package/dist/cjs/model/NiceMorphTargetEditor.js.map +1 -0
- package/dist/cjs/model/NiceOctree.js +339 -0
- package/dist/cjs/model/NiceOctree.js.map +1 -0
- package/dist/cjs/model/NicePhysicsSimulation.js +283 -0
- package/dist/cjs/model/NicePhysicsSimulation.js.map +1 -0
- package/dist/cjs/model/NiceProceduralGeometry.js +269 -0
- package/dist/cjs/model/NiceProceduralGeometry.js.map +1 -0
- package/dist/cjs/model/NiceTerrainEditor.js +343 -0
- package/dist/cjs/model/NiceTerrainEditor.js.map +1 -0
- package/dist/cjs/model/NiceWeightPainter.js +258 -0
- package/dist/cjs/model/NiceWeightPainter.js.map +1 -0
- package/dist/cjs/model/NiceXRPreview.js +269 -0
- package/dist/cjs/model/NiceXRPreview.js.map +1 -0
- package/dist/cjs/model/cadModeUtils.js +130 -0
- package/dist/cjs/model/cadModeUtils.js.map +1 -0
- package/dist/cjs/model/editorShortcuts.js +187 -0
- package/dist/cjs/model/editorShortcuts.js.map +1 -0
- package/dist/cjs/model/modelEditorTypes.js +11 -0
- package/dist/cjs/model/modelEditorTypes.js.map +1 -1
- package/dist/cjs/model/modelEditorUtils.js +1049 -0
- package/dist/cjs/model/modelEditorUtils.js.map +1 -0
- package/dist/cjs/model/simsModeUtils.js +358 -0
- package/dist/cjs/model/simsModeUtils.js.map +1 -0
- package/dist/cjs/model/useModelEditor.js +319 -115
- package/dist/cjs/model/useModelEditor.js.map +1 -1
- package/dist/cjs/model/useModelViewer.js +634 -0
- package/dist/cjs/model/useModelViewer.js.map +1 -0
- package/dist/cjs/nice2dev-ui-3d.css +1 -1
- package/dist/cjs/particle/NiceParticleEditor.js +526 -0
- package/dist/cjs/particle/NiceParticleEditor.js.map +1 -0
- package/dist/cjs/particle/ParticleEditor.module.css.js +6 -0
- package/dist/cjs/particle/ParticleEditor.module.css.js.map +1 -0
- package/dist/cjs/particle/particleEditorTypes.js +92 -0
- package/dist/cjs/particle/particleEditorTypes.js.map +1 -0
- package/dist/cjs/particle/particleEditorUtils.js +1084 -0
- package/dist/cjs/particle/particleEditorUtils.js.map +1 -0
- package/dist/cjs/rendering/NiceCascadedShadows.js +266 -0
- package/dist/cjs/rendering/NiceCascadedShadows.js.map +1 -0
- package/dist/cjs/rendering/NiceRenderExport.js +341 -0
- package/dist/cjs/rendering/NiceRenderExport.js.map +1 -0
- package/dist/cjs/rendering/NiceSSAO.js +359 -0
- package/dist/cjs/rendering/NiceSSAO.js.map +1 -0
- package/dist/cjs/rendering/NiceSSR.js +277 -0
- package/dist/cjs/rendering/NiceSSR.js.map +1 -0
- package/dist/cjs/rendering/NiceWebGPURenderer.js +215 -0
- package/dist/cjs/rendering/NiceWebGPURenderer.js.map +1 -0
- package/dist/cjs/ui/dist/index.js +50089 -0
- package/dist/cjs/ui/dist/index.js.map +1 -0
- package/dist/cjs/uv/NiceUVEditor.js +520 -0
- package/dist/cjs/uv/NiceUVEditor.js.map +1 -0
- package/dist/cjs/uv/UVEditor.module.css.js +6 -0
- package/dist/cjs/uv/UVEditor.module.css.js.map +1 -0
- package/dist/cjs/uv/uvEditorTypes.js +98 -0
- package/dist/cjs/uv/uvEditorTypes.js.map +1 -0
- package/dist/cjs/uv/uvEditorUtils.js +670 -0
- package/dist/cjs/uv/uvEditorUtils.js.map +1 -0
- package/dist/esm/collaborative/collaborativeScene.js +206 -0
- package/dist/esm/collaborative/collaborativeScene.js.map +1 -0
- package/dist/esm/dance/DanceBridge.js +158 -0
- package/dist/esm/dance/DanceBridge.js.map +1 -0
- package/dist/esm/dance/DanceScoreEngine.js +207 -0
- package/dist/esm/dance/DanceScoreEngine.js.map +1 -0
- package/dist/esm/dance/PoseDetector.js +195 -0
- package/dist/esm/dance/PoseDetector.js.map +1 -0
- package/dist/esm/index.js +35 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/material/MaterialEditor.module.css.js +4 -0
- package/dist/esm/material/MaterialEditor.module.css.js.map +1 -0
- package/dist/esm/material/NiceMaterialEditor.js +734 -0
- package/dist/esm/material/NiceMaterialEditor.js.map +1 -0
- package/dist/esm/material/materialEditorTypes.js +62 -0
- package/dist/esm/material/materialEditorTypes.js.map +1 -0
- package/dist/esm/material/materialEditorUtils.js +811 -0
- package/dist/esm/material/materialEditorUtils.js.map +1 -0
- package/dist/esm/material/materialNodeDefinitions.js +1280 -0
- package/dist/esm/material/materialNodeDefinitions.js.map +1 -0
- package/dist/esm/model/ModelEditor.js +4 -2
- package/dist/esm/model/ModelEditor.js.map +1 -1
- package/dist/esm/model/ModelEditor.module.css.js +1 -1
- package/dist/esm/model/ModelEditorLeftPanel.js +5 -4
- package/dist/esm/model/ModelEditorLeftPanel.js.map +1 -1
- package/dist/esm/model/ModelEditorMenuBar.js +8 -3
- package/dist/esm/model/ModelEditorMenuBar.js.map +1 -1
- package/dist/esm/model/ModelEditorRightPanel.js +27 -26
- package/dist/esm/model/ModelEditorRightPanel.js.map +1 -1
- package/dist/esm/model/ModelEditorSubComponents.js +17 -13
- package/dist/esm/model/ModelEditorSubComponents.js.map +1 -1
- package/dist/esm/model/ModelEditorTimeline.js +5 -4
- package/dist/esm/model/ModelEditorTimeline.js.map +1 -1
- package/dist/esm/model/ModelEditorToolbar.js +4 -3
- package/dist/esm/model/ModelEditorToolbar.js.map +1 -1
- package/dist/esm/model/ModelEditorViewport.js +2 -2
- package/dist/esm/model/ModelEditorViewport.js.map +1 -1
- package/dist/esm/model/ModelViewer.js +65 -0
- package/dist/esm/model/ModelViewer.js.map +1 -0
- package/dist/esm/model/ModelViewer.module.css.js +4 -0
- package/dist/esm/model/ModelViewer.module.css.js.map +1 -0
- package/dist/esm/model/NiceArmatureEditor.js +233 -0
- package/dist/esm/model/NiceArmatureEditor.js.map +1 -0
- package/dist/esm/model/NiceMorphTargetEditor.js +184 -0
- package/dist/esm/model/NiceMorphTargetEditor.js.map +1 -0
- package/dist/esm/model/NiceOctree.js +317 -0
- package/dist/esm/model/NiceOctree.js.map +1 -0
- package/dist/esm/model/NicePhysicsSimulation.js +261 -0
- package/dist/esm/model/NicePhysicsSimulation.js.map +1 -0
- package/dist/esm/model/NiceProceduralGeometry.js +242 -0
- package/dist/esm/model/NiceProceduralGeometry.js.map +1 -0
- package/dist/esm/model/NiceTerrainEditor.js +321 -0
- package/dist/esm/model/NiceTerrainEditor.js.map +1 -0
- package/dist/esm/model/NiceWeightPainter.js +236 -0
- package/dist/esm/model/NiceWeightPainter.js.map +1 -0
- package/dist/esm/model/NiceXRPreview.js +247 -0
- package/dist/esm/model/NiceXRPreview.js.map +1 -0
- package/dist/esm/model/cadModeUtils.js +103 -0
- package/dist/esm/model/cadModeUtils.js.map +1 -0
- package/dist/esm/model/editorShortcuts.js +185 -0
- package/dist/esm/model/editorShortcuts.js.map +1 -0
- package/dist/esm/model/modelEditorTypes.js +11 -0
- package/dist/esm/model/modelEditorTypes.js.map +1 -1
- package/dist/esm/model/modelEditorUtils.js +997 -0
- package/dist/esm/model/modelEditorUtils.js.map +1 -0
- package/dist/esm/model/simsModeUtils.js +325 -0
- package/dist/esm/model/simsModeUtils.js.map +1 -0
- package/dist/esm/model/useModelEditor.js +204 -0
- package/dist/esm/model/useModelEditor.js.map +1 -1
- package/dist/esm/model/useModelViewer.js +613 -0
- package/dist/esm/model/useModelViewer.js.map +1 -0
- package/dist/esm/nice2dev-ui-3d.css +1 -1
- package/dist/esm/particle/NiceParticleEditor.js +523 -0
- package/dist/esm/particle/NiceParticleEditor.js.map +1 -0
- package/dist/esm/particle/ParticleEditor.module.css.js +4 -0
- package/dist/esm/particle/ParticleEditor.module.css.js.map +1 -0
- package/dist/esm/particle/particleEditorTypes.js +84 -0
- package/dist/esm/particle/particleEditorTypes.js.map +1 -0
- package/dist/esm/particle/particleEditorUtils.js +1054 -0
- package/dist/esm/particle/particleEditorUtils.js.map +1 -0
- package/dist/esm/rendering/NiceCascadedShadows.js +244 -0
- package/dist/esm/rendering/NiceCascadedShadows.js.map +1 -0
- package/dist/esm/rendering/NiceRenderExport.js +319 -0
- package/dist/esm/rendering/NiceRenderExport.js.map +1 -0
- package/dist/esm/rendering/NiceSSAO.js +337 -0
- package/dist/esm/rendering/NiceSSAO.js.map +1 -0
- package/dist/esm/rendering/NiceSSR.js +255 -0
- package/dist/esm/rendering/NiceSSR.js.map +1 -0
- package/dist/esm/rendering/NiceWebGPURenderer.js +193 -0
- package/dist/esm/rendering/NiceWebGPURenderer.js.map +1 -0
- package/dist/esm/ui/dist/index.js +49686 -0
- package/dist/esm/ui/dist/index.js.map +1 -0
- package/dist/esm/uv/NiceUVEditor.js +518 -0
- package/dist/esm/uv/NiceUVEditor.js.map +1 -0
- package/dist/esm/uv/UVEditor.module.css.js +4 -0
- package/dist/esm/uv/UVEditor.module.css.js.map +1 -0
- package/dist/esm/uv/uvEditorTypes.js +88 -0
- package/dist/esm/uv/uvEditorTypes.js.map +1 -0
- package/dist/esm/uv/uvEditorUtils.js +621 -0
- package/dist/esm/uv/uvEditorUtils.js.map +1 -0
- package/package.json +3 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var Ae = require('react');
|
|
4
4
|
var THREE = require('three');
|
|
5
5
|
var OrbitControls_js = require('three/examples/jsm/controls/OrbitControls.js');
|
|
6
6
|
var TransformControls_js = require('three/examples/jsm/controls/TransformControls.js');
|
|
@@ -24,6 +24,7 @@ var VRMLLoader_js = require('three/examples/jsm/loaders/VRMLLoader.js');
|
|
|
24
24
|
var GCodeLoader_js = require('three/examples/jsm/loaders/GCodeLoader.js');
|
|
25
25
|
var SVGLoader_js = require('three/examples/jsm/loaders/SVGLoader.js');
|
|
26
26
|
var modelEditorTypes = require('./modelEditorTypes.js');
|
|
27
|
+
var modelEditorUtils = require('./modelEditorUtils.js');
|
|
27
28
|
|
|
28
29
|
function _interopNamespaceDefault(e) {
|
|
29
30
|
var n = Object.create(null);
|
|
@@ -71,82 +72,82 @@ function findNodeById(nodes, id) {
|
|
|
71
72
|
═══════════════════════════════════════════ */
|
|
72
73
|
function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
73
74
|
/* ── refs ── */
|
|
74
|
-
const mountRef =
|
|
75
|
-
const rendererRef =
|
|
76
|
-
const sceneRef =
|
|
77
|
-
const cameraRef =
|
|
78
|
-
const orbitRef =
|
|
79
|
-
const transformRef =
|
|
80
|
-
const clockRef =
|
|
81
|
-
const mixerRef =
|
|
82
|
-
const rafRef =
|
|
83
|
-
const fileInputRef =
|
|
84
|
-
const mergeInputRef =
|
|
85
|
-
const videoInputRef =
|
|
86
|
-
const raycasterRef =
|
|
87
|
-
const skeletonHelperRef =
|
|
75
|
+
const mountRef = Ae.useRef(null);
|
|
76
|
+
const rendererRef = Ae.useRef(null);
|
|
77
|
+
const sceneRef = Ae.useRef(new THREE__namespace.Scene());
|
|
78
|
+
const cameraRef = Ae.useRef(new THREE__namespace.PerspectiveCamera(50, 1, 0.01, 10000));
|
|
79
|
+
const orbitRef = Ae.useRef(null);
|
|
80
|
+
const transformRef = Ae.useRef(null);
|
|
81
|
+
const clockRef = Ae.useRef(new THREE__namespace.Clock());
|
|
82
|
+
const mixerRef = Ae.useRef(null);
|
|
83
|
+
const rafRef = Ae.useRef(0);
|
|
84
|
+
const fileInputRef = Ae.useRef(null);
|
|
85
|
+
const mergeInputRef = Ae.useRef(null);
|
|
86
|
+
const videoInputRef = Ae.useRef(null);
|
|
87
|
+
const raycasterRef = Ae.useRef(new THREE__namespace.Raycaster());
|
|
88
|
+
const skeletonHelperRef = Ae.useRef(null);
|
|
88
89
|
/* ── state ── */
|
|
89
|
-
const [sceneTree, setSceneTree] =
|
|
90
|
-
const [selectedId, setSelectedId] =
|
|
91
|
-
const [transformMode, setTransformMode] =
|
|
92
|
-
const [animations, setAnimations] =
|
|
93
|
-
const [activeAnimIdx, setActiveAnimIdx] =
|
|
94
|
-
const [isPlaying, setIsPlaying] =
|
|
95
|
-
const [animTime, setAnimTime] =
|
|
96
|
-
const [animDuration, setAnimDuration] =
|
|
97
|
-
const [animSpeed, setAnimSpeed] =
|
|
98
|
-
const [loopAnim, setLoopAnim] =
|
|
99
|
-
const [bottomCollapsed, setBottomCollapsed] =
|
|
100
|
-
const [showGrid, setShowGrid] =
|
|
101
|
-
const [showAxes, setShowAxes] =
|
|
102
|
-
const [wireframe, setWireframe] =
|
|
103
|
-
const [bgColor, setBgColor] =
|
|
104
|
-
const [statusText, setStatusText] =
|
|
105
|
-
const [dragOver, setDragOver] =
|
|
106
|
-
const [polyCount, setPolyCount] =
|
|
107
|
-
const [meshCount, setMeshCount] =
|
|
108
|
-
const [boneCount, setBoneCount] =
|
|
90
|
+
const [sceneTree, setSceneTree] = Ae.useState([]);
|
|
91
|
+
const [selectedId, setSelectedId] = Ae.useState(null);
|
|
92
|
+
const [transformMode, setTransformMode] = Ae.useState("translate");
|
|
93
|
+
const [animations, setAnimations] = Ae.useState([]);
|
|
94
|
+
const [activeAnimIdx, setActiveAnimIdx] = Ae.useState(-1);
|
|
95
|
+
const [isPlaying, setIsPlaying] = Ae.useState(false);
|
|
96
|
+
const [animTime, setAnimTime] = Ae.useState(0);
|
|
97
|
+
const [animDuration, setAnimDuration] = Ae.useState(0);
|
|
98
|
+
const [animSpeed, setAnimSpeed] = Ae.useState(1);
|
|
99
|
+
const [loopAnim, setLoopAnim] = Ae.useState(true);
|
|
100
|
+
const [bottomCollapsed, setBottomCollapsed] = Ae.useState(false);
|
|
101
|
+
const [showGrid, setShowGrid] = Ae.useState(true);
|
|
102
|
+
const [showAxes, setShowAxes] = Ae.useState(true);
|
|
103
|
+
const [wireframe, setWireframe] = Ae.useState(false);
|
|
104
|
+
const [bgColor, setBgColor] = Ae.useState("#111122");
|
|
105
|
+
const [statusText, setStatusText] = Ae.useState("Ready");
|
|
106
|
+
const [dragOver, setDragOver] = Ae.useState(false);
|
|
107
|
+
const [polyCount, setPolyCount] = Ae.useState(0);
|
|
108
|
+
const [meshCount, setMeshCount] = Ae.useState(0);
|
|
109
|
+
const [boneCount, setBoneCount] = Ae.useState(0);
|
|
109
110
|
// Material editor
|
|
110
|
-
const [selMaterialIdx, setSelMaterialIdx] =
|
|
111
|
-
const [matRefresh, setMatRefresh] =
|
|
111
|
+
const [selMaterialIdx, setSelMaterialIdx] = Ae.useState(0);
|
|
112
|
+
const [matRefresh, setMatRefresh] = Ae.useState(0);
|
|
112
113
|
// AI panel
|
|
113
|
-
const [aiStatus, setAiStatus] =
|
|
114
|
-
const [aiBusy, setAiBusy] =
|
|
114
|
+
const [aiStatus, setAiStatus] = Ae.useState("");
|
|
115
|
+
const [aiBusy, setAiBusy] = Ae.useState(false);
|
|
115
116
|
// Blender 2.0 — viewport & editor
|
|
116
|
-
const [shadingMode, setShadingMode] =
|
|
117
|
-
const [editorMode, setEditorMode] =
|
|
118
|
-
const [contextMenu, setContextMenu] =
|
|
119
|
-
const [outlinerSearch, setOutlinerSearch] =
|
|
120
|
-
const [snapEnabled, setSnapEnabled] =
|
|
121
|
-
const [snapGrid, setSnapGrid] =
|
|
122
|
-
const [showSkeleton, setShowSkeleton] =
|
|
123
|
-
const [addMenuOpen, setAddMenuOpen] =
|
|
124
|
-
const [gizmoSpace, setGizmoSpace] =
|
|
125
|
-
const [propTab, setPropTab] =
|
|
126
|
-
const [fogEnabled, setFogEnabled] =
|
|
127
|
-
const [fogColor, setFogColor] =
|
|
128
|
-
const [fogNear, setFogNear] =
|
|
129
|
-
const [fogFar, setFogFar] =
|
|
130
|
-
const [showLightHelpers, setShowLightHelpers] =
|
|
117
|
+
const [shadingMode, setShadingMode] = Ae.useState("solid");
|
|
118
|
+
const [editorMode, setEditorMode] = Ae.useState("object");
|
|
119
|
+
const [contextMenu, setContextMenu] = Ae.useState(null);
|
|
120
|
+
const [outlinerSearch, setOutlinerSearch] = Ae.useState("");
|
|
121
|
+
const [snapEnabled, setSnapEnabled] = Ae.useState(false);
|
|
122
|
+
const [snapGrid, setSnapGrid] = Ae.useState(1);
|
|
123
|
+
const [showSkeleton, setShowSkeleton] = Ae.useState(true);
|
|
124
|
+
const [addMenuOpen, setAddMenuOpen] = Ae.useState(null);
|
|
125
|
+
const [gizmoSpace, setGizmoSpace] = Ae.useState("world");
|
|
126
|
+
const [propTab, setPropTab] = Ae.useState("object");
|
|
127
|
+
const [fogEnabled, setFogEnabled] = Ae.useState(false);
|
|
128
|
+
const [fogColor, setFogColor] = Ae.useState("#111122");
|
|
129
|
+
const [fogNear, setFogNear] = Ae.useState(10);
|
|
130
|
+
const [fogFar, setFogFar] = Ae.useState(100);
|
|
131
|
+
const [showLightHelpers, setShowLightHelpers] = Ae.useState(true);
|
|
131
132
|
// The root object that was loaded (mesh/character)
|
|
132
|
-
const rootObjectRef =
|
|
133
|
+
const rootObjectRef = Ae.useRef(null);
|
|
133
134
|
// Active animation action
|
|
134
|
-
const activeActionRef =
|
|
135
|
+
const activeActionRef = Ae.useRef(null);
|
|
135
136
|
// All loaded animation clips (for the current model)
|
|
136
|
-
const allClipsRef =
|
|
137
|
+
const allClipsRef = Ae.useRef([]);
|
|
137
138
|
/* keep refs current */
|
|
138
|
-
const animTimeRef =
|
|
139
|
+
const animTimeRef = Ae.useRef(animTime);
|
|
139
140
|
animTimeRef.current = animTime;
|
|
140
|
-
const isPlayingRef =
|
|
141
|
+
const isPlayingRef = Ae.useRef(isPlaying);
|
|
141
142
|
isPlayingRef.current = isPlaying;
|
|
142
|
-
const animSpeedRef =
|
|
143
|
+
const animSpeedRef = Ae.useRef(animSpeed);
|
|
143
144
|
animSpeedRef.current = animSpeed;
|
|
144
|
-
const loopAnimRef =
|
|
145
|
+
const loopAnimRef = Ae.useRef(loopAnim);
|
|
145
146
|
loopAnimRef.current = loopAnim;
|
|
146
147
|
/* ─────────────────────────────────────────
|
|
147
148
|
Scene setup
|
|
148
149
|
───────────────────────────────────────── */
|
|
149
|
-
|
|
150
|
+
Ae.useEffect(() => {
|
|
150
151
|
const container = mountRef.current;
|
|
151
152
|
if (!container)
|
|
152
153
|
return;
|
|
@@ -279,7 +280,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
279
280
|
/* ─────────────────────────────────────────
|
|
280
281
|
Rebuild scene tree from Three.js scene
|
|
281
282
|
───────────────────────────────────────── */
|
|
282
|
-
const rebuildTree =
|
|
283
|
+
const rebuildTree = Ae.useCallback(() => {
|
|
283
284
|
const scene = sceneRef.current;
|
|
284
285
|
let polys = 0;
|
|
285
286
|
let meshes = 0;
|
|
@@ -346,9 +347,9 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
346
347
|
/* ─────────────────────────────────────────
|
|
347
348
|
File loading
|
|
348
349
|
───────────────────────────────────────── */
|
|
349
|
-
const playClipRef =
|
|
350
|
-
const focusOnObjectRef =
|
|
351
|
-
const addObjectToScene =
|
|
350
|
+
const playClipRef = Ae.useRef(() => { });
|
|
351
|
+
const focusOnObjectRef = Ae.useRef(() => { });
|
|
352
|
+
const addObjectToScene = Ae.useCallback((obj, clips, filename) => {
|
|
352
353
|
const scene = sceneRef.current;
|
|
353
354
|
obj.traverse((child) => {
|
|
354
355
|
if (child instanceof THREE__namespace.Mesh) {
|
|
@@ -395,7 +396,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
395
396
|
rebuildTree();
|
|
396
397
|
setStatusText(`Loaded: ${filename} (${clips.length} animation(s))`);
|
|
397
398
|
}, [rebuildTree]);
|
|
398
|
-
const loadFile =
|
|
399
|
+
const loadFile = Ae.useCallback(async (file) => {
|
|
399
400
|
var _a, _b, _c, _d, _e, _f;
|
|
400
401
|
const name = file.name.toLowerCase();
|
|
401
402
|
const ext = "." + name.split(".").pop();
|
|
@@ -514,7 +515,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
514
515
|
URL.revokeObjectURL(url);
|
|
515
516
|
}
|
|
516
517
|
}, [addObjectToScene]);
|
|
517
|
-
const mergeAnimationFBX =
|
|
518
|
+
const mergeAnimationFBX = Ae.useCallback(async (file) => {
|
|
518
519
|
var _a;
|
|
519
520
|
const url = URL.createObjectURL(file);
|
|
520
521
|
setStatusText(`Merging animations from ${file.name}...`);
|
|
@@ -555,7 +556,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
555
556
|
/* ─────────────────────────────────────────
|
|
556
557
|
Animation controls
|
|
557
558
|
───────────────────────────────────────── */
|
|
558
|
-
const playClip =
|
|
559
|
+
const playClip = Ae.useCallback((index) => {
|
|
559
560
|
const mixer = mixerRef.current;
|
|
560
561
|
if (!mixer || index < 0 || index >= allClipsRef.current.length)
|
|
561
562
|
return;
|
|
@@ -575,7 +576,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
575
576
|
setIsPlaying(true);
|
|
576
577
|
}, []);
|
|
577
578
|
playClipRef.current = playClip;
|
|
578
|
-
const togglePlay =
|
|
579
|
+
const togglePlay = Ae.useCallback(() => {
|
|
579
580
|
if (activeAnimIdx < 0 && allClipsRef.current.length > 0) {
|
|
580
581
|
playClip(0);
|
|
581
582
|
return;
|
|
@@ -592,7 +593,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
592
593
|
setIsPlaying(true);
|
|
593
594
|
}
|
|
594
595
|
}, [activeAnimIdx, playClip]);
|
|
595
|
-
const stopAnim =
|
|
596
|
+
const stopAnim = Ae.useCallback(() => {
|
|
596
597
|
const action = activeActionRef.current;
|
|
597
598
|
if (action) {
|
|
598
599
|
action.stop();
|
|
@@ -601,7 +602,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
601
602
|
setIsPlaying(false);
|
|
602
603
|
setAnimTime(0);
|
|
603
604
|
}, []);
|
|
604
|
-
const seekAnim =
|
|
605
|
+
const seekAnim = Ae.useCallback((t) => {
|
|
605
606
|
var _a;
|
|
606
607
|
const action = activeActionRef.current;
|
|
607
608
|
if (!action)
|
|
@@ -615,7 +616,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
615
616
|
/* ─────────────────────────────────────────
|
|
616
617
|
Camera helpers
|
|
617
618
|
───────────────────────────────────────── */
|
|
618
|
-
const focusOnObject =
|
|
619
|
+
const focusOnObject = Ae.useCallback((obj) => {
|
|
619
620
|
const box = new THREE__namespace.Box3().setFromObject(obj);
|
|
620
621
|
const center = box.getCenter(new THREE__namespace.Vector3());
|
|
621
622
|
const size = box.getSize(new THREE__namespace.Vector3());
|
|
@@ -629,7 +630,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
629
630
|
}
|
|
630
631
|
}, []);
|
|
631
632
|
focusOnObjectRef.current = focusOnObject;
|
|
632
|
-
const setCameraPreset =
|
|
633
|
+
const setCameraPreset = Ae.useCallback((preset) => {
|
|
633
634
|
const orbit = orbitRef.current;
|
|
634
635
|
if (!orbit)
|
|
635
636
|
return;
|
|
@@ -663,7 +664,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
663
664
|
/* ─────────────────────────────────────────
|
|
664
665
|
Selection & transform
|
|
665
666
|
───────────────────────────────────────── */
|
|
666
|
-
const selectObject =
|
|
667
|
+
const selectObject = Ae.useCallback((node) => {
|
|
667
668
|
var _a;
|
|
668
669
|
setSelectedId((_a = node === null || node === void 0 ? void 0 : node.id) !== null && _a !== void 0 ? _a : null);
|
|
669
670
|
const tc = transformRef.current;
|
|
@@ -677,29 +678,29 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
677
678
|
tc.detach();
|
|
678
679
|
}
|
|
679
680
|
}, [transformMode]);
|
|
680
|
-
|
|
681
|
+
Ae.useEffect(() => {
|
|
681
682
|
var _a;
|
|
682
683
|
(_a = transformRef.current) === null || _a === void 0 ? void 0 : _a.setMode(transformMode);
|
|
683
684
|
}, [transformMode]);
|
|
684
685
|
/* ─────────────────────────────────────────
|
|
685
686
|
Toggle helpers
|
|
686
687
|
───────────────────────────────────────── */
|
|
687
|
-
|
|
688
|
+
Ae.useEffect(() => {
|
|
688
689
|
const scene = sceneRef.current;
|
|
689
690
|
const grid = scene.getObjectByName("__grid");
|
|
690
691
|
if (grid)
|
|
691
692
|
grid.visible = showGrid;
|
|
692
693
|
}, [showGrid]);
|
|
693
|
-
|
|
694
|
+
Ae.useEffect(() => {
|
|
694
695
|
const scene = sceneRef.current;
|
|
695
696
|
const axes = scene.getObjectByName("__axes");
|
|
696
697
|
if (axes)
|
|
697
698
|
axes.visible = showAxes;
|
|
698
699
|
}, [showAxes]);
|
|
699
|
-
|
|
700
|
+
Ae.useEffect(() => {
|
|
700
701
|
sceneRef.current.background = new THREE__namespace.Color(bgColor);
|
|
701
702
|
}, [bgColor]);
|
|
702
|
-
|
|
703
|
+
Ae.useEffect(() => {
|
|
703
704
|
const root = rootObjectRef.current;
|
|
704
705
|
if (!root)
|
|
705
706
|
return;
|
|
@@ -718,7 +719,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
718
719
|
/* ─────────────────────────────────────────
|
|
719
720
|
Viewport shading mode
|
|
720
721
|
───────────────────────────────────────── */
|
|
721
|
-
|
|
722
|
+
Ae.useEffect(() => {
|
|
722
723
|
const renderer = rendererRef.current;
|
|
723
724
|
if (!renderer)
|
|
724
725
|
return;
|
|
@@ -771,12 +772,12 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
771
772
|
}
|
|
772
773
|
}, [shadingMode]);
|
|
773
774
|
/* Gizmo space */
|
|
774
|
-
|
|
775
|
+
Ae.useEffect(() => {
|
|
775
776
|
var _a;
|
|
776
777
|
(_a = transformRef.current) === null || _a === void 0 ? void 0 : _a.setSpace(gizmoSpace);
|
|
777
778
|
}, [gizmoSpace]);
|
|
778
779
|
/* Snap settings */
|
|
779
|
-
|
|
780
|
+
Ae.useEffect(() => {
|
|
780
781
|
const tc = transformRef.current;
|
|
781
782
|
if (!tc)
|
|
782
783
|
return;
|
|
@@ -792,7 +793,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
792
793
|
}
|
|
793
794
|
}, [snapEnabled, snapGrid]);
|
|
794
795
|
/* Skeleton helper */
|
|
795
|
-
|
|
796
|
+
Ae.useEffect(() => {
|
|
796
797
|
const scene = sceneRef.current;
|
|
797
798
|
if (skeletonHelperRef.current) {
|
|
798
799
|
scene.remove(skeletonHelperRef.current);
|
|
@@ -813,7 +814,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
813
814
|
}
|
|
814
815
|
}, [showSkeleton, sceneTree]);
|
|
815
816
|
/* Fog */
|
|
816
|
-
|
|
817
|
+
Ae.useEffect(() => {
|
|
817
818
|
if (fogEnabled) {
|
|
818
819
|
sceneRef.current.fog = new THREE__namespace.Fog(fogColor, fogNear, fogFar);
|
|
819
820
|
}
|
|
@@ -822,7 +823,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
822
823
|
}
|
|
823
824
|
}, [fogEnabled, fogColor, fogNear, fogFar]);
|
|
824
825
|
/* Light helpers visibility */
|
|
825
|
-
|
|
826
|
+
Ae.useEffect(() => {
|
|
826
827
|
sceneRef.current.traverse((obj) => {
|
|
827
828
|
if (obj.name.startsWith("__helper_")) {
|
|
828
829
|
obj.visible = showLightHelpers;
|
|
@@ -832,16 +833,16 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
832
833
|
/* ─────────────────────────────────────────
|
|
833
834
|
Drag-and-drop
|
|
834
835
|
───────────────────────────────────────── */
|
|
835
|
-
const handleDragOver =
|
|
836
|
+
const handleDragOver = Ae.useCallback((e) => {
|
|
836
837
|
e.preventDefault();
|
|
837
838
|
e.stopPropagation();
|
|
838
839
|
setDragOver(true);
|
|
839
840
|
}, []);
|
|
840
|
-
const handleDragLeave =
|
|
841
|
+
const handleDragLeave = Ae.useCallback((e) => {
|
|
841
842
|
e.preventDefault();
|
|
842
843
|
setDragOver(false);
|
|
843
844
|
}, []);
|
|
844
|
-
const handleDrop =
|
|
845
|
+
const handleDrop = Ae.useCallback((e) => {
|
|
845
846
|
e.preventDefault();
|
|
846
847
|
e.stopPropagation();
|
|
847
848
|
setDragOver(false);
|
|
@@ -856,14 +857,14 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
856
857
|
/* ─────────────────────────────────────────
|
|
857
858
|
Helpers
|
|
858
859
|
───────────────────────────────────────── */
|
|
859
|
-
const selectedNode =
|
|
860
|
-
const selectedMaterials =
|
|
860
|
+
const selectedNode = Ae.useMemo(() => (selectedId ? findNodeById(sceneTree, selectedId) : null), [selectedId, sceneTree]);
|
|
861
|
+
const selectedMaterials = Ae.useMemo(() => {
|
|
861
862
|
if (!selectedNode || !(selectedNode.object instanceof THREE__namespace.Mesh))
|
|
862
863
|
return [];
|
|
863
864
|
const m = selectedNode.object.material;
|
|
864
865
|
return Array.isArray(m) ? m : [m];
|
|
865
866
|
}, [selectedNode, matRefresh]);
|
|
866
|
-
const deleteSelected =
|
|
867
|
+
const deleteSelected = Ae.useCallback(() => {
|
|
867
868
|
var _a;
|
|
868
869
|
if (!selectedNode)
|
|
869
870
|
return;
|
|
@@ -872,7 +873,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
872
873
|
setSelectedId(null);
|
|
873
874
|
rebuildTree();
|
|
874
875
|
}, [selectedNode, rebuildTree]);
|
|
875
|
-
const duplicateSelected =
|
|
876
|
+
const duplicateSelected = Ae.useCallback(() => {
|
|
876
877
|
if (!selectedNode)
|
|
877
878
|
return;
|
|
878
879
|
const clone = selectedNode.object.clone(true);
|
|
@@ -885,7 +886,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
885
886
|
/* ─────────────────────────────────────────
|
|
886
887
|
Keyboard shortcuts
|
|
887
888
|
───────────────────────────────────────── */
|
|
888
|
-
|
|
889
|
+
Ae.useEffect(() => {
|
|
889
890
|
const handleKey = (e) => {
|
|
890
891
|
var _a, _b, _c;
|
|
891
892
|
const tag = (_a = e.target) === null || _a === void 0 ? void 0 : _a.tagName;
|
|
@@ -993,14 +994,14 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
993
994
|
/* ─────────────────────────────────────────
|
|
994
995
|
File input handlers
|
|
995
996
|
───────────────────────────────────────── */
|
|
996
|
-
const handleFileInput =
|
|
997
|
+
const handleFileInput = Ae.useCallback((e) => {
|
|
997
998
|
const files = e.target.files;
|
|
998
999
|
if (!files)
|
|
999
1000
|
return;
|
|
1000
1001
|
Array.from(files).forEach(loadFile);
|
|
1001
1002
|
e.target.value = "";
|
|
1002
1003
|
}, [loadFile]);
|
|
1003
|
-
const handleMergeInput =
|
|
1004
|
+
const handleMergeInput = Ae.useCallback((e) => {
|
|
1004
1005
|
const files = e.target.files;
|
|
1005
1006
|
if (!files)
|
|
1006
1007
|
return;
|
|
@@ -1010,7 +1011,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
1010
1011
|
/* ─────────────────────────────────────────
|
|
1011
1012
|
Export functions
|
|
1012
1013
|
───────────────────────────────────────── */
|
|
1013
|
-
const exportGLTF =
|
|
1014
|
+
const exportGLTF = Ae.useCallback((binary) => {
|
|
1014
1015
|
const root = rootObjectRef.current;
|
|
1015
1016
|
if (!root) {
|
|
1016
1017
|
setStatusText("Nothing to export");
|
|
@@ -1037,7 +1038,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
1037
1038
|
setStatusText(`Export error: ${String(err)}`);
|
|
1038
1039
|
}, options);
|
|
1039
1040
|
}, []);
|
|
1040
|
-
const exportOBJ =
|
|
1041
|
+
const exportOBJ = Ae.useCallback(() => {
|
|
1041
1042
|
const root = rootObjectRef.current;
|
|
1042
1043
|
if (!root) {
|
|
1043
1044
|
setStatusText("Nothing to export");
|
|
@@ -1053,7 +1054,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
1053
1054
|
URL.revokeObjectURL(a.href);
|
|
1054
1055
|
setStatusText("Exported OBJ successfully");
|
|
1055
1056
|
}, []);
|
|
1056
|
-
const exportSTL =
|
|
1057
|
+
const exportSTL = Ae.useCallback((binary) => {
|
|
1057
1058
|
const root = rootObjectRef.current;
|
|
1058
1059
|
if (!root) {
|
|
1059
1060
|
setStatusText("Nothing to export");
|
|
@@ -1075,7 +1076,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
1075
1076
|
URL.revokeObjectURL(a.href);
|
|
1076
1077
|
setStatusText(`Exported STL${binary ? " (binary)" : ""} successfully`);
|
|
1077
1078
|
}, []);
|
|
1078
|
-
const exportPLY =
|
|
1079
|
+
const exportPLY = Ae.useCallback((binary) => {
|
|
1079
1080
|
const root = rootObjectRef.current;
|
|
1080
1081
|
if (!root) {
|
|
1081
1082
|
setStatusText("Nothing to export");
|
|
@@ -1099,7 +1100,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
1099
1100
|
setStatusText(`Exported PLY${binary ? " (binary)" : ""} successfully`);
|
|
1100
1101
|
}, { binary });
|
|
1101
1102
|
}, []);
|
|
1102
|
-
const exportUSDZ =
|
|
1103
|
+
const exportUSDZ = Ae.useCallback(async () => {
|
|
1103
1104
|
const root = rootObjectRef.current;
|
|
1104
1105
|
if (!root) {
|
|
1105
1106
|
setStatusText("Nothing to export");
|
|
@@ -1127,7 +1128,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
1127
1128
|
/* ─────────────────────────────────────────
|
|
1128
1129
|
Dispose & clear
|
|
1129
1130
|
───────────────────────────────────────── */
|
|
1130
|
-
const disposeObject =
|
|
1131
|
+
const disposeObject = Ae.useCallback((obj) => {
|
|
1131
1132
|
obj.traverse((child) => {
|
|
1132
1133
|
if (child.geometry) {
|
|
1133
1134
|
child.geometry.dispose();
|
|
@@ -1147,7 +1148,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
1147
1148
|
}
|
|
1148
1149
|
});
|
|
1149
1150
|
}, []);
|
|
1150
|
-
const clearScene =
|
|
1151
|
+
const clearScene = Ae.useCallback(() => {
|
|
1151
1152
|
var _a;
|
|
1152
1153
|
if (!window.confirm("Clear the entire scene? This cannot be undone."))
|
|
1153
1154
|
return;
|
|
@@ -1185,7 +1186,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
1185
1186
|
/* ─────────────────────────────────────────
|
|
1186
1187
|
Save to Library
|
|
1187
1188
|
───────────────────────────────────────── */
|
|
1188
|
-
const saveToLibrary =
|
|
1189
|
+
const saveToLibrary = Ae.useCallback(() => {
|
|
1189
1190
|
if (!onSaveToLibrary)
|
|
1190
1191
|
return;
|
|
1191
1192
|
const root = rootObjectRef.current;
|
|
@@ -1213,7 +1214,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
1213
1214
|
/* ─────────────────────────────────────────
|
|
1214
1215
|
Add Mesh / Light / Camera / Empty
|
|
1215
1216
|
───────────────────────────────────────── */
|
|
1216
|
-
const addPrimitive =
|
|
1217
|
+
const addPrimitive = Ae.useCallback((type) => {
|
|
1217
1218
|
let geo;
|
|
1218
1219
|
switch (type) {
|
|
1219
1220
|
case "cube":
|
|
@@ -1274,7 +1275,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
1274
1275
|
setStatusText(`Added ${mesh.name}`);
|
|
1275
1276
|
setAddMenuOpen(null);
|
|
1276
1277
|
}, [rebuildTree]);
|
|
1277
|
-
const addSceneLight =
|
|
1278
|
+
const addSceneLight = Ae.useCallback((type) => {
|
|
1278
1279
|
let light;
|
|
1279
1280
|
switch (type) {
|
|
1280
1281
|
case "point": {
|
|
@@ -1327,7 +1328,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
1327
1328
|
setStatusText(`Added ${light.name}`);
|
|
1328
1329
|
setAddMenuOpen(null);
|
|
1329
1330
|
}, [rebuildTree]);
|
|
1330
|
-
const addCameraObject =
|
|
1331
|
+
const addCameraObject = Ae.useCallback(() => {
|
|
1331
1332
|
const cam = new THREE__namespace.PerspectiveCamera(50, 16 / 9, 0.1, 1000);
|
|
1332
1333
|
cam.name = "Camera";
|
|
1333
1334
|
cam.position.set(5, 3, 5);
|
|
@@ -1340,7 +1341,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
1340
1341
|
setStatusText("Added Camera");
|
|
1341
1342
|
setAddMenuOpen(null);
|
|
1342
1343
|
}, [rebuildTree]);
|
|
1343
|
-
const addEmpty =
|
|
1344
|
+
const addEmpty = Ae.useCallback((type) => {
|
|
1344
1345
|
const obj = new THREE__namespace.Object3D();
|
|
1345
1346
|
obj.name = type === "arrows" ? "Empty_Arrows" : type === "cube_empty" ? "Empty_Cube" : "Empty";
|
|
1346
1347
|
sceneRef.current.add(obj);
|
|
@@ -1356,7 +1357,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
1356
1357
|
/* ─────────────────────────────────────────
|
|
1357
1358
|
Raycaster click-to-select
|
|
1358
1359
|
───────────────────────────────────────── */
|
|
1359
|
-
const handleViewportClick =
|
|
1360
|
+
const handleViewportClick = Ae.useCallback((e) => {
|
|
1360
1361
|
if (editorMode !== "object")
|
|
1361
1362
|
return;
|
|
1362
1363
|
if (!mountRef.current || !rendererRef.current || !cameraRef.current)
|
|
@@ -1392,12 +1393,12 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
1392
1393
|
/* ─────────────────────────────────────────
|
|
1393
1394
|
Context menu
|
|
1394
1395
|
───────────────────────────────────────── */
|
|
1395
|
-
const handleContextMenu =
|
|
1396
|
+
const handleContextMenu = Ae.useCallback((e) => {
|
|
1396
1397
|
e.preventDefault();
|
|
1397
1398
|
setContextMenu({ x: e.clientX, y: e.clientY });
|
|
1398
1399
|
}, []);
|
|
1399
|
-
const closeContextMenu =
|
|
1400
|
-
|
|
1400
|
+
const closeContextMenu = Ae.useCallback(() => setContextMenu(null), []);
|
|
1401
|
+
Ae.useEffect(() => {
|
|
1401
1402
|
if (!contextMenu)
|
|
1402
1403
|
return;
|
|
1403
1404
|
const close = () => setContextMenu(null);
|
|
@@ -1407,7 +1408,7 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
1407
1408
|
/* ─────────────────────────────────────────
|
|
1408
1409
|
AI animation from video
|
|
1409
1410
|
───────────────────────────────────────── */
|
|
1410
|
-
const handleAIVideo =
|
|
1411
|
+
const handleAIVideo = Ae.useCallback(async (e) => {
|
|
1411
1412
|
var _a;
|
|
1412
1413
|
const file = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
|
|
1413
1414
|
if (!file)
|
|
@@ -1453,14 +1454,197 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
1453
1454
|
/* ─────────────────────────────────────────
|
|
1454
1455
|
Node helpers
|
|
1455
1456
|
───────────────────────────────────────── */
|
|
1456
|
-
const toggleNodeVisibility =
|
|
1457
|
+
const toggleNodeVisibility = Ae.useCallback((node) => {
|
|
1457
1458
|
node.object.visible = !node.object.visible;
|
|
1458
1459
|
rebuildTree();
|
|
1459
1460
|
}, [rebuildTree]);
|
|
1460
|
-
const toggleNodeExpanded =
|
|
1461
|
+
const toggleNodeExpanded = Ae.useCallback((node) => {
|
|
1461
1462
|
node.expanded = !node.expanded;
|
|
1462
1463
|
setSceneTree((prev) => [...prev]);
|
|
1463
1464
|
}, []);
|
|
1465
|
+
/* ─────────────────────────────────────────
|
|
1466
|
+
Undo / Redo
|
|
1467
|
+
───────────────────────────────────────── */
|
|
1468
|
+
const undoRedoRef = Ae.useRef(new modelEditorUtils.UndoRedoStack());
|
|
1469
|
+
const [undoRedoVer, setUndoRedoVer] = Ae.useState(0);
|
|
1470
|
+
const pushUndo = Ae.useCallback((action) => {
|
|
1471
|
+
undoRedoRef.current.push(action);
|
|
1472
|
+
setUndoRedoVer((v) => v + 1);
|
|
1473
|
+
}, []);
|
|
1474
|
+
const undo = Ae.useCallback(() => {
|
|
1475
|
+
const label = undoRedoRef.current.undo();
|
|
1476
|
+
if (label) {
|
|
1477
|
+
setStatusText(`Undo: ${label}`);
|
|
1478
|
+
rebuildTree();
|
|
1479
|
+
}
|
|
1480
|
+
setUndoRedoVer((v) => v + 1);
|
|
1481
|
+
}, [rebuildTree]);
|
|
1482
|
+
const redo = Ae.useCallback(() => {
|
|
1483
|
+
const label = undoRedoRef.current.redo();
|
|
1484
|
+
if (label) {
|
|
1485
|
+
setStatusText(`Redo: ${label}`);
|
|
1486
|
+
rebuildTree();
|
|
1487
|
+
}
|
|
1488
|
+
setUndoRedoVer((v) => v + 1);
|
|
1489
|
+
}, [rebuildTree]);
|
|
1490
|
+
const canUndo = undoRedoRef.current.canUndo();
|
|
1491
|
+
const canRedo = undoRedoRef.current.canRedo();
|
|
1492
|
+
const undoLabel = undoRedoRef.current.undoLabel;
|
|
1493
|
+
const redoLabel = undoRedoRef.current.redoLabel;
|
|
1494
|
+
// Keyboard shortcuts for undo/redo
|
|
1495
|
+
Ae.useEffect(() => {
|
|
1496
|
+
const handleKeyDown = (e) => {
|
|
1497
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "z" && !e.shiftKey) {
|
|
1498
|
+
e.preventDefault();
|
|
1499
|
+
undo();
|
|
1500
|
+
}
|
|
1501
|
+
else if ((e.ctrlKey || e.metaKey) && (e.key === "y" || (e.key === "z" && e.shiftKey))) {
|
|
1502
|
+
e.preventDefault();
|
|
1503
|
+
redo();
|
|
1504
|
+
}
|
|
1505
|
+
};
|
|
1506
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
1507
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1508
|
+
}, [undo, redo]);
|
|
1509
|
+
/* ─────────────────────────────────────────
|
|
1510
|
+
Batch Import
|
|
1511
|
+
───────────────────────────────────────── */
|
|
1512
|
+
const loadFiles = Ae.useCallback(async (files) => {
|
|
1513
|
+
const fileArray = Array.from(files).filter((f) => {
|
|
1514
|
+
const ext = f.name.substring(f.name.lastIndexOf(".")).toLowerCase();
|
|
1515
|
+
return modelEditorTypes.SUPPORTED_EXTENSIONS.includes(ext);
|
|
1516
|
+
});
|
|
1517
|
+
if (fileArray.length === 0) {
|
|
1518
|
+
setStatusText("No supported files found");
|
|
1519
|
+
return;
|
|
1520
|
+
}
|
|
1521
|
+
setStatusText(`Importing ${fileArray.length} file(s)...`);
|
|
1522
|
+
let loaded = 0;
|
|
1523
|
+
for (const file of fileArray) {
|
|
1524
|
+
try {
|
|
1525
|
+
await loadFile(file);
|
|
1526
|
+
loaded++;
|
|
1527
|
+
setStatusText(`Imported ${loaded}/${fileArray.length}: ${file.name}`);
|
|
1528
|
+
}
|
|
1529
|
+
catch (err) {
|
|
1530
|
+
log.error(`Failed to import ${file.name}:`, err);
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
setStatusText(`Batch import complete: ${loaded}/${fileArray.length} files loaded`);
|
|
1534
|
+
}, [loadFile]);
|
|
1535
|
+
/* ─────────────────────────────────────────
|
|
1536
|
+
PBR Presets
|
|
1537
|
+
───────────────────────────────────────── */
|
|
1538
|
+
const applyPreset = Ae.useCallback((preset) => {
|
|
1539
|
+
const node = selectedNode;
|
|
1540
|
+
if (!node || !(node.object instanceof THREE__namespace.Mesh)) {
|
|
1541
|
+
setStatusText("Select a mesh to apply material preset");
|
|
1542
|
+
return;
|
|
1543
|
+
}
|
|
1544
|
+
const mesh = node.object;
|
|
1545
|
+
const oldMaterial = Array.isArray(mesh.material) ? mesh.material[selMaterialIdx] : mesh.material;
|
|
1546
|
+
if (!oldMaterial)
|
|
1547
|
+
return;
|
|
1548
|
+
// Save for undo
|
|
1549
|
+
const oldClone = oldMaterial.clone();
|
|
1550
|
+
const materialIdx = selMaterialIdx;
|
|
1551
|
+
const newMaterial = modelEditorUtils.createMaterialFromPreset(preset);
|
|
1552
|
+
// Copy existing textures if present
|
|
1553
|
+
if (oldMaterial instanceof THREE__namespace.MeshStandardMaterial) {
|
|
1554
|
+
if (oldMaterial.map)
|
|
1555
|
+
newMaterial.map = oldMaterial.map;
|
|
1556
|
+
if (oldMaterial.normalMap)
|
|
1557
|
+
newMaterial.normalMap = oldMaterial.normalMap;
|
|
1558
|
+
if (oldMaterial.aoMap)
|
|
1559
|
+
newMaterial.aoMap = oldMaterial.aoMap;
|
|
1560
|
+
}
|
|
1561
|
+
if (Array.isArray(mesh.material)) {
|
|
1562
|
+
mesh.material[materialIdx] = newMaterial;
|
|
1563
|
+
}
|
|
1564
|
+
else {
|
|
1565
|
+
mesh.material = newMaterial;
|
|
1566
|
+
}
|
|
1567
|
+
pushUndo({
|
|
1568
|
+
label: `Apply ${preset.name} preset`,
|
|
1569
|
+
undo: () => {
|
|
1570
|
+
if (Array.isArray(mesh.material)) {
|
|
1571
|
+
mesh.material[materialIdx] = oldClone;
|
|
1572
|
+
}
|
|
1573
|
+
else {
|
|
1574
|
+
mesh.material = oldClone;
|
|
1575
|
+
}
|
|
1576
|
+
},
|
|
1577
|
+
redo: () => {
|
|
1578
|
+
if (Array.isArray(mesh.material)) {
|
|
1579
|
+
mesh.material[materialIdx] = newMaterial;
|
|
1580
|
+
}
|
|
1581
|
+
else {
|
|
1582
|
+
mesh.material = newMaterial;
|
|
1583
|
+
}
|
|
1584
|
+
},
|
|
1585
|
+
});
|
|
1586
|
+
setMatRefresh((v) => v + 1);
|
|
1587
|
+
setStatusText(`Applied ${preset.name} preset`);
|
|
1588
|
+
}, [selectedNode, selMaterialIdx, pushUndo]);
|
|
1589
|
+
/* ─────────────────────────────────────────
|
|
1590
|
+
LOD Generation
|
|
1591
|
+
───────────────────────────────────────── */
|
|
1592
|
+
const generateSelectedLOD = Ae.useCallback(() => {
|
|
1593
|
+
const node = selectedNode;
|
|
1594
|
+
if (!node || !(node.object instanceof THREE__namespace.Mesh)) {
|
|
1595
|
+
setStatusText("Select a mesh to generate LOD");
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1598
|
+
const mesh = node.object;
|
|
1599
|
+
const parent = mesh.parent || sceneRef.current;
|
|
1600
|
+
const lod = modelEditorUtils.generateLOD(mesh);
|
|
1601
|
+
parent.remove(mesh);
|
|
1602
|
+
parent.add(lod);
|
|
1603
|
+
pushUndo({
|
|
1604
|
+
label: `Generate LOD for ${mesh.name}`,
|
|
1605
|
+
undo: () => { parent.remove(lod); parent.add(mesh); },
|
|
1606
|
+
redo: () => { parent.remove(mesh); parent.add(lod); },
|
|
1607
|
+
});
|
|
1608
|
+
rebuildTree();
|
|
1609
|
+
setStatusText(`Generated LOD for ${mesh.name} (4 levels)`);
|
|
1610
|
+
}, [selectedNode, pushUndo, rebuildTree]);
|
|
1611
|
+
/* ─────────────────────────────────────────
|
|
1612
|
+
Instancing
|
|
1613
|
+
───────────────────────────────────────── */
|
|
1614
|
+
const convertSelectedToInstanced = Ae.useCallback(() => {
|
|
1615
|
+
const root = rootObjectRef.current || sceneRef.current;
|
|
1616
|
+
const result = modelEditorUtils.convertToInstanced(root);
|
|
1617
|
+
if (result.created === 0) {
|
|
1618
|
+
setStatusText("No duplicate meshes found to instance");
|
|
1619
|
+
return;
|
|
1620
|
+
}
|
|
1621
|
+
rebuildTree();
|
|
1622
|
+
setStatusText(`Created ${result.created} instanced meshes, removed ${result.removed} duplicates`);
|
|
1623
|
+
}, [rebuildTree]);
|
|
1624
|
+
/* ── HDRI Environment ── */
|
|
1625
|
+
const applyHDRI = Ae.useCallback((preset) => {
|
|
1626
|
+
const renderer = rendererRef.current;
|
|
1627
|
+
if (!renderer)
|
|
1628
|
+
return;
|
|
1629
|
+
modelEditorUtils.applyHDRIPreset(sceneRef.current, renderer, preset);
|
|
1630
|
+
setBgColor(`#${new THREE__namespace.Color(preset.bgColor).getHexString()}`);
|
|
1631
|
+
if (preset.fog) {
|
|
1632
|
+
setFogEnabled(true);
|
|
1633
|
+
setFogColor(`#${new THREE__namespace.Color(preset.fog.color).getHexString()}`);
|
|
1634
|
+
setFogNear(preset.fog.near);
|
|
1635
|
+
setFogFar(preset.fog.far);
|
|
1636
|
+
}
|
|
1637
|
+
else {
|
|
1638
|
+
setFogEnabled(false);
|
|
1639
|
+
}
|
|
1640
|
+
setStatusText(`Environment: ${preset.name}`);
|
|
1641
|
+
}, []);
|
|
1642
|
+
/* ── Snap ── */
|
|
1643
|
+
const snapPoint = Ae.useCallback((point) => {
|
|
1644
|
+
const root = rootObjectRef.current || sceneRef.current;
|
|
1645
|
+
const modes = snapEnabled ? ["vertex", "edge", "grid"] : ["grid"];
|
|
1646
|
+
return modelEditorUtils.snapMulti(point, root, snapGrid, modes, 0.5);
|
|
1647
|
+
}, [snapEnabled, snapGrid]);
|
|
1464
1648
|
/* ═══════════════════════════════════════════
|
|
1465
1649
|
Return API
|
|
1466
1650
|
═══════════════════════════════════════════ */
|
|
@@ -1574,6 +1758,26 @@ function useModelEditor({ onSaveToLibrary, onAIPoseVideo, }) {
|
|
|
1574
1758
|
toggleNodeVisibility,
|
|
1575
1759
|
toggleNodeExpanded,
|
|
1576
1760
|
setStatusText,
|
|
1761
|
+
// undo/redo
|
|
1762
|
+
undo,
|
|
1763
|
+
redo,
|
|
1764
|
+
canUndo,
|
|
1765
|
+
canRedo,
|
|
1766
|
+
undoLabel,
|
|
1767
|
+
redoLabel,
|
|
1768
|
+
// batch import
|
|
1769
|
+
loadFiles,
|
|
1770
|
+
// PBR presets
|
|
1771
|
+
pbrPresets: modelEditorUtils.PBR_PRESETS,
|
|
1772
|
+
applyPreset,
|
|
1773
|
+
// LOD & instancing
|
|
1774
|
+
generateSelectedLOD,
|
|
1775
|
+
convertSelectedToInstanced,
|
|
1776
|
+
// HDRI environment
|
|
1777
|
+
hdriPresets: modelEditorUtils.HDRI_PRESETS,
|
|
1778
|
+
applyHDRIPreset: applyHDRI,
|
|
1779
|
+
// Snap
|
|
1780
|
+
snapPoint,
|
|
1577
1781
|
};
|
|
1578
1782
|
}
|
|
1579
1783
|
|