1
$\begingroup$

The Setup:

I was trying to create a material and a texture, and then attach the texture to the material's Displacement slot, but I couldn't find the texture node's outputs through the Python api.

Here's my test scene:

import os, bpy

bpy.context.scene.render.engine = 'CYCLES'

# new texture
texture=bpy.data.textures.new('tex', 'IMAGE')
texture.use_nodes = True
texnodes = texture.node_tree.nodes

# new material
mat = bpy.data.materials.new('mat')
mat.use_nodes = True
matnodes = mat.node_tree.nodes

# assign texture to material's displacement
disp = matnodes['Material Output'].inputs['Displacement']
mat.node_tree.links.new(disp, texnodes["Output"].color)

That last line threw the error:

TypeError: NodeLinks.new(): error with argument 2, "output" - Function.output expected a NodeSocket type, not Color

I've seen this method used elsewhere:

mat.node_tree.links.new(disp, texture.outputs[0])

But if I try it in this situation, it throws this error:

AttributeError: 'ImageTexture' object has no attribute 'outputs'

???

The Plot Twist:

Then I discovered that if I create the texture directly on the material, like so:

texture=matnodes.new("ShaderNodeTexImage")

...the texture.outputs[0] method works, although then the texture node claims it doesn't have a node_tree property.

Questions:

What's the difference between these two textures? Is there a way to make my first try work like the second? What's the best way to do this?

$\endgroup$
3
$\begingroup$

The cool thing about Blender is you can try stuff in the Python Console and see exactly what's happening!

The first way is NOT creating a node that can be used in a shader. It's simply creating a texture that Blender can use for various things.

enter image description here

You don't want to say use_nodes=True because it doesn't make this texture into a node that can be used in a shader anyway, it just makes the Image inaccessible via the UI (if you care about that):

enter image description here

Anyway, this is NOT a node for a shader graph, it's JUST a texture. Therefore, even with the use_nodes=True, the "color" output gives you simply the RGB values of the color, and you can't connect that to a node socket (and that's why you get the error message).

>>> print (texnodes["Output"].color)
<Color (r=0.6080, g=0.6080, b=0.6080)>

The second way is actually creating a node for a shader network. It does have outputs that are socket connections, so that's why you can connect it.

When you run this:

>>> mat = bpy.data.materials.new('mat')
>>> mat.use_nodes = True

Then in your Node Editor you can actually see it:

enter image description here

And you run this:

>>> texture=matnodes.new("ShaderNodeTexImage")

The node pops up:

enter image description here

...and in the console:

>>> print (texture.outputs.keys())
['Color', 'Alpha']

you can see that the outputs look like what you see in the node, and the type is a nice socket connection type instead of an RGB value:

>>> print (texture.outputs["Color"])
<bpy_struct, NodeSocketColor("Color")>

BUT, if you notice in our node, there actually is no image. THAT'S how we can tie this to the first method. So supposing we had created a texture via the first method added an image to it, then:

>>> print (firstway.image)
<bpy_struct, Image("01.PNG")>

...the .image property holds the image file from the texture. To use that in our texture node, you simply set the node .image equal to this one:

>>> texture.image = firstway.image

...and you can see in the UI that it's now set in the node:

enter image description here

The nice thing is that you can re-use the texture in other shaders or nodes if you want to.

I hope this helps to understand the difference. TL;DR is that a Texture NODE contains a Texture, but a Texture cannot be used as a node itself.

$\endgroup$
  • 2
    $\begingroup$ Great answer. Small thing, given you listed the keys, could use texture.outputs["Color"] , which IMHO makes it more readable than texture.outputs[0] $\endgroup$ – batFINGER Nov 30 '17 at 8:34
  • 2
    $\begingroup$ Doh! I totally meant to do that...good catch; I made that edit! $\endgroup$ – Dale Cieslak Dec 1 '17 at 4:23
1
$\begingroup$

The first one is just a texture, could be used for anything I guess, like brushes, displacement modifier, etc. The second is a texture node, specific to cycles materials. To do what your trying to achieve, your code would look something like this:

import os, bpy

bpy.context.scene.render.engine = 'CYCLES'

# new material
mat = bpy.data.materials.new('mat')
matnodes = mat.node_tree.nodes

#new texture
tex = matnodes.new('ShaderNodeTexImage')


# assign texture to material's displacement
disp = matnodes['Material Output'].inputs[2]
mat.node_tree.links.new(disp, tex.outputs[0])
$\endgroup$
1
$\begingroup$

The bpy.data.textures is used when the engine is set to Blender Render not Cycles Render. For Blender Render a material can have a number of textures assigned to it, these are all accumulated in bpy.data.textures. While not used very often, these textures can use nodes.

When using Cycles, the one material node tree is used, it can contain image texture nodes and these are not related to the above textures. There is no way to link the old textures to a cycles material.

To adjust your code to work, create an image texture node, then link that to the displacement of the material output.

import bpy

bpy.context.scene.render.engine = 'CYCLES'

# new material
mat = bpy.data.materials.new('mat')
mat.use_nodes = True
matnodes = mat.node_tree.nodes

#new texture
tex = matnodes.new('ShaderNodeTexImage')
tex.image = bpy.data.images['MyTexture']

# connect texture to material's displacement
disp = matnodes['Material Output'].inputs['Displacement']
mat.node_tree.links.new(disp, tex.outputs['Color'])
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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