Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi modal nodes #11

Closed
ly29 opened this issue Jan 30, 2017 · 21 comments
Closed

Multi modal nodes #11

ly29 opened this issue Jan 30, 2017 · 21 comments

Comments

@ly29
Copy link
Contributor

ly29 commented Jan 30, 2017

Many functions that we want to expose fall into logical groups that it doesn't make sense to break apart into multipile nodes even though the function are different and in many cases also the function signatures.

For example math nodes and different sort modes.

One, simple option is below, which I feel might be appriate for nodes that keep the same signature but change mode.

mathnode

from svrx.nodes.node_base import node_func
from svrx.typing import Number, Float, Int, EnumP


mode_list_tmp = [('ADD', 1), ('SUB', 2)]
mode_list = [(t, t, t, idx) for t, idx in mode_list_tmp]


@node_func(bl_idname='SvRxNodeNumberMath')
def math(x: Number = 0.0, y: Number = 0.0,
        mode: EnumP(items=mode_list, default='ADD') = 'ADD')-> Number:
    f = func_lookup[mode]
    return f(x, y)


def add(x, y):
    return x  + y

def sub(x, y):
    return x - y

func_lookup = {'ADD': add,
               'SUB': sub, }

However I don't feel this is good enough so will work a bit on this today.

@ly29
Copy link
Contributor Author

ly29 commented Jan 30, 2017

Something like below would make me happier if it worked.

from svrx.nodes.node_base import node_func

from svrx.typing import Number, Float, Int

@node_func(bl_idname='SvRxNodeMath2', multi_label='Math', id=0)
def add(x: Number = 0.0, y: Number = 0.0) -> Number:
    return x + y

@node_func(bl_idname='SvRxNodeMath2', id=1)
def negate(x: Number = 0.0) -> Number:
    return -x  

@ly29
Copy link
Contributor Author

ly29 commented Jan 30, 2017

So now this works quite well I think.

Notes:

  • id has to be set to a unique value
  • first node multi node must give the label for the node called multi_label

Then the node should adjust signature and sockets as needed.

math2

https://github.com/Sverchok/Sverchok/blob/master/nodes/number/math.py

@ly29
Copy link
Contributor Author

ly29 commented Jan 30, 2017

@zeffii
This seems resonable and simple enough, right?

@zeffii
Copy link
Contributor

zeffii commented Jan 30, 2017

sure! i'd like a separate similar node for Trig , that can output two sockets, and not have the trig functions mixed with the math ones.

@zeffii
Copy link
Contributor

zeffii commented Jan 30, 2017

a clear separation between Math Operators and Trig

@ly29
Copy link
Contributor Author

ly29 commented Jan 30, 2017

@zeffii
See, #12 and you can have any number of sockets you would like.

@zeffii
Copy link
Contributor

zeffii commented Jan 30, 2017

having a def draw_label that responds to the mode is one of sverchok's cooler features.

@ly29
Copy link
Contributor Author

ly29 commented Jan 30, 2017

You can attach one the class via the first node with draw_label=draw_label_func but even by default we could do something somewhat nice

@ly29
Copy link
Contributor Author

ly29 commented Jan 30, 2017

skarmklipp

def draw_label(self):
    name_or_value = [self.mode]
    for socket in self.inputs:
        if socket.is_linked:
            name_or_value.append(socket.name)
        else:
            name_or_value.append(str(socket.default_value))
    return ' '.join(name_or_value)

@node_func(bl_idname='SvRxNodeMath', multi_label="Math", id=0, draw_label=draw_label)
def add(x: Number = 0.0, y: Number = 0.0) -> Number:
    return x + y

@ly29
Copy link
Contributor Author

ly29 commented Jan 30, 2017

The function could be shared but not quite ready to make it default. .title() would probably look nice

@zeffii
Copy link
Contributor

zeffii commented Jan 30, 2017

yeah looks ok , i find it more useful if the label is only drawn when self.hide evaluates to True.

  ...
if self.hide:
    custom label ...
else:
    return self.label or self.name

@ly29
Copy link
Contributor Author

ly29 commented Jan 30, 2017

Agreed

@portnov
Copy link

portnov commented Jan 30, 2017

If @node_func will have many arguments in many cases, then maybe better do it by using class override? smth like

class SomeNode(SvNode):
  bl_idname = ...
  multi_label = ...

  def draw_label(...):

  @node_func
  def add(...):
...

Here @node_func is used to indicate "main" method in the class.

?

@ly29
Copy link
Contributor Author

ly29 commented Jan 30, 2017

@portnov
Yeah maybe. But there isn't a one to one translation between node and function. While I agree that writing a more complete class could be useful, I am trying to enforce more standard behavior by default and keeping focus on the actual core functionality.

@portnov
Copy link

portnov commented Jan 30, 2017

Theoretically it is possible to make it working both ways. For example, @node_func(...) could expand into class definition. Then in case we have simple node, we could write it as decorated function. For more complex nodes it could be more readable if written as explicitly defined class with methods.

@ly29
Copy link
Contributor Author

ly29 commented Jan 30, 2017

I would almost prefer to do it with a class decorator in that case to make it clear. Also tempted to rewrite the class factory function using a similar method the stateful class decorator. The main thing I am trying to avoid is that the functions access their corresponding nodes during execution, right now it wouldn't have that big effect but I tempted to try some things later where it would perhaps have a big effect.

def stateful(cls):
    func = cls()
    get_signature(func)
    module_name = func.__module__.split(".")[-2]
    props = getattr(cls, 'properties', {})
    props.update(func.properties)

    class InnerStateful(cls, Stateful):
        category = module_name
        inputs_template = func.inputs_template.copy()
        outputs_template = func.outputs_template.copy()
        properties = props.copy()
        parameters = func.parameters.copy()
        returns = func.returns.copy()

    func_new = InnerStateful()
    class_factory(func_new)
    InnerStateful.node_cls = func_new.cls
    _node_classes[cls.bl_idname] = InnerStateful
    return InnerStateful

@stateful
class IntNode(ValueNodeCommon):
    bl_idname = "SvRxNodeIntValue"
    label = "Int input"

    def __call__(self) -> IntValue("i"):
        return np.array([self.value])

Anyway I will consider it and tomorrow I plan to make separate issue about how to write nodes and hopefully reach the basic set so we can start experimenting a bit more.

@ly29
Copy link
Contributor Author

ly29 commented Jan 30, 2017

To be clear what the nodes needs to implement right now is basically a compile(self) method returning a function object used to inform the the type. In general the node is generated from the function definition.

Looking at it as below it is of course not that complicated...

function(s) <-> class (autogenerated)
function(s) <-> class, handwritten

@portnov
I do appreciate your feedback

@ly29
Copy link
Contributor Author

ly29 commented Jan 31, 2017

I have been thinking about this while doing some other stuff today, some refactoring needed anyway

@ly29
Copy link
Contributor Author

ly29 commented Feb 1, 2017

@portnov

So looking back the support for custom classes where kind of halfway there already in that I had made some preparation for it but not really fully developed the idea.

Some refactoring later and now it is possible. For now in the following form.

class MyCustomClass(NodeBase):
     # do magical class stuff

@node_func(bl_idname = "SvRxMySpecial", cls_bases=(MyCustomClass,))
def my_special_func(....)
    pass

@portnov
Copy link

portnov commented Feb 1, 2017

good.

@ly29
Copy link
Contributor Author

ly29 commented Feb 1, 2017

So this is working sane enough for now.

@ly29 ly29 closed this as completed Feb 1, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants