Open marwie opened 3 months ago
Very valuable addition in my view. I usually have to make a lot of manual additions to my gltfjsx output (i.e. adding custom children into the tree) and whenever the model changes in any significant way those changes to the jsx get lost.
// generated gltfjsx
function Tower() {
return (
<mesh material={myMaterial} geometry={towerGeometry} />
{props.lightsource}
</mesh>
)
}
// use gltfjsx component throughout the app with different elements injected into the generated code
<>
{/*actual three light*/}
<Tower lightsource={<pointLight intensity={0.5} />} />
{/*or with a sprite animation*/}
<Tower lightsource={<SpriteAnimator src={fireSprite} loop />}
{/*or with a custom vfx effect component*/}
<Tower lightsource={<VfxSpawner preset={"fire"} />}
</>
Here is a real world example where the castle environment is quite complex and ran through gltfjsx (couple hundred LOC). I had to manually search for these pillars in the generated code to insert my animated sprites. With this change I could already mark these pillars inside blender and just pass the sprite component from the outside, not having to worry about changes that will be made to the environment in the future.
@marwie i think it should be
export function Model({ foo, bar, ...props }) {
const { nodes, materials } = useGLTF('/untitled-transformed.glb')
return (
<group {...props} dispose={null}>
<mesh geometry={nodes.Tower_Brick_MT_0.geometry} material={materials.Brick_MT} position={[0, 7.561, 0]} rotation={[-Math.PI / 2, 0, 0]} />
<mesh geometry={nodes.Tower_Details_MT_0.geometry} material={materials.Details_MT} position={[0, 7.561, 0]} rotation={[-Math.PI / 2, 0, 0]} />
<mesh geometry={nodes.Tower_Glass_MT_0.geometry} material={materials.Glass_MT} position={[0, 7.561, 0]} rotation={[-Math.PI / 2, 0, 0]} />
<mesh geometry={nodes.Tower_Plaster_MT_0.geometry} material={materials.Plaster_MT} position={[0, 7.561, 0]} rotation={[-Math.PI / 2, 0, 0]}>
{foo}
</mesh>
<mesh geometry={nodes.Tower_Roof_MT_0.geometry} material={materials.Roof_MT} position={[0, 7.561, 0]} rotation={[-Math.PI / 2, 0, 0]}>
{bar}
</mesh>
</group>
)
}
otherwise it would spread props all over the root mesh (and could also introduce some real problems, like naming a slot "position". it would require though that slots for all nodes are known beforehand, just some javascript mapping and reducing.
Ah that's a good point. I missed that. I think currently all objects are collected at the start of codegen anyways so collecting that info could be added there.
What do you think about the callbacks idea (or slots becoming functions) what I mentioned at the end of the post?
(to allow something like (ref) => useFrame(...)
)
Ah that's a good point. I missed that. I think currently all objects are collected at the start of codegen anyways so collecting that info could be added there.
What do you think about the callbacks idea (or slots becoming functions) what I mentioned at the end of the post? (to allow something like
(ref) => useFrame(...)
)
wouldn't be necessary imo. defining functions inline is considered bad because this isn't a true component, it would un-mount/re-mount every render. if it contained useEffect(() => ..., []) it would fire every render as well.
as for accessing parents, you can do this
<Tower roof={<Foo />} />
function Foo(props) {
const ref = useRef()
useLayoutEffect(() => {
console.log(ref.current.parent)
}, [])
useFrame((state, delta) => {
console.log(ref.current.parent)
})
return <group ref={ref} />
}
Any thoughts on the naming of prop
vs slot
for the custom data field?
The default blender name is prop
which also seems fitting and it would save a few clicks in Blender of renaming "prop" to "slot" and users would just have to change the type to a String to become useable.
Example output:
export function Model({ cat, screen, ...props }) {
const { nodes, materials } = useGLTF('/untitled.glb')
return (
<group {...props} dispose={null}>
<group rotation={[-Math.PI / 2, 0, 0]} scale={0.002}>
<group rotation={[Math.PI / 2, 0, 0]}>
<group>
<mesh geometry={nodes.defaultMaterial001.geometry} material={materials.wire_134006006} />
{cat}
</group>
<group>
<mesh geometry={nodes.defaultMaterial.geometry} material={materials.Screen} />
{screen}
</group>
</group>
</group>
</group>
)
}
Objects that have a slot property would also not be pruned with this change.
Wondering, could it make sense to have a differentiation between something like childSlots
and propSlots
, for the cases where you'd want to set an actual prop (i.e. position, visibility, etc.) from the outside, vs. injecting elements into the tree?
Other than that, very much looking forward to this, being able to decouple the generated code and lift dependencies to the parent component, not having to re-edit the jsx every time you update your meshes/scene will help a lot with more demanding usecases where you are iterating a lot (like gamedev).
Any thoughts on the naming of
prop
vsslot
for the custom data field?The default blender name is
prop
which also seems fitting and it would save a few clicks in Blender of renaming "prop" to "slot" and users would just have to change the type to a String to become useable.
no issue with that, let's make it the closest to blender defaults
Let me know if there's anything else you'd like to change
I was wondering how this works with multiple slots. I was testing the process on my end and you can only add one slot
custom prop. Trying to add another merges into the first. Would it be supported with some kind of symbol, like a ,
?
@krispya You mean adding multiple slots to the same object in blender? When would you want to do that for example?
I guess you are right, I was considering something more like a registry. If there is a name collision what happens?
Multiple objects can have the same slot name (e.g. you can add the same custom property slot name in blender to multiple objects).
The slotted objects will then appear in multiple places.
export function Model({ my_slot, ...props }) {
const { nodes, materials } = useGLTF('/untitled.glb')
return (
<group {...props} dispose={null}>
<group rotation={[-Math.PI / 2, 0, 0]} scale={0.002}>
<group rotation={[Math.PI / 2, 0, 0]}>
<group>
<mesh geometry={nodes.defaultMaterial001.geometry} material={materials.wire_134006006} />
{my_slot}
</group>
<group>
<mesh geometry={nodes.defaultMaterial.geometry} material={materials.Screen} />
{my_slot}
</group>
</group>
</group>
</group>
)
}
Is that what you meant?
What about multiple different props, i.e. my_slot1, my_slot2?
Like here? https://github.com/pmndrs/gltfjsx/pull/265#issuecomment-2183555504 Or do you mean something else?
Multiple objects can have the same slot name (e.g. you can add the same custom property slot name in blender to multiple objects).
The slotted objects will then appear in multiple places.
export function Model({ my_slot, ...props }) { const { nodes, materials } = useGLTF('/untitled.glb') return ( <group {...props} dispose={null}> <group rotation={[-Math.PI / 2, 0, 0]} scale={0.002}> <group rotation={[Math.PI / 2, 0, 0]}> <group> <mesh geometry={nodes.defaultMaterial001.geometry} material={materials.wire_134006006} /> {my_slot} </group> <group> <mesh geometry={nodes.defaultMaterial.geometry} material={materials.Screen} /> {my_slot} </group> </group> </group> </group> ) }
Is that what you meant?
Looks good to me.
Like here? #265 (comment) Or do you mean something else?
Ah yeah that's what I meant!
@drcmda just squashed and rebased on the latest release and updated the example here: https://codesandbox.io/p/sandbox/xenodochial-dawn-77st43
@drcmda let me know if you'd like to have anything else changed or checked
@marwie could you solve the conflicts?
@drcmda done
@drcmda hi do you have any further concerns regarding the feature? :)
This PR allows to use custom properties defined in Blender to be used as custom react slots.
Example
https://codesandbox.io/p/sandbox/xenodochial-dawn-77st43
Usage:
1) create a custom string property in Blender and name it
slot
and enter a value e.g."mySlot"
2) export to glTF or GLB (make sure to enable Include/Custom Properties in the Exporter) 3) usegltfjsx myModel.glb
as usual 4) see the gltfjsx output has generated{ props.mySlot }
which can now be access from outsideExample
Codegen
Screenshots
https://github.com/pmndrs/gltfjsx/assets/5083203/c6fc0f05-0491-4d40-a722-b424180f68cb
Further Ideas (not implemented)
Props could possibly also / instead be functions that take a ref argument - perhaps with a different property name in blender like
callback: "myCallback"
. I'm not sure about drawbacks of potentially creating a lot of refs vs just injecting properties and/or if this is already possible with the code in this PR through some other hook.E.g. codegen would then create refs for all objects that need them and invoke the callback methods
This PR is funded by needle