Closed zeffii closed 9 years ago
This pretty much aligns with my thinking.
kind of tricky to start bootstrapping this. Let's start with a very non functional Custom NodeTree with no more than a couple nodes (one of which being a Debug print/console node), that way we can
__init__.py
.I don't think we even need more than 1 socket type to begin with..
I think all the kludge
has been added now to __init__.py
and node_indexing.py
( formerly called menu.py ... i was never happy with this name) . it seems like some of the uglier stuff there is necessarily ugly ( but it could in some way be pushed into sub modules and imported and called from init ) --
Should we care? this isn't the portion of sverchok that I have the biggest desire to tear down. it works, and is write once use many.
Concretely what needs to happen from the low level dict
that represents a node tree?
.bl_idname
to a functional unitWriting this above one step is longer and needs more formal declaration. Some consideration into what is a valid programs needs to be done, the nodes links does create a quite clear picture of what is going on considering node groups and special contexts some further thoughts needs to be put down. Especially considering nodes that control things, overtook only has two such node cases, node groups and switch which works inside existing framework of what nodes, now such nodes should be treated as special symbol nodes, I think.
@zeffii A separate indexing step is one thing that is need, a lot of organizing to be done. Right now it is a bit fragile.
@ly29 currently i'm not deeply engaged with the code you've written (for reasons explained previously). The abstractions seem to be clean enough. The real test will be to implement more complex nodes, propose a trig node (float -> [sin(float), cos(float), 0]) for fast circles.. a drawing node is a big task, so perhaps a proper pydata->objmesh node for quick and dirty testing. (Plenty of existing code to ransack, but I understand if you want to take a new approach (especially to the naming))
First step is just debug print the nodes I have now actually running, then gradually add complexity. I expect to do a lot of copying from Sverchok since know my way around the code base quite well.
Tentatively which are the special nodes that require knowledge from the system and aren't just functions that execute?
Once something is up and running things to do include documentation, writing more node, thinking about interaction with blender.
An OpenGL viewer is a big topic, and something to look forward to experiment with, sadly this is still some time away.
Some further thinking gives the following order:
While meditating work on menu, node indexing or documentation.
so would the mathnode be declared something like
import numpy as np
from ... import math_node_modes
from ... SvRx.types import mode_enum
def math(x: np.array = 0.0, y: np.array = 1.0, mode: mode_enum = 'add') -> [("res", np.array)]:
return math_node_modes(mode, (x, y))
where mode_enum
is just a class to indicate that that parameter will delegate to some enum/dict
I have been thinking about how to combine different function into one logical ui, as stated above is one approach but I don't feel so happy about it. We also have some nice ui additions on the current math node where it show different things depending on connections etc. Even though we can certainly create some logic to auto generate this, but perhaps having handwritten ui classes for certain nodes isn't a bad idea.
So pure ui class that resolve into appropriate functions at a later stage. But I think what to do will become clearer when the other pieces fall into place.
However, the above should most definitely be supported anyway.
i'm just trying to get a better understanding of where this is going regarding multi-mode nodes, or if nodes will be hot-swapped in place to a dedicated MathAdd
node, if there's a mode change.
I think I agree that certain nodes can be suitably handwritten, at least initially as a boot strap step.. eventually, if a more automated way becomes clearly more convenient we migrate.
The upside is we don't have backward compatibility limitations for the foreseeable future. I expect and hope stuff will break in the name of progress.
Everything will break. Backwards compatibility is a thing for the past. Honestly, surpassing the current Sverchok is a big project, but with better standards and auto generation of boring code it is possible. A lot of time was spent avoiding breaking things because some thing weren't well defined.
Just keeping the i/o standards of each node will prove invaluable.
But first boot strapping...
Execution of simple functions working.
>>> layout = ng.serialize()
>>> node = SvRx.core.compiler.compile(layout)
>>> node.execute()
Entering debug_print
Entering add
Entering Linear space
Entering Range
[ 0. 1.11111111 2.22222222 3.33333333 4.44444444
5.55555556 6.66666667 7.77777778 8.88888889 10. ]
Next step include support for data tree, and type matching.
There isn't any direct reaction to ui actions. Python console is needed for running things. Also note that the different steps right now correspond to mostly to the graph even if it don't use the AST module. Still needs some more grease between the execution units...
i don't have any sensible questions, but i'm glad to see you're juggling these concepts and still catching them :)
It is very incremental right now, small steps forward. But a sensible structure, or at least much more so than the old mess... Two steps, here, matching different list lengths and only executing each node once. Also, as a implementation detail no socket values exist but special value node are implemented. Not so sure about that.
The graph, the value nodes are named "node.socket". The graph is sorted by socket order.
'debug_print'
'add'
'add.002'
'Range'
'Range.start'
'Range.stop'
'Range.step'
'add.003'
'add.001'
'Linear space'
'Linear space.start'
'Linear space.stop'
'Linear space.count'
'add.001.y'
The execution order is:
Range.start
Range.stop
Range.step
Range
Linear space.start
Linear space.stop
Linear space.count
Linear space
add.001.y
add.001
add.003
add.002
add
debug_print
nice and clean
Side step to autoload and auto create menu from available nodes. Based on http://stackoverflow.com/a/25562415
def import_submodules(package, recursive=True):
""" Import all submodules of a module, recursively, including subpackages
:param package: package (name or actual module)
:type package: str | module
:rtype: dict[str, types.ModuleType]
"""
if isinstance(package, str):
package = importlib.import_module(package)
results = {}
for loader, name, is_pkg in pkgutil.walk_packages(package.__path__):
print(name)
full_name = package.__name__ + '.' + name
results[full_name] = importlib.import_module(full_name)
if recursive and is_pkg:
results.update(import_submodules(full_name))
return results
Then in nodes
we build a node_dict
which is a central repository of node data.
From this we build the menu, recording category, class and function associated with each bl_idname
, think about populating with more metadata like types etc.
So right now, all that is needed for adding a node is a adding .py
file with the right info into a subdirectory of nodes
. A class will be created with ui and the menu will be populated.
and with this it is possible to do a reload by unloading package
and then reloading?
Maybe, haven't tested that, I just do a reload on the imported modules. works anyway.
Working if node.
Compile/execution system needs some cleaning up... Especially the flow of data need some proper cleaning up.
Todo:
With that I would consider bootstrapping and getting started done.
Developing nodes flow:
Create a file called trig.py
in nodes/math
with:
import numpy as np
def sin(x: np.array = 0) -> [("res", np.array)]:
return np.sin(x)
def cos(x: np.array = 0) -> [("res", np.array)]:
return np.cos(x)
SvRxFunc = [sin, cos]
And press F8... voila.
The SvRxFunc = [sin, cos]
declaration of which function to create nodes from could become optional using inspect. Using importlib magic, blender texts files could be considered modules and imported under a user label.
Making special code script node practically not needed, (except importlib trickery), more like a simple user interface thing.
can we make somehow to user zoom on error nodes? we can make list of error nodes, and button for zoom, than it can orient on layout and center view. What do you think? or better to find nodes by name? not sure. for noob better automate.
Yes automatic is a better. The whole user interface is thing that could be redone much smoother I think, for a later stage.
should i draw draft of new menus? Should we do shift+A menu call as in AN is
I have not played around with AN in a long while, which I probably should do a bit.
Start a new issue for ui issues so the discussion does not get lost here.
Infinitely auto multiplying levels. Working but not verified.
A I suspected needs a little bit more work.
def sin_cos(x: np.ndarray = 0) -> [("sin", np.ndarray), ("cos", np.ndarray)]:
return np.sin(x), np.cos(x)
:)
Two return values doesn't really work yet. Not that anything really works yet. But the class from function is really nice and autoloading is really nice, no?
Yep, seems lightweight. Will love to get to work on nodes once the graph is complete. I don't want to interfere with this.
Array math node, how will that look (internally) , my implementation in FLOW was a little convoluted.
I'm struggling a bit with types at the moment. How to deal with them...
Please show some code, and I can comment.
I can see now that that a lot of the code should have been handled in the sockets somehow, and abstracted away from the working code. It was a very simple and dumb execution model.
Yeah, that is thing I am struggling with. I resisting, for some reason, to write it that verbose but struggling for a cleaner version. I think I have to look a bit at type handling systems in python...
Also I am not happy with any of the ideas for socket handling. The most temping right now is to have a one Node for every math op with a switch that switches between them and reconnecting etc...
def match_length(args):
lengths = [len(arg) for arg in args]
if lengths.count(lengths[0]) == len(lengths):
return args
max_len = max(lengths)
out = []
for arg, length in zip(args, lengths):
if length < max_len:
new_arg = np.zeros(max_len)
new_arg[:length] = arg
new_arg[length:] = arg[-1]
out.append(new_arg)
else:
out.append(arg)
return out
def recursive_map(func, args, inputs_types, level=0):
if level == 0 and isinstance(args, inputs_types[0]):
return func(*args)
checked = [isinstance(arg, types) for arg, types in zip(args, inputs_types)]
if all(checked):
return func(*args)
if any(checked):
new_args = [repeat(arg) if check else arg for check, arg in zip(checked, args)]
else:
new_args = args
return [recursive_map(func, arg, inputs_types, level + 1) for arg in zip(*new_args)]
Should float
be auto converted into int? It is this kind of convenience that suits a node system, having to convert values just lead us to write range(int(start), int(stop))
and similar things in the old Sverchok.
These functions are all bit to specific and skips important bits. Oh well.
The most temping right now is to have a one Node for every math op with a switch that switches between them and reconnecting
It's even tempting to make an abstract text layer for all of this, instead of nodes first. like DSL https://github.com/Sverchok/SverchokRedux/issues/12
yes, most definitely have a way to auto cast,
hehe:)
First type rule, an int works every a float value works, python and numpy converts as needed.
UI hooks works, writing nodes works (and has never been as simple). For now I consider this bootstrapped. The state is super alpha and some refactoring needs to be done. Todo urgently is dealing with broadcasting and typecasting. Then more complex nodes can be created, not as in code but as having python properties and ui etc.
A lot of things await.
import numpy as np
def random_uniform(low: float = 0.0, top: float = 1.0, count: int = 10) -> [("rnd uniform", np.ndarray)]:
return np.random.uniform(low, top, count)
random_uniform.label = "Random uniform"
SvRxFunc = [random_uniform]
I can't help but feel that there should be a way to use the signature of the numpy functions themselves to bulk generate their nodification. By this I mean we could declare the list of numpy functions that we want to generate a node for and let introspection make the SvRxFunc.. ?
It is an interesting approach. Could certainly work quite well for the nodes with 1:1 mapping to numpy concepts
I guess by parsing the .__doc__
attribute.
print(numpy.random.uniform.__doc__)
yeah hmmmm it's tempting..
it's a finite function base , so perhaps manual declaration isn't too bad.. the parsing code might be longer than the manual aprroach
I am practically itching to get started on this...
I was reading about Hylang (A form of Lisp that compiles to Python), it uses an 'abstract syntax' as an intermediate representation of the code, the Lisp is translated to Python using this route (i think):
I'm not sure how well this translates to what we want the future of Sverchok to be, but it's worth to pause for a while to consider something @ly29 said some time ago (paraphrased): "Sverchok is almost a language in its own right". Granted you have to look at it a bit more abstract than the Node tree you see in the Node editor. but every node tree is a definite, predictable (usually), program with flow control.
It's to time experiment with an intermediate representation of the Node tree
DAG
The purpose of the DAG would be to construct the correct execution order for the Nodes, and output one or more executable entities which can be run in Sequence or Parallel.
abstract node_tree
This serves multiple purposes
More later