nortikin / sverchok

Sverchok
http://nortikin.github.io/sverchok/
GNU General Public License v3.0
2.25k stars 234 forks source link

implementing groups #785

Closed zeffii closed 8 years ago

zeffii commented 8 years ago

For my own sanity i felt it was necessary to have a closer look at the mindfuck that is CustomGroup Nodes :) . I was especially interested in reusing as much of the PyNode api as possible, but I think @ly29 already concluded that also this requires us to pretty much roll our own.

I started a branch https://github.com/nortikin/sverchok/tree/group_experiment which hopes to get away from the use of the Frame and instead be more like the ShaderNodeTree with a back button and an overlay to show the parent NodeTree.


This is merely an exploration i'm not married to any of this code

here a list of things that need to be taken care of (feel free to interject / edit) in no order of importance.


Design Reference

lukas_t made his own node group for object_nodes: https://developer.blender.org/diffusion/B/browse/object_nodes/release/scripts/nodes/group_nodes.py

zeffii commented 8 years ago

The blender way isn't always the best. Am tempted to get as much of your node_group socket wrappers understood and implemented.. on the other hand I would like to try my own version of the panel (even if it doesn't make it into a final/accepted version).

ly29 commented 8 years ago

As my design teachers used to say, don't be afraid to try! And don't be afraid to change and do things over again.

About the blender way, as much as I like blender sometimes it does drive me mad. But that is true for most advanced software...

ly29 commented 8 years ago

The whole issue of having the properties in the nodes comes back to bite us. Of course we could make dynamic nodes with type and copying the custom properties. Or something.

The other option is to expose the relevant properties. But that includes creating an extra node and managing all of that... The best long term option given current blender limits is making a hierarchy of sockets like the standard blender ones...

ly29 commented 8 years ago

Been playing around and some observations of issues, not necessarily in order of importance or directly urgent.

zeffii commented 8 years ago

good points.

ly29 commented 8 years ago

Compiling would slow us down a lot, right now the code in many cases assumes that the process function lives in blender node group etc, now reason to change that right now. for redux it could be a possibility.

I don't agree that copying the node should make the node group unique. Every node group simple has a series of parent nodes and every node group node has one node group.

Something like this in node tree (this case assume a specific node group type for storing the node trees).

    @property
    def parent_nodes(self):
        parents = []
        for ng in filter(lambda ng: ng.bl_idname == "SverchCustomTreeType", bpy.data.node_groups):
            for node in ng.nodes:
                if node.bl_idname == "SvGroupNode" and node.group_name == self.name:
                    parents.append(node)
        return parents

Then we update them all, otherwise the functions/monads aren't reusable

(oops forgot filter syntax)

zeffii commented 8 years ago

I don't agree that copying the node should make the node group unique.

I didn't mean to suggest that, copying ideally should offer to ability to

  1. make unique
  2. make duplicate ( edit one, edit all )
  3. make unique from duplicate
ly29 commented 8 years ago

Okay, good, my mistake. 😄

Anyway, a decision that should be taken soon is if we hide away the node groups in an other node tree type that makes them invisible in the selection.

Also, just for fun, you can edit the old SvGroup implementation if you enter the right node group name in Group Node and press edit... skarmavbild 2016-08-01 kl 10 29 25

ly29 commented 8 years ago

There is a lot of duplication going around, the user interface and your code mostly looks cleaner than mine old, but if we modify my old previously created node groups will still work.

There are some assumptions about editing in my old code and it does not seem to work very well.

zeffii commented 8 years ago

I just read Lukas_t's Interface code, and I think I understand it now after implementing my own.. neat to see much of the same things happening in both implementations.. I kind of like my approach, but might give his approach a run anyway..

ly29 commented 8 years ago

If nothing else you learn something about a different style. I think I going to give my old group creation from selection code a test

ly29 commented 8 years ago

The sane approach, in retrospect, would be to have a standard hierarchy of sockets with the different default values, even if we would need more than we cycles. having the properties in the node wasn't clever, but at the time we didn't know this problem.

now if we want to have the proper props display we need to replicate all of this, or create generic display mechanism like script did.

for me the most sane approach would be to create node representing the node group interface somwwhere and then expose the property when needed in the node interface.

therefore I conclude that sockets are a mess the way they are done Sverchok today.

zeffii commented 8 years ago

the bpy.props.IntProperty or FloatProperty would come from the Socket, I think only the StringsSocket has any real usefulness as a on-ui-slider.

ly29 commented 8 years ago

Yeah, but it would be nice with different types, locked between say 0 to 1 as the original nodes do.

Anyway, defer that until later, I have some solution for that

zeffii commented 8 years ago

i'm just going to call node_groups/sub_groups "monads", you'll know what i'm talking about.

The weird/klunky thing (to me) about using a UiList to display + edit the props is that each monad must necessarily have a unique pair (tied to the group, but not necessarily to groupname) registered at monad instantiation.

I have questions, well one main thing..

zeffii commented 8 years ago

My apprehension to going with UILists grows either due to laziness or knowing that in order for UILists to work we might as well copy most of Lukas' object_node node group implementation.

I'm not sure there's a net gain either way, but probably implementing UILists for the Interface should happen in a separate branch, to keep that implementation closer to the example.

zeffii commented 8 years ago

dynamically create properties: http://blender.stackexchange.com/a/1786/47 have not tried yet, but seems interesting...

ly29 commented 8 years ago

To get properties to show without having to invent a whole new socket system there are some options I can think of, listed in how "clean" they make me feel.

Somehow the second option kind of appeals me right now.

Some notes:

For option one, it is trivial to collect the needed info for the properties for the dynamic class creation.

>>> ng.nodes['Sphere'].inputs['Radius'].prop_name
'rad_'

>>> ng.nodes['Sphere'].rna_type.rad_[0]
<built-in function FloatProperty>

>>> ng.nodes['Sphere'].rna_type.rad_[1]
{'options': {'ANIMATABLE'}, 'description': 'Radius', 'update': <function updateNode at 0x116560268>, 'name': 'Radius', 'attr': 'rad_', 'default': 1.0}
# collect properties
node_properties = default_properties.copy()
for socket in inputs:
     other = get_other_socket(socket)
     if other.prop_name:
            func, options = getattr(other.node.rna_type, other.prop_name) 
            properties[other.prop_name] = (func, options)
group_type = type("SvGroupNode{}".format{group_name}, (Node, SvGroupBase), properties)
#register etc

Ref https://github.com/Sverchok/SverchokRedux/blob/master/core/factory.py

For two the question is where to store them, the answer would be, I guess, in the node monad layout when we save it or another node tree type which shadows the node groups by name and contains relevant properties. this is backward and complicated, but pure brutish hack of it seems kind of fun. This also has the advantage that more complicated things might work better.

The script node method of having a number of fixed properties and using them feel ok there but here it feels out of place since the properties won't be well behaved.

ly29 commented 8 years ago

Something like that could work, of course it is also not so pretty. But less bad than some other options...

zeffii commented 8 years ago

I really like the idea that the property used for the slider / bool / whatever is part of the SocketType.

ly29 commented 8 years ago

Re where value belongs: It really should be, it makes it clearer where the data comes from and removes the need for bad code. All I have lot more appreciation for the standard hierarchy of node sockets now. I really wish changing to such a format would be none breaking but it isn't. Too much cruft going on.

Also the amount of work is because bad decisions earlier, due lack of understanding of the tradeoffs, if would say we don't care about backwards compatibility it becomes so much easier.

zeffii commented 8 years ago

i think we could tunnel the sliderproperty from the node the socket came from?

parent_node_tree:       parent node.inputs (3) (slider from 1 if not linked)     
--------------------
monad_node_tree:        ng_outputs  (2) <- some_node_input.FloatProperty (1)
ly29 commented 8 years ago

unless we have two instance of a monad node, then that would lead to adjusting one of them changes the other.

ly29 commented 8 years ago

Imagine something like this for socket.draw()

def draw(self, context, layout, node, text):
    data = bpy.node_groups[node.monad].nodes[self.prop_ref]
    layout.prop(data, self.prop_name)

then changing the exposed property changes all monad nodes interfacing the monad. Get around this lead to option two above, the exact specifics that can be argued. it is fairly straightforward to implement but the lead to lots of things lay around.

zeffii commented 8 years ago

OK.. I'll try with the Frame thing then, but it's not clear to me how that would make monad "instances" easier.

zeffii commented 8 years ago

first I want to see if this is a viable way to tunnel a property.. ;)

zeffii commented 8 years ago

@ly29 do you know if there's a way to do a prop_search on a subset of a collection? like

    layout.prop_search(self, "group_name", bpy.data.node_groups, filter('attr', 'sub_group'))
ly29 commented 8 years ago

No, not directly. I guess you can use an enum with a function making the filtering in worst case

zeffii commented 8 years ago

well, it's displaying the 'tunneled' socket now, but no data is transferred when the parent node's sliders are moved. A problem for tomorrow. ..almost tempted to revert because this just feel ridiculous and brittle (even if the code isn't too ugly.. it's still icky...)

zeffii commented 8 years ago

image

ly29 commented 8 years ago

It does feel wrong, doesn't? For the update to work some changes has to be made, which would make it even more brittle...

zeffii commented 8 years ago

am going to revert...

ly29 commented 8 years ago

Yeah looking at it now it feels like the sane decision.

zeffii commented 8 years ago

http://stackoverflow.com/a/12049323/1243487

git reset c640aeb >> will say : Unstaged changes after reset: git add -A >> addsthem git reset --hard >> bulldoze git commit -am 'reset to c640aeb' git push origin group_experiment -f

zeffii commented 8 years ago
bpy.ops.node.clipboard_copy()
bpy.ops.node.clipboard_paste()

wtf :)

ly29 commented 8 years ago

If it works great, otherwise my old operator to create a group from selection should work with minimal changes (.bl_idname for nodes etc)

ly29 commented 8 years ago

https://github.com/nortikin/sverchok/blob/master/utils/group_tools.py

zeffii commented 8 years ago

Yeah I did dig through that first before starting this :) I felt there had to be some way to reuse code that's already tried and tested by millions.

class SvMonadCreateFromSelected(bpy.types.Operator):

    bl_idname = "node.sv_monad_from_selected"
    bl_label = "Create monad from selected nodes (sub graph)"

    group_name = StringProperty(default="Monad")

    def execute(self, context):

        ng = context.space_data.edit_tree
        nodes = [n for n in ng.nodes if n.select]

        if not nodes:
            self.report({"CANCELLED"}, "No nodes selected")
            return {'CANCELLED'}

        parent_node = ng.nodes.new('SvGroupNodeExp')
        parent_node.select = False
        parent_tree = parent_node.id_data
        parent_node.location = average_of_selected(nodes)
        bpy.ops.node.clipboard_copy()

        monad = group_make(parent_node, self.group_name)
        bpy.ops.node.sv_switch_layout(layout_name=monad.name)

        # by switching, space_data is now different
        path = context.space_data.path
        path.clear()
        path.append(parent_tree) # below the green opacity layer
        path.append(monad)  # top level

        bpy.ops.node.clipboard_paste()

        # remove nodes from parent_tree
        for n in reversed(nodes):
            parent_tree.nodes.remove(n)

        return {'FINISHED'}

but this hasn't been heavily tested by me yet.

zeffii commented 8 years ago

it might be sane to have a filter list to unselect certain kinds of nodes if they are in the user's selection... but that's the easy bit.

ly29 commented 8 years ago

that code does even spend that much time of copying, just collecting links and such things is quite enough of a chore.

zeffii commented 8 years ago

sv_monad_tools.py holds the Operators and the utils now..

image

It probably makes sense to place the monad input and output at the extremes/bounding box of the copied nodes, I find myself moving them all the time..

now monad IO nodes are placed at (min_y + max_y) / 2 and min_x and max_x respectively.

zeffii commented 8 years ago

I realize now the one important difference between my simpler make_monad_from_selected vs your original counterpart is that you auto connect the head and tail of the selected nodes to the monad IO nodes, and the parent node.. This means my implementation is only partially successful at the moment

#psuedo
links_to_reestablish = [for selected nodes track which nodes have links with un-selected nodes]
for link in links_to_reestablish:
    # (I think this will automatically propagate like the manual connection)
    if link is from_socket:
         link monad_input_exp 
    if link is to_socket
         link monad_output_exp

or

import bpy

ng = bpy.data.node_groups['NodeTree']
nodes = [n for n in ng.nodes if n.select]

relinks = dict(inputs=[], outputs=[])
for n in nodes:
    for idx, s in enumerate(n.inputs):
        if s.is_linked:
            link = s.links[0]
            if not link.from_node in nodes:
                relinks['inputs'].append(dict(socket_index=idx, linked_node=link.from_node))

    for idx, s in enumerate(n.outputs):
        if s.is_linked:
            link = s.links[0]
            if not link.to_node in nodes:
                relinks['outputs'].append(dict(socket_index=idx, linked_node=link.to_node))

print(relinks)

current implementation of mine is a bit broken

zeffii commented 8 years ago

also..

get_relinks() and relink() are a little broken.. but committed anyway.

zeffii commented 8 years ago

let's add more naming convention to make it easier to talk about.

after monad creation

zeffii commented 8 years ago

I may copy liberally from group_tools.py/SvNodeGroupCreator :)

ly29 commented 8 years ago

Feel free to do it. Also an important point is that one socket may have many to sockets. Or also why socket.links is an array. (well it is a property function actually but that is a different matter)

zeffii commented 8 years ago

ah yes, outputs[index].links[:] nodes can output to various other nodes from one socket, but can only receive (or we have limited it to do so) into inputs[index].links[0].

..this way take a while to figure out a nice way to do it, no ETA.

ly29 commented 8 years ago

The ugly way is in SvNodeGroup

zeffii commented 8 years ago

I'm under no illusion that i'll come up with anything nicer than SvNodeGroup, but i still want to have a think about it

zeffii commented 8 years ago

Possible the code I just pushed will suffice as 'make monad from selection' - else i'll need to really just rip your code.. because I have little patience for this.