Sverchok / SverchokRedux

A reimplementation of Sverchok
GNU General Public License v3.0
4 stars 2 forks source link

Node Declaration #20

Open ly29 opened 8 years ago

ly29 commented 8 years ago

How to make nodes simple and clean to write.

ly29 commented 8 years ago

The first idea is to enable setting arguments via decorater like follows.  


@node_func(label="Input Int")
def int_in() -> [("Value", ValueInteger)]:
    pass

Instead of

def int_in() -> [("Value", ValueInteger)]:
    pass

int_in.label = "Input Int Value"

In essence doing something similar to:

def annotate(func, **values):
    for key, value in values.items():
        setattr(func, key, value)

def add(a,b):
    return a+b

annotate(add, label="Add")
add.label
> "Add"
ly29 commented 8 years ago

After eating lunch and messing with decorators for a while I came up with this.

def node_func(**values):
    def real_node_func(func):
        def annotate(func):
            for key, value in values.items():
                setattr(func, key, value)
            return func
        return annotate(func)
    return real_node_func

>>> @node_func(key="Beta", label="Blue")
... def test(a: int =1, b: float = 1.0) -> float:
...     return a+b

>>> test.label
'Blue'

>>> test.__annotations__
{'b': <class 'float'>, 'return': <class 'float'>, 'a': <class 'int'>}
ly29 commented 8 years ago

Should @node_func be required or optional? Required, better than the SvRxFunc = [add, sub] syntax, more direct.

ly29 commented 8 years ago

Current version as below.

def node_func(**values):
    def real_node_func(func):
        def annotate(func):
            for key, value in values.items():
                setattr(func, key, value)
            return func
        annotate(func)
        register_node(func)
        return func
    return real_node_fun

Note that this requires () like this even when not setting any arguments, sure it could be solved.

@node_func()
def add(x: Number = 0.0, y: Number = 1.0) -> [("res", Number)]:
    return x + y
ly29 commented 8 years ago

It is solved, just required some thinking...

def node_func(*args, **values):
    def real_node_func(func):
        def annotate(func):
            for key, value in values.items():
                setattr(func, key, value)
            return func
        annotate(func)
        register_node(func)
        return func
    if args and callable(args[0]):
        return real_node_func(args[0])
    else:
        return real_node_fun

Usage like


# where you can pass set optional parameters on the function object.

@node_func(label="Input Int")
def int_in() -> [("Value", ValueInteger)]:
    pass

# or just register it and leave everything as default.

@node_func
def add(x: Number = 0.0, y: Number = 1.0) -> [("res", Number)]:
    return x + y
ly29 commented 8 years ago

Maybe a more sane name would be register_node

ly29 commented 8 years ago

@zeffii Comments?

zeffii commented 8 years ago

yes let me read.

zeffii commented 8 years ago

quick first comment, the key=beta, OOh i like that.. does that imply the menu/indexing of nodes and their locations will be automated into the node definition? :)

ly29 commented 8 years ago

I meant to imply that such a behavior. Where the default will be depending on the module the node functions is placed in. But overridable with category="Alpaha" for example.

zeffii commented 8 years ago

A @register_node that takes care of the SvRxFunc = [add, sub] , looks altogether more cohesive. I'm still trying to mentally compile the code. may take a while.

ly29 commented 8 years ago

Remember:


@decorate
def func(a,b):
    return something

# is the same as
def func(a, b):
    return something
func = decorate(func)

# and

@decorate(c)
def func(a, b)
    return something

func = decorate(c)(func)
zeffii commented 8 years ago

what about docstrings for each node, how do you feel about that? could help simplify documentation by being able to view node docs in the text editor.. I have some ideas about this, maybe worth its own thread) -- I feel the current way is OK, but slightly work-intense.

zeffii commented 8 years ago

@ly29 I think when it comes time to start writing a larger set of nodes that we'll get a better feel for the good and (possibly?.. i'm not pessimistic) weaknesses of being this abstract. I welcome it, and look forward to writing a few new nodes this way.

I think it's good to take charge for 1 person here, by committee has its weaknesses.

ly29 commented 8 years ago

I think being this abstract is good thing. There will be some weaknesses I am sure some real and more perceived. I think after doing generics it will be ready on the node side if things. Then nodes will be easy but initially a bit limited.

Keep the docs with the nodes declaration is good a thing and I have thought a bit about it but nothing really concrete.

ly29 commented 8 years ago

If you have any good ideas please feel free to try them out. Of course the signatures will some give nice things to play with.

ly29 commented 8 years ago

Today I leaning towards node_func as a name, you are, after all, declaring that this function is node. The verbosity is appropriate.

zeffii commented 8 years ago

I can still follow this and while it is becoming more abstract it is no more abstract than Blender's own usage of layout.operator layout.props conventions..and register_module(__name__). Once the convention is shown to work it will be adopted.

go with node_func, as it will be doing more than merely registering the node.

zeffii commented 8 years ago

@ly29 what about nodes that dont have output?

oh i see.

@node_func
def debug_print(data: Generic) -> None:
    pprint.pprint(data)

nice :)

ly29 commented 8 years ago

Node defaults, due to python requirement that parameters with defaults has to come after parameters without default values basically all input need defaults.

>>> def func(a=1, b):
...     print(a,b)
  File "<blender_console>", line 1
SyntaxError: non-default argument follows default argument

Therefore two special default values are supported.

ly29 commented 8 years ago
@node_func(label="If")
def if_node(value: Integer = 1,
            If: Generic = Required,
            Else: Generic = Required,
            ) -> [("value", Generic)]:
    pass