diff --git a/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx b/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx index 818bd8a..5cb4b4e 100644 --- a/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx @@ -6,145 +6,128 @@ import PositionInput from "../customInput/PositionInputs"; import RotationInput from "../customInput/RotationInput"; import { useSelectedFloorItem, useObjectPosition, useObjectRotation } from "../../../../store/builder/store"; import { useSceneContext } from "../../../../modules/scene/sceneContext"; +import { useBuilderStore } from "../../../../store/builder/useBuilderStore"; interface UserData { - id: number; // Unique identifier for the user data - label: string; // Label of the user data field - value: string; // Value of the user data field + id: number; + label: string; + value: string; } const AssetProperties: React.FC = () => { - const [userData, setUserData] = useState([]); // State to track user data - const [nextId, setNextId] = useState(1); // Unique ID for new entries - const { selectedFloorItem } = useSelectedFloorItem(); - const { objectPosition } = useObjectPosition(); - const { objectRotation } = useObjectRotation(); - const { assetStore } = useSceneContext(); - const { assets, setCurrentAnimation } = assetStore() - const [hoveredIndex, setHoveredIndex] = useState(null); - const [isPlaying, setIsplaying] = useState(false); - // Function to handle adding new user data - const handleAddUserData = () => { - const newUserData: UserData = { - id: nextId, - label: `Property ${nextId}`, - value: "", + const [userData, setUserData] = useState([]); + const { selectedFloorItem } = useSelectedFloorItem(); + const { objectPosition } = useObjectPosition(); + const { objectRotation } = useObjectRotation(); + const { assetStore } = useSceneContext(); + const { assets, setCurrentAnimation } = assetStore(); + const { loopAnimation } = useBuilderStore(); + const [hoveredIndex, setHoveredIndex] = useState(null); + + const handleAddUserData = () => { }; - setUserData([...userData, newUserData]); - setNextId(nextId + 1); // Increment the ID for the next entry - }; - // Function to update the value of a user data entry - const handleUserDataChange = (id: number, newValue: string) => { - setUserData((prevUserData) => - prevUserData.map((data) => - data.id === id ? { ...data, value: newValue } : data - ) - ); - }; + const handleUserDataChange = (id: number, newValue: string) => { + }; - // Remove user data - const handleRemoveUserData = (id: number) => { - setUserData((prevUserData) => - prevUserData.filter((data) => data.id !== id) - ); - }; + const handleRemoveUserData = (id: number) => { + }; - const handleAnimationClick = (animation: string) => { - if (selectedFloorItem) { - setCurrentAnimation(selectedFloorItem.uuid, animation, true); + const handleAnimationClick = (animation: string) => { + if (selectedFloorItem) { + setCurrentAnimation(selectedFloorItem.uuid, animation, true, loopAnimation); + } } - } - if (!selectedFloorItem) return null; + if (!selectedFloorItem) return null; - return ( -
- {/* Name */} -
{selectedFloorItem.userData.modelName}
-
- {objectPosition.x && objectPosition.z && - { }} - value1={parseFloat(objectPosition.x.toFixed(5))} - value2={parseFloat(objectPosition.z.toFixed(5))} - /> - } - {objectRotation.y && - { }} - value={parseFloat(objectRotation.y.toFixed(5))} - /> - } -
+ return ( +
+ {/* Name */} +
{selectedFloorItem.userData.modelName}
+
+ {objectPosition && + { }} + value1={parseFloat(objectPosition.x.toFixed(5))} + value2={parseFloat(objectPosition.z.toFixed(5))} + /> + } + {objectRotation && + { }} + value={parseFloat(objectRotation.y.toFixed(5))} + /> + } +
-
-
Render settings
- - -
+
+
Render settings
+ + +
-
-
User Data
- {userData.map((data) => ( -
- handleUserDataChange(data.id, newValue)} // Pass the change handler - /> -
handleRemoveUserData(data.id)} - > - -
-
- ))} +
+
User Data
+ {userData.map((data) => ( +
+ handleUserDataChange(data.id, newValue)} + /> +
handleRemoveUserData(data.id)} + > + +
+
+ ))} - {/* Add new user data */} -
- + Add -
-
-
- {selectedFloorItem.uuid &&
Animations
} - {assets.map((asset) => ( -
- {asset.modelUuid === selectedFloorItem.uuid && - asset.animations && - asset.animations.length > 0 && - asset.animations.map((animation, index) => ( -
-
handleAnimationClick(animation)} - onMouseEnter={() => setHoveredIndex(index)} - onMouseLeave={() => setHoveredIndex(null)} - style={{ - height: "20px", - width: "100%", - borderRadius: "5px", - background: - hoveredIndex === index - ? "#7b4cd3" - : "transparent", - }} - > - {animation.charAt(0).toUpperCase() + - animation.slice(1).toLowerCase()} -
+ {/* Add new user data */} +
+ + Add
- ))} -
- ))} -
-
- ); +
+
+ {selectedFloorItem.uuid &&
Animations
} + {assets.map((asset) => ( +
+ {asset.modelUuid === selectedFloorItem.uuid && + asset.animations && + asset.animations.length > 0 && + asset.animations.map((animation, index) => ( +
+
handleAnimationClick(animation)} + onMouseEnter={() => setHoveredIndex(index)} + onMouseLeave={() => setHoveredIndex(null)} + style={{ + height: "20px", + width: "100%", + borderRadius: "5px", + background: + hoveredIndex === index + ? "#7b4cd3" + : "transparent", + }} + > + {animation.charAt(0).toUpperCase() + + animation.slice(1).toLowerCase()} +
+
+ ))} +
+ ))} +
+
+ ); }; export default AssetProperties; diff --git a/app/src/modules/builder/asset/assetsGroup.tsx b/app/src/modules/builder/asset/assetsGroup.tsx index cb54f87..6fcdc7c 100644 --- a/app/src/modules/builder/asset/assetsGroup.tsx +++ b/app/src/modules/builder/asset/assetsGroup.tsx @@ -1,4 +1,4 @@ - import * as THREE from "three" +import * as THREE from "three" import { useEffect } from 'react' import { getFloorAssets } from '../../../services/factoryBuilder/asset/floorAsset/getFloorItemsApi'; import { useLoadingProgress, useRenameModeStore, useSelectedFloorItem, useSelectedItem, useSocketStore } from '../../../store/builder/store'; @@ -226,7 +226,8 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { modelUuid: item.modelUuid, modelName: item.modelName, position: item.position, - rotation: [item.rotation.x, item.rotation.y, item.rotation.z], state: "idle", + rotation: [item.rotation.x, item.rotation.y, item.rotation.z], + state: "idle", type: "storageUnit", point: { uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(), @@ -242,6 +243,36 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { } }; addEvent(storageEvent); + } else if (item.eventData.type === 'Human') { + const humanEvent: HumanEventSchema = { + modelUuid: item.modelUuid, + modelName: item.modelName, + position: item.position, + rotation: [item.rotation.x, item.rotation.y, item.rotation.z], + state: "idle", + type: "human", + point: { + uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(), + position: [item.eventData.point?.position[0] || 0, item.eventData.point?.position[1] || 0, item.eventData.point?.position[2] || 0], + rotation: [item.eventData.point?.rotation[0] || 0, item.eventData.point?.rotation[1] || 0, item.eventData.point?.rotation[2] || 0], + actions: [ + { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Action 1", + actionType: "animation", + animation: null, + loopAnimation: true, + loadCapacity: 1, + travelPoints: { + startPoint: null, + endPoint: null, + }, + triggers: [] + } + ] + } + } + addEvent(humanEvent); } } else { assets.push({ diff --git a/app/src/modules/builder/asset/functions/addAssetModel.ts b/app/src/modules/builder/asset/functions/addAssetModel.ts index 978b45c..9fa104c 100644 --- a/app/src/modules/builder/asset/functions/addAssetModel.ts +++ b/app/src/modules/builder/asset/functions/addAssetModel.ts @@ -162,6 +162,7 @@ async function handleModelLoad( // SOCKET if (selectedItem.type) { + console.log('selectedItem: ', selectedItem); const data = PointsCalculator( selectedItem.type, gltf.scene.clone(), @@ -170,7 +171,7 @@ async function handleModelLoad( if (!data || !data.points) return; - const eventData: any = { type: selectedItem.type, }; + const eventData: any = { type: selectedItem.type }; if (selectedItem.type === "Conveyor") { const ConveyorEvent: ConveyorEventSchema = { @@ -378,6 +379,7 @@ async function handleModelLoad( actionName: "Action 1", actionType: "animation", animation: null, + loopAnimation: true, loadCapacity: 1, travelPoints: { startPoint: null, @@ -416,6 +418,7 @@ async function handleModelLoad( userId: userId, }; + console.log('completeData: ', completeData); socket.emit("v1:model-asset:add", completeData); const asset: Asset = { diff --git a/app/src/modules/builder/asset/models/model/model.tsx b/app/src/modules/builder/asset/models/model/model.tsx index b47fcd7..3af4bde 100644 --- a/app/src/modules/builder/asset/models/model/model.tsx +++ b/app/src/modules/builder/asset/models/model/model.tsx @@ -6,7 +6,7 @@ import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'; import { ThreeEvent, useFrame, useThree } from '@react-three/fiber'; import { useActiveTool, useDeletableFloorItem, useLimitDistance, useRenderDistance, useSelectedFloorItem, useSocketStore, useToggleView, useToolMode } from '../../../../../store/builder/store'; import { AssetBoundingBox } from '../../functions/assetBoundingBox'; -import { CameraControls, Html } from '@react-three/drei'; +import { CameraControls } from '@react-three/drei'; import useModuleStore, { useSubModuleStore } from '../../../../../store/useModuleStore'; import { useLeftData, useTopData } from '../../../../../store/visualization/useZone3DWidgetStore'; import { useSelectedAsset } from '../../../../../store/simulation/useSimulationStore'; @@ -15,6 +15,7 @@ import { useParams } from 'react-router-dom'; import { getUserData } from '../../../../../functions/getUserData'; import { useSceneContext } from '../../../../scene/sceneContext'; import { useVersionContext } from '../../../version/versionContext'; +import { SkeletonUtils } from 'three-stdlib'; function Model({ asset }: { readonly asset: Asset }) { const { camera, controls, gl } = useThree(); @@ -23,7 +24,7 @@ function Model({ asset }: { readonly asset: Asset }) { const { subModule } = useSubModuleStore(); const { activeModule } = useModuleStore(); const { assetStore, eventStore, productStore } = useSceneContext(); - const { assets, removeAsset, setAnimations } = assetStore(); + const { removeAsset, setAnimations, resetAnimation } = assetStore(); const { setTop } = useTopData(); const { setLeft } = useLeftData(); const { getIsEventInProduct } = productStore(); @@ -33,7 +34,7 @@ function Model({ asset }: { readonly asset: Asset }) { const { setSelectedAsset, clearSelectedAsset } = useSelectedAsset(); const { socket } = useSocketStore(); const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem(); - const { setSelectedFloorItem } = useSelectedFloorItem(); + const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem(); const { limitDistance } = useLimitDistance(); const { renderDistance } = useRenderDistance(); const [isRendered, setIsRendered] = useState(false); @@ -46,13 +47,15 @@ function Model({ asset }: { readonly asset: Asset }) { const { selectedVersion } = selectedVersionStore(); const { projectId } = useParams(); const { userId, organization } = getUserData(); - const [animationNames, setAnimationNames] = useState([]); const mixerRef = useRef(); const actions = useRef<{ [name: string]: THREE.AnimationAction }>({}); useEffect(() => { setDeletableFloorItem(null); - }, [activeModule, toolMode]) + if (selectedFloorItem === null) { + resetAnimation(asset.modelUuid); + } + }, [activeModule, toolMode, selectedFloorItem]) useEffect(() => { const loader = new GLTFLoader(); @@ -62,40 +65,21 @@ function Model({ asset }: { readonly asset: Asset }) { loader.setDRACOLoader(dracoLoader); const loadModel = async () => { try { - // Check Cache - // const assetId = asset.assetId; - // const cachedModel = THREE.Cache.get(assetId); - // if (cachedModel) { - // setGltfScene(cachedModel.scene.clone()); - // calculateBoundingBox(cachedModel.scene); - // return; - // } // Check Cache - // const assetId = asset.assetId; - // console.log('assetId: ', assetId); - // const cachedModel = THREE.Cache.get(assetId); - // console.log('cachedModel: ', cachedModel); - // if (cachedModel) { - // setGltfScene(cachedModel.scene.clone()); - // calculateBoundingBox(cachedModel.scene); - // return; - // } - const assetId = asset.assetId; const cachedModel = THREE.Cache.get(assetId); if (cachedModel) { - const clonedScene = cachedModel.scene.clone(); - clonedScene.animations = cachedModel.animations || []; - setGltfScene(clonedScene); - calculateBoundingBox(clonedScene); - if (cachedModel.animations && clonedScene.animations.length > 0) { - const animationName = clonedScene.animations.map((clip: any) => clip.name); - setAnimationNames(animationName) + const clone: any = SkeletonUtils.clone(cachedModel.scene); + clone.animations = cachedModel.animations || []; + setGltfScene(clone); + calculateBoundingBox(clone); + if (cachedModel.animations && clone.animations.length > 0) { + const animationName = clone.animations.map((clip: any) => clip.name); setAnimations(asset.modelUuid, animationName) - mixerRef.current = new THREE.AnimationMixer(clonedScene); + mixerRef.current = new THREE.AnimationMixer(clone); - clonedScene.animations.forEach((animation: any) => { + clone.animations.forEach((animation: any) => { const action = mixerRef.current!.clipAction(animation); actions.current[animation.name] = action; }); @@ -293,28 +277,27 @@ function Model({ asset }: { readonly asset: Asset }) { clearSelectedAsset() } } + useFrame((_, delta) => { if (mixerRef.current) { mixerRef.current.update(delta); } }); - useEffect(() => { - - if (asset.animationState && asset.animationState.playing) { + if (asset.animationState && asset.animationState.isPlaying) { if (!mixerRef.current) return; Object.values(actions.current).forEach((action) => action.stop()); const action = actions.current[asset.animationState.current]; - if (action && asset.animationState?.playing) { - action.reset().setLoop(THREE.LoopOnce, 1).play(); + if (action && asset.animationState?.isPlaying) { + const loopMode = asset.animationState.loopAnimation ? THREE.LoopRepeat : THREE.LoopOnce; + action.reset().setLoop(loopMode, loopMode === THREE.LoopRepeat ? Infinity : 1).play(); } } else { Object.values(actions.current).forEach((action) => action.stop()); } - }, [asset.animationState]) return ( diff --git a/app/src/store/builder/useAssetStore.ts b/app/src/store/builder/useAssetStore.ts index fec16b4..a9111f4 100644 --- a/app/src/store/builder/useAssetStore.ts +++ b/app/src/store/builder/useAssetStore.ts @@ -22,7 +22,8 @@ interface AssetsStore { // Animation controls setAnimations: (modelUuid: string, animations: string[]) => void; - setCurrentAnimation: (modelUuid: string, current: string, isPlaying: boolean) => void; + setCurrentAnimation: (modelUuid: string, current: string, isisPlaying: boolean, loopAnimation: boolean) => void; + resetAnimation: (modelUuid: string) => void; addAnimation: (modelUuid: string, animation: string) => void; removeAnimation: (modelUuid: string, animation: string) => void; @@ -149,18 +150,28 @@ export const createAssetStore = () => { if (asset) { asset.animations = animations; if (!asset.animationState) { - asset.animationState = { current: '', playing: false }; + asset.animationState = { current: '', isPlaying: false, loopAnimation: true }; } } }); }, - setCurrentAnimation: (modelUuid, current, isPlaying) => { + setCurrentAnimation: (modelUuid, current, isisPlaying, loopAnimation) => { set((state) => { const asset = state.assets.find(a => a.modelUuid === modelUuid); if (asset?.animationState) { asset.animationState.current = current; - asset.animationState.playing = isPlaying; + asset.animationState.isPlaying = isisPlaying; + asset.animationState.loopAnimation = loopAnimation; + } + }); + }, + + resetAnimation: (modelUuid) => { + set((state) => { + const asset = state.assets.find(a => a.modelUuid === modelUuid); + if (asset?.animationState) { + asset.animationState = { current: '', isPlaying: false, loopAnimation: true }; } }); }, @@ -184,7 +195,7 @@ export const createAssetStore = () => { if (asset?.animations) { asset.animations = asset.animations.filter(a => a !== animation); if (asset.animationState?.current === animation) { - asset.animationState.playing = false; + asset.animationState.isPlaying = false; asset.animationState.current = ''; } } diff --git a/app/src/store/builder/useBuilderStore.ts b/app/src/store/builder/useBuilderStore.ts index 6147c0c..5be4e3b 100644 --- a/app/src/store/builder/useBuilderStore.ts +++ b/app/src/store/builder/useBuilderStore.ts @@ -15,6 +15,7 @@ interface BuilderState { // Floor Asset selectedFloorAsset: Object3D | null; + loopAnimation: boolean; // Wall Settings selectedWall: Object3D | null; @@ -64,6 +65,7 @@ interface BuilderState { // Setters - Floor Asset setSelectedFloorAsset: (asset: Object3D | null) => void; + setLoopAnimation: (loop: boolean) => void; // Setters - Wall setSelectedWall: (wall: Object3D | null) => void; @@ -118,6 +120,7 @@ export const useBuilderStore = create()( deletableWallAsset: null, selectedFloorAsset: null, + loopAnimation: true, selectedWall: null, wallThickness: 0.5, @@ -197,6 +200,12 @@ export const useBuilderStore = create()( }); }, + setLoopAnimation(loopAnimation: boolean) { + set((state) => { + state.loopAnimation = loopAnimation; + }); + }, + // === Setters: Wall === setSelectedWall: (wall: Object3D | null) => { diff --git a/app/src/types/builderTypes.d.ts b/app/src/types/builderTypes.d.ts index de7586a..a8b0175 100644 --- a/app/src/types/builderTypes.d.ts +++ b/app/src/types/builderTypes.d.ts @@ -26,7 +26,8 @@ interface Asset { animations?: string[]; animationState?: { current: string; - playing: boolean; + isPlaying: boolean; + loopAnimation: boolean; }; eventData?: { type: string; diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index 1d2f364..26802f8 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -74,6 +74,7 @@ interface HumanAction { actionName: string; actionType: "animation" | "animatedTravel"; animation: string | null; + loopAnimation: boolean; loadCapacity: number; travelPoints?: { startPoint: [number, number, number] | null; endPoint: [number, number, number] | null; } triggers: TriggerSchema[];