Add scene context and animation handling to AssetProperties and Model components

- Enhanced animation handling in Model component with animation state management.
- Updated useAssetStore to support multiple animations for assets.
This commit is contained in:
2025-06-26 15:11:52 +05:30
parent e5e92d2b9f
commit d926809dec
4 changed files with 117 additions and 10 deletions

View File

@@ -31,6 +31,7 @@ import AisleProperties from "./properties/AisleProperties";
import WallProperties from "./properties/WallProperties"; import WallProperties from "./properties/WallProperties";
import { useBuilderStore } from "../../../store/builder/useBuilderStore"; import { useBuilderStore } from "../../../store/builder/useBuilderStore";
import SelectedWallProperties from "./properties/SelectedWallProperties"; import SelectedWallProperties from "./properties/SelectedWallProperties";
import { useSceneContext } from "../../../modules/scene/sceneContext";
const SideBarRight: React.FC = () => { const SideBarRight: React.FC = () => {
const { activeModule } = useModuleStore(); const { activeModule } = useModuleStore();
@@ -38,12 +39,15 @@ const SideBarRight: React.FC = () => {
const { toolMode } = useToolMode(); const { toolMode } = useToolMode();
const { subModule, setSubModule } = useSubModuleStore(); const { subModule, setSubModule } = useSubModuleStore();
const { selectedFloorItem } = useSelectedFloorItem(); const { selectedFloorItem } = useSelectedFloorItem();
const { selectedWall } = useBuilderStore(); const { selectedWall } = useBuilderStore();
const { selectedEventData } = useSelectedEventData(); const { selectedEventData } = useSelectedEventData();
const { selectedEventSphere } = useSelectedEventSphere(); const { selectedEventSphere } = useSelectedEventSphere();
const { viewVersionHistory, setVersionHistoryVisible } = useVersionHistoryVisibleStore(); const { viewVersionHistory, setVersionHistoryVisible } = useVersionHistoryVisibleStore();
const { isVersionSaved } = useSaveVersion(); const { isVersionSaved } = useSaveVersion();
// Reset activeList whenever activeModule changes // Reset activeList whenever activeModule changes
useEffect(() => { useEffect(() => {
if (activeModule !== "simulation") setSubModule("properties"); if (activeModule !== "simulation") setSubModule("properties");
@@ -142,6 +146,8 @@ const SideBarRight: React.FC = () => {
</div> </div>
)} )}
{/* process builder */} {/* process builder */}
{!viewVersionHistory && {!viewVersionHistory &&
subModule === "properties" && subModule === "properties" &&
@@ -170,6 +176,7 @@ const SideBarRight: React.FC = () => {
<div className="sidebar-right-container"> <div className="sidebar-right-container">
<div className="sidebar-right-content-container"> <div className="sidebar-right-content-container">
<AssetProperties /> <AssetProperties />
</div> </div>
</div> </div>
)} )}
@@ -224,6 +231,7 @@ const SideBarRight: React.FC = () => {
)} )}
</> </>
)} )}
{/* realtime visualization */} {/* realtime visualization */}
{activeModule === "visualization" && <Visualization />} {activeModule === "visualization" && <Visualization />}
</> </>

View File

@@ -5,6 +5,7 @@ import { RemoveIcon } from "../../../icons/ExportCommonIcons";
import PositionInput from "../customInput/PositionInputs"; import PositionInput from "../customInput/PositionInputs";
import RotationInput from "../customInput/RotationInput"; import RotationInput from "../customInput/RotationInput";
import { useSelectedFloorItem, useObjectPosition, useObjectRotation } from "../../../../store/builder/store"; import { useSelectedFloorItem, useObjectPosition, useObjectRotation } from "../../../../store/builder/store";
import { useSceneContext } from "../../../../modules/scene/sceneContext";
interface UserData { interface UserData {
id: number; // Unique identifier for the user data id: number; // Unique identifier for the user data
@@ -18,6 +19,9 @@ const AssetProperties: React.FC = () => {
const { selectedFloorItem } = useSelectedFloorItem(); const { selectedFloorItem } = useSelectedFloorItem();
const { objectPosition } = useObjectPosition(); const { objectPosition } = useObjectPosition();
const { objectRotation } = useObjectRotation(); const { objectRotation } = useObjectRotation();
const { assetStore } = useSceneContext();
const { assets, setCurrentAnimation } = assetStore()
// Function to handle adding new user data // Function to handle adding new user data
const handleAddUserData = () => { const handleAddUserData = () => {
const newUserData: UserData = { const newUserData: UserData = {
@@ -45,6 +49,12 @@ const AssetProperties: React.FC = () => {
); );
}; };
const handleAnimationClick = (animation: string) => {
if (selectedFloorItem) {
const isPlaying = selectedFloorItem.animationState?.playing || false;
setCurrentAnimation(selectedFloorItem.uuid, animation, !isPlaying);
}
}
return ( return (
<div className="asset-properties-container"> <div className="asset-properties-container">
{/* Name */} {/* Name */}
@@ -96,6 +106,22 @@ const AssetProperties: React.FC = () => {
+ Add + Add
</div> </div>
</section> </section>
<div style={{ display: "flex", flexDirection: "column" }}>
{assets.map((asset) => (
<div key={asset.modelUuid} className="asset-item">
{
asset.modelUuid === selectedFloorItem.uuid &&
asset.animations && asset.animations.length > 0
&& asset.animations.map((animation, index) => (
<div key={index} className="animation-item" style={{ gap: "5px" }} onClick={() => { handleAnimationClick(animation) }}>
{animation}
</div>
))
}
</div>
))}
</div>
</div> </div>
); );
}; };

View File

@@ -6,7 +6,7 @@ import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
import { ThreeEvent, useFrame, useThree } from '@react-three/fiber'; import { ThreeEvent, useFrame, useThree } from '@react-three/fiber';
import { useActiveTool, useDeletableFloorItem, useRenderDistance, useSelectedFloorItem, useSocketStore, useToggleView, useToolMode } from '../../../../../store/builder/store'; import { useActiveTool, useDeletableFloorItem, useRenderDistance, useSelectedFloorItem, useSocketStore, useToggleView, useToolMode } from '../../../../../store/builder/store';
import { AssetBoundingBox } from '../../functions/assetBoundingBox'; import { AssetBoundingBox } from '../../functions/assetBoundingBox';
import { CameraControls } from '@react-three/drei'; import { CameraControls, Html } from '@react-three/drei';
import useModuleStore, { useSubModuleStore } from '../../../../../store/useModuleStore'; import useModuleStore, { useSubModuleStore } from '../../../../../store/useModuleStore';
import { useLeftData, useTopData } from '../../../../../store/visualization/useZone3DWidgetStore'; import { useLeftData, useTopData } from '../../../../../store/visualization/useZone3DWidgetStore';
import { useSelectedAsset } from '../../../../../store/simulation/useSimulationStore'; import { useSelectedAsset } from '../../../../../store/simulation/useSimulationStore';
@@ -23,7 +23,7 @@ function Model({ asset }: { readonly asset: Asset }) {
const { subModule } = useSubModuleStore(); const { subModule } = useSubModuleStore();
const { activeModule } = useModuleStore(); const { activeModule } = useModuleStore();
const { assetStore, eventStore, productStore } = useSceneContext(); const { assetStore, eventStore, productStore } = useSceneContext();
const { removeAsset } = assetStore(); const { assets, removeAsset, setAnimations } = assetStore();
const { setTop } = useTopData(); const { setTop } = useTopData();
const { setLeft } = useLeftData(); const { setLeft } = useLeftData();
const { getIsEventInProduct } = productStore(); const { getIsEventInProduct } = productStore();
@@ -45,6 +45,9 @@ function Model({ asset }: { readonly asset: Asset }) {
const { selectedVersion } = selectedVersionStore(); const { selectedVersion } = selectedVersionStore();
const { projectId } = useParams(); const { projectId } = useParams();
const { userId, organization } = getUserData(); const { userId, organization } = getUserData();
const [animationNames, setAnimationNames] = useState<string[]>([]);
const mixerRef = useRef<THREE.AnimationMixer>();
const actions = useRef<{ [name: string]: THREE.AnimationAction }>({});
useEffect(() => { useEffect(() => {
setDeletableFloorItem(null); setDeletableFloorItem(null);
@@ -59,11 +62,45 @@ function Model({ asset }: { readonly asset: Asset }) {
const loadModel = async () => { const loadModel = async () => {
try { try {
// Check Cache // 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 assetId = asset.assetId;
const cachedModel = THREE.Cache.get(assetId); const cachedModel = THREE.Cache.get(assetId);
if (cachedModel) { if (cachedModel) {
setGltfScene(cachedModel.scene.clone()); const clonedScene = cachedModel.scene.clone();
calculateBoundingBox(cachedModel.scene); 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)
setAnimations(asset.modelUuid, animationName)
mixerRef.current = new THREE.AnimationMixer(clonedScene);
clonedScene.animations.forEach((animation: any) => {
const action = mixerRef.current!.clipAction(animation);
actions.current[animation.name] = action;
});
} else {
console.log('No animations');
}
return; return;
} }
@@ -252,6 +289,32 @@ function Model({ asset }: { readonly asset: Asset }) {
clearSelectedAsset() clearSelectedAsset()
} }
} }
useFrame((_, delta) => {
if (mixerRef.current) {
mixerRef.current.update(delta);
}
});
useEffect(() => {
const handlePlay = (clipName: string) => {
console.log('clipName: ', clipName, asset.animationState);
if (!mixerRef.current) return;
Object.values(actions.current).forEach((action) => action.stop());
const action = actions.current[clipName];
if (action && asset.animationState?.playing) {
action.reset().setLoop(THREE.LoopOnce, 1).play();
console.log(`Playing: ${clipName}`);
} else {
console.warn(`No action found for: ${clipName}`);
}
};
handlePlay(asset.animationState?.current || '');
}, [asset])
return ( return (
<group <group
@@ -299,7 +362,18 @@ function Model({ asset }: { readonly asset: Asset }) {
<AssetBoundingBox boundingBox={boundingBox} /> <AssetBoundingBox boundingBox={boundingBox} />
) )
)} )}
</group> {/* <group >
<Html>
<div style={{ position: 'absolute', }}>
{animationNames.map((name) => (
<button key={name} onClick={() => handlePlay(name)} style={{ margin: 4 }}>
{name}
</button>
))}
</div>
</Html>
</group> */}
</group >
); );
} }

View File

@@ -21,7 +21,7 @@ interface AssetsStore {
setOpacity: (modelUuid: string, opacity: number) => void; setOpacity: (modelUuid: string, opacity: number) => void;
// Animation controls // Animation controls
setAnimation: (modelUuid: string, animation: string) => void; setAnimations: (modelUuid: string, animations: string[]) => void;
setCurrentAnimation: (modelUuid: string, current: string, isPlaying: boolean) => void; setCurrentAnimation: (modelUuid: string, current: string, isPlaying: boolean) => void;
addAnimation: (modelUuid: string, animation: string) => void; addAnimation: (modelUuid: string, animation: string) => void;
removeAnimation: (modelUuid: string, animation: string) => void; removeAnimation: (modelUuid: string, animation: string) => void;
@@ -143,14 +143,13 @@ export const createAssetStore = () => {
}, },
// Animation controls // Animation controls
setAnimation: (modelUuid, animation) => { setAnimations: (modelUuid, animations) => {
set((state) => { set((state) => {
const asset = state.assets.find(a => a.modelUuid === modelUuid); const asset = state.assets.find(a => a.modelUuid === modelUuid);
if (asset) { if (asset) {
asset.animations = animations;
if (!asset.animationState) { if (!asset.animationState) {
asset.animationState = { current: animation, playing: false }; asset.animationState = { current: '', playing: false };
} else {
asset.animationState.current = animation;
} }
} }
}); });