-1
$\begingroup$

So my question is that I want to change the texture of the object after a few frames. For example, after every 100 frames, the texture changes to the next texture. I have this little bit of code, so can you please.

import bpy

D = bpy.data

D.objects['Cube'].material_slots[0].material = D.materials['Material.001']

BTW, I am using blender 2.83.

$\endgroup$
3
  • 2
    $\begingroup$ Objects don't have textures, materials do. Also your code is apparently attempting to assign materials to slots, there doesn't seem to be any code dealing with textures. Could you clarify what you are trying to achieve? $\endgroup$ Apr 7 '21 at 15:46
  • $\begingroup$ I don't have a complete answer (setting a key) but for changing materials I would use a mix shader and key the Factor from 0 to 1, as in bpy.data.materials["Material1"].node_tree.nodes["Mix Shader"].inputs[0].default_value = 0 . How to set the "key" for these changes... uhm... $\endgroup$
    – james_t
    Apr 7 '21 at 19:17
  • $\begingroup$ Besides previous comment, there may also be a way to subscribe to a frame-change message and set the material accordingly. $\endgroup$
    – james_t
    Apr 7 '21 at 19:21
1
$\begingroup$

I had time and interest to conjure up an example python script that may do what you're looking for: to reassign a material based on a frame change. It uses a call-back Handler that subscribes to frame changes and applies a different material when it detects a frame # boundary change. You'd want to enhance this perhaps with a list of frame changes and Material names in two lists, for multiple materials.

This assume objects['Cube'], and materials['Material.001'] and materials['Material.002']

The file contains the script and demo object and materials (plus an extra animated sphere). Note that I also incorporated @MarkusvonBroady's Group technique as an example applied to the Sphere object as Material.003.

import bpy

matlID=0
    
# this handler will is set up to be called with the "handlers.frame_change_pre" callback for every frame change
def frame_change_handler( scene ):
    global matlID
    print( "frame=", bpy.context.scene.frame_current )
    currFrame = bpy.context.scene.frame_current
    print(matlID)
    if ( (currFrame >= 10) and (matlID!=2) ):
        print("change to Material.002")
        D.objects['Cube'].material_slots[0].material = bpy.data.materials['Material.002']
        matlID=2
    if ( (currFrame < 10) and (matlID!=1) ):
        print("change to Material.001")
        D.objects['Cube'].material_slots[0].material = bpy.data.materials['Material.001']
        matlID=1 



D = bpy.data
# initialize to first material
D.objects['Cube'].material_slots[0].material = D.materials['Material.001']
matlID=1
for m in D.materials:
    print(m)

bpy.app.handlers.frame_change_pre.clear()

bpy.app.handlers.frame_change_pre.append( frame_change_handler )

print( "frame_change being tracked" )
```
$\endgroup$
1
$\begingroup$

for "changing materials" I might use a mix shader in a single material and key the Factor between 0 and 1, with animation. As you'll see in the screencap, the factor is in color showing that it was animated (by the following script)

enter image description here

It's important to notice that when you add Mix Shaders, they are not referenced by the (re)name you give them but appear to be "Mix Shader", "Mix Shader.001", "Mix Shader.002" and so on.

Per @MarkusvonBroady's comment below, you can also use Node Groups to mix "Material Outputs", to better organize what could become an unwieldly number of nodes in a single Material definition. Here is the file.


for node in bpy.data.materials["MyMatl with mixer"].node_tree.nodes:
    print(node)
bpy.data.materials["Material.003"].node_tree.nodes["Mix Shader.001"].inputs[0].default_value = 0
bpy.data.materials["Material.003"].node_tree.nodes["Mix Shader.001"].inputs[0].keyframe_insert('default_value', frame=1)
bpy.data.materials["Material.003"].node_tree.nodes["Mix Shader.001"].inputs[0].keyframe_insert('default_value', frame=49)
bpy.data.materials["Material.003"].node_tree.nodes["Mix Shader.001"].inputs[0].default_value = 1
bpy.data.materials["Material.003"].node_tree.nodes["Mix Shader.001"].inputs[0].keyframe_insert('default_value', frame=50)

$\endgroup$
7
  • $\begingroup$ Hi, I have seen your answer and I had tried something similar, but the problem is I will be having 10 textures, not 2, so if you could please give a solution for something like that, I would be eternally grateful. $\endgroup$
    – Aster17
    Apr 9 '21 at 2:54
  • $\begingroup$ @Aster17 you can chain mix nodes like so: i.imgur.com/yIAwlB9.png the drivers are simply #frame > 10, #frame > 20, #frame > 30 which mean the first texture will display at the beginning and be switched to 2nd on frame 10, which will be switched to 3rd on frame 20 which will be switched to 4th on frame 30. You should be fine doing that for 10 textures. Fore more textures consider an automated script to generate the material or a custom node like here: blender.stackexchange.com/questions/67487/… $\endgroup$ Apr 10 '21 at 0:09
  • $\begingroup$ @MarkusvonBroady Thanks a lot Markus, I really appreciate it. $\endgroup$
    – Aster17
    Apr 10 '21 at 2:58
  • 1
    $\begingroup$ @james_t you can use a custom group, and use the output it sends to the Output node inside another material. But I don't see how it helps. $\endgroup$ Apr 10 '21 at 20:59
  • 1
    $\begingroup$ Thanks @MarkusvonBroady for teaching me how this works. This could be useful as an organizing and re-use approach, as I'm sure that's the intention of node groups. I have included it in my blend file in my latest answer for "dummies like me" who learn lots from you with more expertise. $\endgroup$
    – james_t
    Apr 12 '21 at 16:06

Not the answer you're looking for? Browse other questions tagged or ask your own question.