9
$\begingroup$

I'm developing an add-on that uses a custom node tree. I want to be able to set a link between nodes as being valid or not:

enter image description here

NodeLink has the property is_valid which controls also how it's drawn (as pictured above). I want to use this value, but something keeps changing it to True any time the node tree updates.

Node's have the insert_link(link) method which you can override. It seems to be the first thing called when a new link is made. This would be an ideal time to validate the link and set the property.

After insert_link(link) returns, the NodeTree.update() method is called on the parent NodeTree which you can also override. What ever value you assigned to NodeLink.is_valid is still the same at this point.

I don't think any other functions are called inside the custom tree after that? But once the UI updates the NodeLink.is_valid value has returned to being True regardless of what it was set to.

The same thing happens if you change the value from the python console. It will draw in the red color and be False if you check the value. But as soon as any new link or update is made to the tree, it goes back to being True again.

Something else must be determining if a link is valid each time it updates? But I cannot find anything in the documentation that says what this is or how to interact with it??? But, it seems like this should be something that can be done given there is a built-in value for it and the UI checks that value when drawing it...

So how do you validate links in a custom node tree?

# First function called in custom tree when a link is made
def Node.insert_link(link):
    # Validate the link
    link.is_valid = False

# Called after insert_link is finished
def NodeTree.update():
    # Link valid value is still set from above
    link.is_valid
    False

# After update link valid value has been set to True
link.is_valid
True

I'm currently working around it with a custom bool property, which essentially just duplicates the existing property. Which works fine for preventing problems with invalid connections, but the state of the link isn't displayed in the UI.

I suppose an acceptable solution would be a way of making the link draw in red using my custom property. It just seems really silly when the property already exists and does the thing I want, but something keeps on changing it on me.

ntree_validate_links(bNodeTree *ntree) in the C code is the thing that causes it to change and is called as essentially the last thing when updating a node tree. It does three things, first is sets the flag to valid, second it checks for a cyclic link and will set the flag to invalid if found. Lastly it checks for ntree->typeinfo->validate_link which I'm assuming is a function pointer and calls that to set the flag.

I'm not seeing anything in the RNA node stuff to interact with it though.

$\endgroup$
4
  • $\begingroup$ I haven't tested this myself hence just a comment and not an answer, but it seems in insert_link you would use node_tree.requested_links.add() to mark links you would like to add which you can then actually add to the node tree in update. Assuming that you're using self.links.new() in update that would be a different link object than in insert_link(). That is the idea that I've got from a quick glance over existing code, e.g. github.com/LuxCoreRender/BlendLuxCore/blob/refactor/… $\endgroup$ Commented Sep 26, 2019 at 15:54
  • $\begingroup$ @rjg I could have it create/not create the link that way. But the standard behaviour is for the link to be created even if it's invalid, but have it marked as such. That causes it to be drawn red in the UI which is what I want to emulate. I don't really like the idea of the user drawing a link and having it not connect. $\endgroup$
    – hekete
    Commented Sep 27, 2019 at 4:14
  • $\begingroup$ The idea that I had was to still use is_valid, the choice of the user shouldn't be ignored. If I'm not mistaken you should be able to follow the described approach and set is_valid on the link created from the requested_link in update. I'll look into it if I got a bit of time this weekend. $\endgroup$ Commented Sep 27, 2019 at 5:17
  • $\begingroup$ @rjg I think the problem is that is_valid will still get reset on future updates, but I will try it out and see if that's the case. $\endgroup$
    – hekete
    Commented Sep 27, 2019 at 5:26

2 Answers 2

3
$\begingroup$

Sorry for the bump, but I have found a way to set is_valid and have it stick. You just have to call it from a timer, which starts right after update is called on the node tree.

class CustomNodeTree(NodeTree):
    '''Custom Node Tree'''
    bl_label = 'Custom Nodes'
    bl_icon = 'MONKEY'

    def update(self):
        '''Called when node graph is changed'''
        bpy.app.timers.register(self.mark_invalid_links)

    def mark_invalid_links(self):
        '''Mark invalid links, must be called from a timer'''
        for link in self.links:
            if type(link.from_socket) != type(link.to_socket):
                link.is_valid = False

The actual check for validity is very simple in this example; feel free to substitute your own.

$\endgroup$
2
  • 1
    $\begingroup$ If I understand this correctly, checking if the link is valid on update won't work because properties are not updated yet, so using a timer postpones the actual calculations to the next frame. Although, it's not really clear to me exactly when a registered timer function will be called if the first_interval parameter is 0 (default) $\endgroup$
    – Gorgious
    Commented Jun 22, 2021 at 6:28
  • $\begingroup$ It gets called as soon as Blender's scheduler for timers decides to call it, which is usually pretty soon. $\endgroup$
    – bonjorno7
    Commented Aug 2, 2021 at 14:38
3
+50
$\begingroup$

I'm not sure this is what you're looking for.

In Sverchok we add a property to the socket class, called 'other', which returns the socket instance of the other socket, and even iterates back through "ReRoute" nodes.

@property
def other(self):
    return get_other_socket(self)

This lets us do a simple test when appropriate.

if not socket.is_output:
    if not (socket.bl_idname == socket.other.bl_idname):
        ... # handle the mismatch in socket types

You figure the logic out for input sockets vs output sockets. Some node systems (Sverchok included) don't allow a node to accept multiple links on an input socket -- By design.

modified from sverchok/data_structure.py

def get_other_socket(socket):
    """
    Get next real upstream socket.
    Will return None if there isn't a another socket connect
    so no need to check socket.links
    """
    if not socket.is_linked:
        return None
    if not socket.is_output:
        other = socket.links[0].from_socket
    else:
        other = socket.links[0].to_socket

    if other.node.bl_idname == 'NodeReroute':
        if not socket.is_output:
            return get_other_socket(other.node.inputs[0])
        else:
            return get_other_socket(other.node.outputs[0])
    else:
        return other
$\endgroup$
5
  • $\begingroup$ My current workaround is a custom property on the sockets. Input sockets validate links made to them and set the property. So under the hood it works (though I'm basically just duplicating the existing property which seems dumb). But the state of link isn't reflected in the UI, which is what I'm mostly after at this point. $\endgroup$
    – hekete
    Commented Sep 27, 2019 at 15:36
  • 1
    $\begingroup$ @hekete i might be wrong here, but maybe is_valid is used to identify cyclic links? -- in which case it would make sense that the property is continuously reset. May be useful to see this: github.com/sobotka/blender/blob/… $\endgroup$
    – zeffii
    Commented Sep 27, 2019 at 16:23
  • 1
    $\begingroup$ It does identify cyclic links and does so automatically even in a custom tree. But if you look at a cycles node tree, connecting say a BDSF socket to a Value socket will also set the flag. Some process seems to validate links after a NodeTree.update() but maybe it's not possible for a custom tree to interact with it... $\endgroup$
    – hekete
    Commented Sep 28, 2019 at 5:19
  • $\begingroup$ @hekete the is_valid from Python maps to flag in the RNA code and is indeed used to determine how the link is drawn. This can be seen in node_draw_link() in drawnode.c. You can find the code where this is used by searching for ->flag or by searching for NODE_LINKFLAG_HILITE, NODE_LINK_VALID and NODE_LINK_TEST which are used to indicate the state that the link is in. $\endgroup$ Commented Sep 28, 2019 at 12:46
  • $\begingroup$ I'm just not seeing any way to interact with it from python :( $\endgroup$
    – hekete
    Commented Sep 29, 2019 at 8:52

You must log in to answer this question.

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