1
$\begingroup$

I am currently writing a script that creates nodes in the Node Editor. One of the core issues I am having is understanding how to convert my script into an add-on. In my previous question I got some good, although vague responses on how to use context properly.

What is the context? Is it different from getting the active material via bpy.context.active_object.active_material.name? Should I be using a poll method?

$\endgroup$
4
  • $\begingroup$ In my opinion samblers answer is detailed, precise and answers your question. Anyway I don't get it - Is this question about how to convert your script into an addon or is your question about what is bpy.context? Notice that your question is very vague and even the title does not really match the body. Also can you please post all relevant code here? $\endgroup$
    – brockmann
    Commented Sep 1, 2017 at 10:13
  • $\begingroup$ @brockmann I do see that my question is not very good ettiquette, though the answer provided does answer my question. Should I change the body in order to fit the answer, delete the thread or just leave it as is? $\endgroup$
    – Rug
    Commented Sep 1, 2017 at 12:22
  • 1
    $\begingroup$ Too many questions in one basically... I suggest have a look at operator_node.py template that's shipped with blender to get your script converted into an addon (Templates > Python > Operator Node) and store your properties in one PropertyGroup, that's good practice and well organized (my answer to your questions). $\endgroup$
    – brockmann
    Commented Sep 1, 2017 at 14:19
  • $\begingroup$ Right, sorry for that. Is it okay to leave this thread or should I change it? I did see that template while researching, Stephanè's answer really gave me the insight i needed, i think i needed a different example although im sure seeing it as my code helped :-) $\endgroup$
    – Rug
    Commented Sep 1, 2017 at 15:55

1 Answer 1

2
$\begingroup$

I understand your problem.

When you load your script as an addon, it reads the scripts when loading blender. Thus the scripts register the the bpy.context.active_object = the active object when the addon is loader. Thus it does not point to the actual bpy.context.active_object when you run the operator.

For instance your makeLink(var1, var2, var3, var4): reference the link object which is the link of the node_three of the material of the active object when the addons was enable. Which may be not the one you want to modify. I hope your understand your mistake.

To solve your problem, you need to modify every function you made to be context sensitive. For instance, your main function should take as argument the context variable of the operator. (BTW the main class should'nt be a class but a functon, i.e. def main(...) instead of class main() ).

Then in the main function you should now declare the the mat_name, the node_tree, the links,... (everything that you declare at the beginning of your script)

def main(context):
    mat_name = context.active_object.active_material.name
    mat = (bpy.data.materials.get(mat_name) or bpy.data.materials.new(mat_name))
    mat.use_nodes = True

    nt = mat.node_tree
    nodes = nt.nodes
    links = nt.links

    while (nodes): nodes.remove(nodes[0])

    createNodes(nodes)
    ...

But then you also need to modify every function you have since the need to point to the correct variable. For instance the make link should be:

def makeLink(links, var1, var2, var3, var4):
    """
    Creates a link between a Material Node's output,
    and another Material Node's input.
    args follow the order: Output Node Name, Socket Name, Input Node Name, Socket Name
    """

    links.new(var1.inputs[var2], var3.outputs[var4])

def createNodes(nodes):
    link = nodes.link;
    output = nodes.new("ShaderNodeOutputMaterial")
    principled = nodes.new ("ShaderNodeBsdfPrincipled")
    mapping = nodes.new("ShaderNodeMapping")
    texCoord = nodes.new("ShaderNodeTexCoord")
    colorMap = nodes.new("ShaderNodeTexImage")
    normalMap = nodes.new("ShaderNodeTexImage")
    roughMap = nodes.new("ShaderNodeTexImage")#.color_space = 'NONE'

    mainTexCoord = [ node for node in mat.node_tree.nodes if node.bl_idname=="ShaderNodeTexCoord"]
    # python list comprehension filters the list of nodes down to the ones that match the right bl_idname
    texCoordinateNode = mainTexCoord[0]

    #Image Texture Node Image
    colorMap.image = bpy.data.images.load(image_path)
    normalMap.image = bpy.data.images.load(image_path2)
    roughMap.image = bpy.data.images.load(image_path2)

    makeLink(output, 'Surface', principled, 'BSDF')
    makeLink(mapping, 'Vector', texCoordinateNode, 'UV')

    makeLink(principled, 'Base Color', colorMap, 'Color')
    makeLink(colorMap, 'Vector', mapping, 'Vector')

    makeLink(principled, 'Normal', normalMap, 'Color')
    makeLink(normalMap, 'Vector', mapping, 'Vector')

    makeLink(principled, "Metallic", roughMap, 'Color')
    makeLink(roughMap, 'Vector', mapping, 'Vector')

Then the operator should be like this:

class QuickLayout(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "myops.quick_layout"
    bl_label = "Quick PBR Layout"
    def execute(self, context):
        main(context)
        self.report({'INFO'}, "Layout Complete!")       
        return {'FINISHED'}

I hope you understood where the problem was.

Cheers

$\endgroup$
2
  • 1
    $\begingroup$ It's good practice to pass the operator reference as well. $\endgroup$
    – brockmann
    Commented Sep 1, 2017 at 10:20
  • 1
    $\begingroup$ Conversely, for instance if your operator works on a material, define main(mat) , ie it is passed a material. Use the operator, which has context to determine what the material is to pass (eg main(context.object.active_material)). This makes it easier to reuse "main" without the need to be in any context. $\endgroup$
    – batFINGER
    Commented Sep 1, 2017 at 13:54

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .