6
$\begingroup$

I want to create a custom pynode that reads the object properties (position, rotation, scale ...) of the current object on what the material with the custom pynode is applied to and use these values for the shader itself. E.g. If the position of the object changes, then this should affect the color of the shader.

I've used this base setup from Atom and tried to draw the object position as float value into the node:

#NOTE: Run this code first then use SHIFT-A, below, to add Custom Float node type.
import bpy
from bpy.types import NodeTree, Node, NodeSocket

# Implementation of custom nodes from Python
# Derived from the NodeTree base type, similar to Menu, Operator, Panel, etc.
class MyCustomTree(NodeTree):
    bl_idname = 'CustomTreeType'
    bl_label = 'Custom Node Tree'

# Defines a poll function to enable filtering for various node tree types.
class MyCustomTreeNode :
    @classmethod
    def poll(cls, ntree):
        b = False
        # Make your node appear in different node trees by adding their bl_idname type here.
        if ntree.bl_idname == 'ShaderNodeTree': b = True
        return b

# Derived from the Node base type.
class MyCustomNode(Node, MyCustomTreeNode):
    '''A custom node'''
    bl_idname = 'CustomNodeType'
    bl_label = 'Custom Float'
    bl_icon = 'INFO'

    def update_value(self, context):
        self.outputs["Float"].default_value = self.some_value
        self.outputs["Color"].default_value = self.some_color_value
        self.update ()

    # float value
    some_value = bpy.props.FloatProperty(default=0.0, update = update_value)

    # color value
    some_color_value = bpy.props.FloatVectorProperty(
        name = "Color",
        subtype = "COLOR",
        size = 4,
        min = 0.0,
        max = 1.0,
        default = (0.75,0.0,0.8,1.0)
        )

    def init(self, context):
        self.outputs.new('NodeSocketFloat', "Float")
        self.outputs.new('NodeSocketColor', 'Color')
        self.outputs["Float"].default_value = self.some_value
        self.outputs["Color"].default_value = self.some_color_value


    def update(self):
        #Review linked outputs.
        try:
            out = self.outputs["Float"]
            out = self.outputs["Color"]
            print (out_clr)
            can_continue = True
        except:
            can_continue = False


        if can_continue:
            if out.is_linked:
                # I am an ouput node that is linked, try to update my link.
                for o in out.links:
                    if o.is_valid:
                        o.to_socket.node.inputs[o.to_socket.name].default_value = self.outputs["Float"].some_value   #self.some_value
                for o in out_clr.links:
                        o.to_socket.node.inputs[o.to_socket.name].default_value = self.outputs["Color"].some_color_value   #self.some_value


    # Additional buttons displayed on the node.
    def draw_buttons(self, context, layout):
        layout.prop(self, "some_value",text = '')
        layout.prop(self, "some_color_value",text = 'Color')

    # Optional: custom label
    # Explicit user label overrides this, but here we can define a label dynamically.
    def draw_label(self):
        return "My Float"

### Node Categories ###
import nodeitems_utils
from nodeitems_utils import NodeCategory, NodeItem

# our own base class with an appropriate poll function,
# so the categories only show up in our target tree type
class MyNodeCategory(NodeCategory):
    @classmethod
    def poll(cls, context):
        b = False
        # Make your node appear in different node trees by adding their bl_idname type here.
        if context.space_data.tree_type == 'ShaderNodeTree': b = True
        return b

# all categories in a list
node_categories = [
    # identifier, label, items list
    MyNodeCategory("SOMENODES", "Custom Float", items=[
        NodeItem("CustomNodeType"),
        ]),
    ]

def register():
    bpy.utils.register_class(MyCustomNode)
    nodeitems_utils.register_node_categories("CUSTOM_NODES", node_categories)

def unregister():
    nodeitems_utils.unregister_node_categories("CUSTOM_NODES")
    bpy.utils.unregister_class(MyCustomNode)

def pre_frame_change(scene):
    if scene.render.engine == 'CYCLES':
        # Scan materials to see if I have a custom node within any of the trees.
        for obj in bpy.data.objects:
            if obj.type == 'MESH':          
                for m in obj.data.materials:
                    if m.node_tree != None:
                        for n in m.node_tree.nodes:
                            if n.bl_idname == 'CustomNodeType':
                                print ("THIS IS THE OBJECT" + obj.name)
                                print(n.bl_idname)
                                # One of our custom nodes, let's update it.
                                # When we set the value that will trigger an update inside the node.
                                # Even if we change it to the same value it was.
                                v = n.some_color_value
                                print (v)
                                n.some_color_value = v
                                print (n)


if __name__ == "__main__":
    register()
    bpy.app.handlers.frame_change_pre.append(pre_frame_change)

But unfortunately I don't really understand the concept of pynodes to accessing the properties of the scene object and update it for every frame if the values are animated.

How can I get object properties to use it for the shader itself?

$\endgroup$
  • $\begingroup$ I dont think I understand this question, do you want to get object reference if you know the path to a node? Or does this help: blender.stackexchange.com/questions/23436/… ? $\endgroup$ – Jaroslav Jerryno Novotny Jan 19 '15 at 15:43
  • $\begingroup$ Thanks for your response @Jerryno. You are right - the question is bad. I will try to edit it. I want to write a py node: blenderartists.org/forum/… With this I want to read object properties (position, scale...) of the object on what the material with the custom py node is applied to use these values for the shader itself. Same situation like your answered question here: blender.stackexchange.com/questions/23417/…. Hope that's more useful :) $\endgroup$ – p2or Jan 19 '15 at 16:02
  • $\begingroup$ I see now, ill look into it when I get home $\endgroup$ – Jaroslav Jerryno Novotny Jan 19 '15 at 16:34
  • 1
    $\begingroup$ You asked me to help you with that: I think what you are trying to do won't work for the same reasons which are mentioned in the BA thread you linked. For now pynodes aren't ment to work in cycles/compositor/.. They are visually there but won't do anything. I think the script by Atom only works because it is such a simple node that does in fact nothing. $\endgroup$ – Jacques Lucke May 6 '15 at 18:23
  • $\begingroup$ Mhh. Thanks @JacquesLucke, I've also realized that it's just not supported to evaluate cycles nodes (read the output values) from python. I will go for your animation nodes and overwrite cycles shader values by using Cycles Output Node. Feel free to write an answer, I'll accept it. $\endgroup$ – p2or May 8 '15 at 8:43
2
$\begingroup$

I've come to the conclusion that this is not possible using Pynodes - mostly due to the 'update' of the nodes not being triggered at suitable times in the processing of the materials for multiple objects (so they'd all evaluate with the same properties. However, if this could be triggered at the correct moment (ie, when a 'ray' strikes the material on a particular object) this would mean that Python code would be executed for every ray calculated (as it would need to re-evaluate to be able to determine the 'current' object's properties) which would be extremely inefficient.

An alternative is to capture the properties you desire from the objects (such as position, rotation, scale) and feed this into the material in a form that can be efficiently accessed by Cycles - eg, by encoding the properties in the pixels of a Image Texture.

See https://blender.stackexchange.com/a/75505/29586 for an example of this to capture the Rotation of all objects for all frames. This could be extended to capture Location and Scale (and, indeed, any other numeric property) to additional images which could then be referenced within the Material.

$\endgroup$
  • 1
    $\begingroup$ Thanks for your answer Rich! However, I think it's possible to implement that, either with handlers (probably a bad idea) or with some kind of low-fi ecosystem version of animation nodes or sverchok (unfortunately I never had the time to check how both systems handling the values). Even if it's working at the end, it's a lot of work and I'm not sure whether the effort is worth it. Also I think any C/C++ built-in mechanism to access and update all the scene data in whatever node tree is long overdue (IMO), hopefully a developer takes care of it in the near future... Anyway, thanks again. $\endgroup$ – p2or Mar 13 '17 at 19:07

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.