pmndrs / gltfjsx

🎮 Turns GLTFs into JSX components
https://gltf.pmnd.rs
MIT License
4.45k stars 290 forks source link

[BUG] Type 'GLTFActions' does not satisfy the constraint 'AnimationClip'. #101

Open Glavin001 opened 2 years ago

Glavin001 commented 2 years ago

Problem

Using --types, for example:

npx gltfjsx --types model.gltf

TypeScript error message:

Type 'GLTFActions' does not satisfy the constraint 'AnimationClip'.
  Type 'GLTFActions' is missing the following properties from type 'AnimationClip': name, tracks, blendMode, duration, and 8 more.ts(2344)

From GLTFActions as shown:

image

  const { actions } = useAnimations<GLTFActions>(animations, group);

Redacted versions list:

$ yarn list --depth=0
yarn list v1.22.5
├─ @react-three/drei@7.2.2
├─ @react-three/fiber@7.0.6
├─ @types/react@17.0.14
├─ react@17.0.2
├─ three-stdlib@2.0.8
├─ three@0.124.0

Cause

I think it was caused by this commit appears to have changed the TypeScript generic interface for useAnimations: https://github.com/pmndrs/drei/commit/fe00d576fa39c7059cbba89d441ec996deb5d251 PR: https://github.com/pmndrs/drei/pull/378 Original bug: https://github.com/pmndrs/drei/issues/377

Solution

What worked for me:

- type GLTFActions = Record<ActionName, THREE.AnimationAction>
+ interface GLTFAction extends THREE.AnimationClip {
+  name: ActionName;
+ }

Move GLTFResult below GLTFAction / GLTFActions declaration and apply change:

type GLTFResult = GLTF & {
  nodes: {
// ...
  }
  materials: {
// ...
  }
+  animations: GLTFAction[];
}
  const { nodes, materials, animations } = useGLTF('MODEL_NAME.glb') as GLTFResult
-  const { actions } = useAnimations<GLTFActions>(animations, group);
+  const { actions } = useAnimations(animations, group);

actions['SOME_ACTION'].play();

actions has index signature with ActionName keys instead of `string

useAnimations does not need to explicitly pass the type GLTFActions because GLTFResult already has type narrowed interface for animations, which extends THREE.AnimationClip.


Please let me know if you think this is an acceptable work around and then I can try to make a Pull Request to contribute to GLTFJSX. Thank you for this awesome tool!

rafelis1997 commented 1 year ago

Hi, for some reason this does not work for me. My model has two animations and typescript is having trouble to use the string value as key because the actions type comes like this const actions: { WavingAnim: THREE.AnimationAction | null; ScareAnim: THREE.AnimationAction | null; }

do you know why it's having this possible null value or how topass by this error?

const actions: { WavingAnim: THREE.AnimationAction | null; ScareAnim: THREE.AnimationAction | null; } Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ WavingAnim: AnimationAction | null; ScareAnim: AnimationAction | null; }'. No index signature with a parameter of type 'string' was found on type '{ WavingAnim: AnimationAction | null; ScareAnim: AnimationAction | null; }'.ts(7053)

nunoskew commented 1 year ago

hey @rafelis1997 try actions.WavingAnim!.play() and/or actions.ScareAnim!.play(). Let me know how it worked for you.

shaase commented 1 year ago

Hey @Glavin001, I'm new to this, but using this in a Typescript react app. I saw the same warnings you saw, and your fixes worked perfectly for me. Let me know if there's something else that would be helpful to test. Cheers!

aVileBroker commented 1 year ago

Hopping on this thread to say I encountered the same thing - and for some reason I couldn't use the actions until I fixed the type (?). The actions were all coming back as {} which had no ActionNames as potential keys in the Record.

Using @Glavin001 's suggestions for the gltfjsx library, I was able to simply add this to my tsx file:

type ActionName = 'actionNameOne' | 'actionNameTwo';

interface GLTFAction extends THREE.AnimationClip {
  name: ActionName;
}

interface MyGLTFResult extends GLTFResult {
  animations: GLTFAction[];
}
  const groupRef = useRef<THREE.Group>(null);
  const { nodes, materials, animations } = useGLTF('/model.gltf') as MyGLTFResult;

  const { actions } = useAnimations(animations, groupRef);

  useEffect(() => {
    console.log(actions);
    actions?.actionNameOne?.play();
  });

Obviously I won't use this useEffect as-is, but it does demonstrate that this workaround is viable until gltfjsx is updated. Hope that helped anyone with the same problem!

drcmda commented 1 year ago

could you make this a pr?

davcri commented 1 year ago

@Glavin001 thanks for sharing your solution.

Is someone already working on a PR @drcmda ? If not, I could give it a try.

rafelis1997 commented 1 year ago

@nunoskew actually this does not solve the types issue, basically the GLTFAction type does not recognize this constrains. This leads to a series of erros like not having the animations correctly infered in actions, for example

rafelis1997 commented 1 year ago

I've implemented the @Glavin001 solution on this PR https://github.com/pmndrs/gltfjsx/pull/193