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

Ideas about nodes #3

Open
ly29 opened this issue Jun 16, 2015 · 59 comments
Open

Ideas about nodes #3

ly29 opened this issue Jun 16, 2015 · 59 comments

Comments

@ly29
Copy link
Contributor

ly29 commented Jun 16, 2015

This isn't a finished model.

  1. UI and execution should be separated
  2. The execution unit, in which ever form, it takes shouldn't have to access the node but be created with it as input.
  3. Individual nodes shouldn't parse the whole data tree (in general) but only individual bits of data
  4. For practical purposes the most meaningful size of individual data is the mesh, a lot of data is only valid for
  5. A lot of flexibility and possibility would come from building the possibility of serializing the data into the system.

How should this be accomplished?

Let us at looks some different models to create nodes.

SN1

import numpy as np

def sv_main(n_items=1500, q_factor_x=2, q_factor_y=2, seed=6):

    in_sockets = [
        ['s', 'n_items', n_items],
        ['s', 'q_factor_x', q_factor_x],
        ['s', 'q_factor_y', q_factor_y],
        ['s', 'seed', seed]]

    np.random.seed(seed)
    points=np.random.uniform(0.0,1.0,size = (n_items,2))
    points *= (1000, 200)

    a = points[:,0]
    b = points[:,1]
    a = np.floor(a / q_factor_x) * q_factor_x
    b = np.floor(b / q_factor_y) * q_factor_y
    c = np.array([0.0 for i in b])
    d = np.column_stack((a,b,c))

    # consumables
    Verts = [d.tolist()]

    out_sockets = [
        ['v', 'Verts', Verts]
    ]

    return in_sockets, out_sockets

SN2

from itertools import chain

class DeleteLooseVerts(SvScriptSimpleFunction):
    inputs = [("v", "verts"), ("s", "pol")]
    outputs = [("v", "verts"), ("s", "pol")]

    @staticmethod
    def function(*args, **kwargs):
        ve, pe = args       
        indx = set(chain.from_iterable(pe))
        v_index = sorted(set(chain.from_iterable(pe)))
        v_out = [ve[i] for i in v_index]
        mapping = dict(((j, i) for i, j in enumerate(v_index)))
        p_out = [tuple(map(mapping.get, p)) for p in pe]
        return v_out, p_out
shader gabor_noise(
    point Point = P,
    vector Direction = vector(1, 0, 0),
    int Anisotropic = 0,
    float Bandwidth = 1.0,
    float Impulses = 16,
    output float Gabor = 0.8)
{
    Gabor = noise("gabor", Point,
                  "direction", Direction,
                  "anisotropic", Anisotropic,
                  "do_filter", 1, // Set to 0 to disable filtering/anti-aliasing 
                  "bandwidth", Bandwidth,
                  "impulses", Impulses);
}

Basically there are more or less identical, as I see the most easily inspectable one with types etc is more complex SN2.

What I propose is a python class that provides a functional unit that can be executed on individual pieces of the mesh.

@nortikin
Copy link
Member

vectorization on some level. main levels of vectorizations - objects and their values. we ned convention

@zeffii
Copy link
Contributor

zeffii commented Jun 16, 2015

as a model for generating mesh data I still really like SN1's simplicity, but it's rubbish not the most convenient as a modifier of incoming nested data, am quite happy not to bring SN1 to Redux. (potentially a loader to hoist the sv_main function as a class member of a class proper would be interesting to code )

@ly29
Copy link
Contributor Author

ly29 commented Jun 16, 2015

@zeffii
Yeah I also like SN1 and osl more style more.

@nortikin
please explain a bit more about your thoughts here, vectorization is a main concern of course and most nodes shouldn't have to support it.

@nortikin
Copy link
Member

@ly29
inputs as [1,2,3][1,2] have to produce two objects with three and two vertices i.e., and it have to occure on all nodes, plus 1 level is objects level, have to be defined by default and deal with objects in all nodes. it is rules

@ly29
Copy link
Contributor Author

ly29 commented Jun 16, 2015

@nortikin
Yes, that is one of the main issues that we to fix. The names of things might change a bit but such things will be possible by default, always, for all nodes.

@ly29
Copy link
Contributor Author

ly29 commented Jun 17, 2015

We could even have multiple forms for writing nodes as long as they can describe themselves properly. But a standard form is better.

@zeffii
Copy link
Contributor

zeffii commented Jun 17, 2015

Some nodes (minority) would be really short to describe because they are essentially Effects. Take Remove Doubles or Intersect Edges, nodes like that could be described in an eval/exec form like

from svrx_base_modules import intersect_verts_edges

inputs("verts")  # this named input is special and will always generate a V input
inputs("edges")  # now StringProperty, probably we should call it NestedPropery, or IndicesProperty
a, b = intersect_verts_edges("verts", "edges")
outputs("verts", a) 
outputs("edges", b)

Remove doubles:

from svrx_base_modules import remove_doubles

inputs("verts")
inputs("edges")
inputs("faces")
distance = ui_slider("distance", dict(default=0.0001, min=0.0))
a, b, c = remove_doubles("verts", "edges", distance)
outputs("verts", a) 
outputs("edges", b)
outputs("faces", c)

or

from svrx_base_modules import remove_doubles

v = inputs("verts")
e = inputs("edges")
f = inputs("faces")
distance = ui_slider("distance", dict(default=0.0001, min=0.0))
a, b, c = remove_doubles([v, e, f], [distance])
outputs("verts", a) 
outputs("edges", b)
outputs("faces", c)

or even more common

from svrx_base_modules import remove_doubles

v, e, f = inputs("verts", "edges", "faces")
distance = ui_slider("distance", dict(default=0.0001, min=0.0))
a, b, c = remove_doubles([v, e, f], [distance])
outputs("verts", a) 
outputs("edges", b)
outputs("faces", c)

or taking that abstraction further by using Mesh to mean verts, edges, faces

from svrx_base_modules import remove_doubles

# 'mesh' would auto inflate to `verts, edges, faces` sockets

v, e, f = inputs("mesh")
distance = ui_slider("distance", dict(default=0.0001, min=0.0))
a, b, c = remove_doubles([v, e, f], [distance])
outputs("mesh", [a, b, c]) 

here intersect_verts_edges and remove_doubles can be defined with zero knowledge of where they're called from..

Then i start to think adding the UI can be a separate file...or section in the same file

from svrx_base_modules import remove_doubles

v, e, f = inputs("mesh")

svrx_prop1 = StringProperty(name='prop1')
svrx_prop2 = IntProperty(name='prop2')
params = [svrx_prop1, svrx_prop2]

def draw(self, context):
    ... # optional, will draw props in order of appearance in params list.

a, b, c = remove_doubles([v, e, f],  params)
outputs("mesh", [a, b, c]) 

@zeffii
Copy link
Contributor

zeffii commented Jun 17, 2015

a good time to rethink the naming convention of our socket types. StringsSocket needs to go.

@ly29
Copy link
Contributor Author

ly29 commented Jun 17, 2015

StringsSocket is out for sure.

the whole getting data from socket paradigm has to go I think, the node execution unit should be called with the relevant data, for its execution type.

The socket is an indication of data type, not a guarantee unless we enforce type safety on top of python which to me, doesn't seem worth it.

I general I would like to store the blender properties in the socket, not in the node, but this has some limitations so am not sure about it. The problem of course being the we would have to generate a new socket type for each case of say limits in the float property. I wish properties could be individually adjusted with default/min/max etc

@zeffii
Copy link
Contributor

zeffii commented Jun 17, 2015

I general I would like to store the blender properties in the socket,

right i get ya!

@ly29
Copy link
Contributor Author

ly29 commented Jun 17, 2015

Socket Types

  • Vertices
  • Numbers (float, int and bool)
  • Mesh index data
  • Matrix
  • Color
  • String
  • Euler
  • Quaternion
  • Blender obj reference
  • Bmesh

@ly29
Copy link
Contributor Author

ly29 commented Jun 17, 2015

For now I think pynodes are workable but writing our own nodes thing would be a nice challenge.

@zeffii
Copy link
Contributor

zeffii commented Jun 17, 2015

can we not combine Matrix/Euler/Quaternion as TransformsConstructs, where the socket/stream has a mode. (just wondering if the separation is needed.. )

@zeffii
Copy link
Contributor

zeffii commented Jun 17, 2015

I think Curve data would be great too, 10 socket types isn't too bad.. I think @nortikin had objections to raising the socket count?

@ly29
Copy link
Contributor Author

ly29 commented Jun 17, 2015

Node -> Dict and Dict -> Node

Ideally this should be two parts, one being the blender properties ui location, dimension, the other being the properties of the node as relevant to sverchok execution.

@zeffii
A certain amount of sockets is needed I think, the other approach to having many socket types is too have one socket for everything which isn't a good option.
I don't think we should enforce type safety however, if you want use color data as vertex locations go ahead.

@zeffii
Copy link
Contributor

zeffii commented Jun 17, 2015

I don't think we should enforce type safety however, if you want use color data as vertex locations go ahead.

yeah, this fuzzy logic has many positive uses, and it mirrors the behaviour of the shader node trees

@ly29
Copy link
Contributor Author

ly29 commented Jun 17, 2015

exactly

@ly29
Copy link
Contributor Author

ly29 commented Jun 17, 2015

I also think we should copy the socket naming convention from the rest of blender i.e. Name instead of name, that being more controversial I understand.

@nortikin
Copy link
Member

about names - propose tolimit names lenght to 5-6 maximum 8
in Grasshopper there are 1-letter names wich is not cozy, better to inform user in 4 characters, he will understand.
Vers
Edgs
Pols
Mats (it is better to stay with its name but put there Euler and quaternion i guess)
Float
Int
Obj
Curv
Str
Bmesh
ID (with explanation - it is blender ids)
Color
and additionaly short:
Norm
Tang
Centr
Mask
Index
Vdonor
Vrecip

Have to admit we need basic sockets to be defined, not to use verts+pols in one node and bmesh or obj in other. it should be some kind of basic data-paradigm. If we use bmesh as second basic in hierarhy than obj have to have switch or something like converter we have to have in any case. more types more probllems.
Obj and ID can they be combined? And bmesh can be used instead of verts and polygons? we can use bmesh in general cases on modifiers and analyzers, list functions and others, but when generating or work with lowlevel data we need decompose object to bmesh (obviouse it is basic socket) and matrix and than to vertices and polygons and edges and than vertices to digits.

Sorry for many characters

@nortikin
Copy link
Member

the problem of matrix generator and matrix deform node is not obviouse for user - second float socket for rotation can eat vertex (or vector wich is second problem how to name).
Or we can add Vers in socket class as we do with dot and number of objects, than we have not headake how to name, but have to define behaviour of labeling - in node name Vers becomes Vers. in labe but name Norm becomes VersNorm. in label for example, or node-creator's name Centr becomes VersCentr.12

@nortikin
Copy link
Member

now we have to collect all issues to mind-map, because it helps a lot, at least for me.

@zeffii
Copy link
Contributor

zeffii commented Jun 17, 2015

On UI I prefer proper names or sensible shortnames. I think 'vers' is not the best. 'verts' is one letter extra and actually means something in English. Similarly with Edges what's the problem with the extra e? I think the 8 letter max is good, but trying to make shortnames to save one letter if it's already under 8 characters is not so pretty.

@nortikin
Copy link
Member

you know English better anyway, let it bee.
👍

@zeffii
Copy link
Contributor

zeffii commented Jun 17, 2015

polys, bm, VNorm, FNorm

@zeffii
Copy link
Contributor

zeffii commented Jun 17, 2015

but agreed that simply one letter like grasshopper is a bit too economical :)

@zeffii
Copy link
Contributor

zeffii commented Jun 17, 2015

it would be awesome if socket nipples had a hover state which could trigger a tooltip for that socket, I haven't seen that anywhere, in blender , but would be handy.

@nortikin
Copy link
Member

https://gist.github.com/nortikin/eace3b46e85c585b06a0 this has to be in one node (not formula shape node, but make surface from two vertices sequences)

@nortikin
Copy link
Member

@zeffii even not imagin how. OpenGL popups?

@ly29
Copy link
Contributor Author

ly29 commented Jun 17, 2015

I think is 8 shorter max than needed, for example in one of the most used nodes a socket is called Displacement. Anyway I can live with 8 but agree with @zeffii about clarity.

tooltips would be nice.

@nortikin
While such node is needed let us keep this discussion on a more fundamental level

@zeffii
Copy link
Contributor

zeffii commented Jun 17, 2015

i'm not fussy about going over 8 characters, if it's acceptable for standard Blender nodes, as @ly29 mentions 'Displacement' is 12 and i've never considered that too long. After a while of using new nodes your brain doesn't really read them any more, it works more on position (if the node isn't dynamically naming sockets )

@zeffii
Copy link
Contributor

zeffii commented Jun 17, 2015

about fuzzy sockets, I think Matrix socket should accept and implicitly convert Locations / Vectors to a Translate Matrix, this would allow convenient

  • Location -> Matrix

rather than

  • Location -> Make Matrix -> Matrix

@ly29
Copy link
Contributor Author

ly29 commented Jun 17, 2015

Such conversion rules should be supported.

@zeffii
Copy link
Contributor

zeffii commented Jun 17, 2015

@ly29

I wish properties could be individually adjusted with default/min/max etc

yeah, this is such a pain, but perhaps it is possible to simply assign a dynamically added Property to a node along the lines of

    some_prop = IntProperty(min=0, max=20, step=2)

    #later 
    stored_value = copy.copy(self.some_prop)
    del self.some_prop
    self.some_prop = IntProperty(min=0, max=40, step=4)
    self.some_prop = stored_value

del is not supported

@nortikin
Copy link
Member

@zeffii again, rna, not id, maybe node can have id property on fly? oh, no it cannot, for sure

@nortikin
Copy link
Member

@ly29 material socket with texture assigned to it? we can make unwrap with python, maybe someone wish to assign material and unwrap exact polygon of bmesh (when we decompose bmesh to vertices polygons we lost unwrap layout data and others how to solve?)

@zeffii
Copy link
Contributor

zeffii commented Jun 17, 2015

I was under the impression that if you have a list of edges from obj.data.edges, say like edges [0,1,4,5,6,10], and then take a bm representation of obj.data , the edges on indices of bm.edges are not the same as obj.data.edges, the only shared data are bm.verts and obj.data.vertices, bm itself seems to just mangle them for speed reasons. (But ! I do recall seeing a sort function in the bmesh code)

@ly29
Copy link
Contributor Author

ly29 commented Jun 17, 2015

@nortikin
I think we might work with bmesh for that.

@nortikin
Copy link
Member

might but appears several ways to deal to sverchok - first is usual low level, second is bmesh, third objects and scene. Or maybe whan we edit in low level than bmesh not deleted during layout but updated its data - changed vertices - making ensure lookup table and output it? so every bmesh decomposition must meet node called change bmesh data? it becomes difficult but not see how to deal in other way with bmesh.

@nortikin
Copy link
Member

or we deal with data as container with bmesh,matrix,verts,edges,polygons,textures,materials etc. and pipeline of processing layout only change some part of it even if it is not presented as bmesh socket? so from start to end we deal with object containing bmesh? not sure, because we can devide one mesh to parts and how than?
allready problem is how to view bmesh and change it after viewing, we need get it from scene again, it must not happend

@zeffii
Copy link
Contributor

zeffii commented Jun 17, 2015

after adding any verts + edges + face manually to a bm we must call bm.<type>.ensure_lookup_table(), i'm not positive it is needed excplicitly when doing a bmesh.ops operation

@ly29
Copy link
Contributor Author

ly29 commented Jun 17, 2015

Some of the problems you see I don't see. However it is not the stage I am at the moment.
I think some things will resolve themselves, some will not however. But then we try to solve them then, incrementally.

@nortikin
Copy link
Member

nortikin commented Jul 3, 2015

guys, we also need vector socket that will have:
[x,y,z][dx,dy,dz] and optionally dx,dy,dz can be normalized vector but additionally we can add strength or capasity of each vector in length single value
so plain list will consists of 7 numbers [x,y,z,dx,dy,dz,c,...] we can teach viewers to display them (Jimmy Gunnavan asked long ago).

@zeffii
Copy link
Contributor

zeffii commented Jul 3, 2015

but dx, dy, dz can be inferred at calculation time, it can be a simple separate additional structure for np_Mesh to produce when asked. Storing that information explicitely and allways is not necessary. If we have a node that uses deltas then it can call a function and store all the result to the np_mesh.delta_vectors array or something.

or make it possible to push an array of deltas at any time onto np_Mesh, same with 'weight' (capacity) w. Separate stream included in np_Mesh.

@nortikin
Copy link
Member

nortikin commented Jul 3, 2015

who knows what else will be needed - vertex colors can be coded just like thhat, so additional dictionaries needed extremely, maybe globally it will cover curve type of data -with addtional handles to knots, so you will have additionally two lists with identical looku table for level 2,3,4... so level 1 wil only lookup as 3 or 1 or 2 numbers depends on what we need. for one object one lookup table wich will decrease memory usage. cool.

@ly29
Copy link
Contributor Author

ly29 commented Nov 14, 2015

Function annotations seems perfect for type info...
https://docs.python.org/3/tutorial/controlflow.html#function-annotations

def f(a : int = 2, b: float = 3.0) -> (float, float):
    return b, b*a
>>> import inspect
>>> f.__annotations__
{'b': <class 'float'>, 'a': <class 'int'>, 'return': (<class 'float'>, <class 'float'>)}
>>> sig = inspect.signature(f)
>>> str(sig)
"(a:int=2, b:float=3.0) -> (<class 'float'>, <class 'float'>)"

@ly29
Copy link
Contributor Author

ly29 commented Nov 14, 2015

Furthermore in 3.5, will have to review a bit soonish.
https://docs.python.org/3/library/typing.html

@ly29
Copy link
Contributor Author

ly29 commented Nov 15, 2015

import numpy as np

def linspace(start : float = 0.0, stop : float = 1.0, count : int = 10) -> ("linspace", np.array):
    return np.linspace(start, stop, num)

Something like this could be a complete node for simple cases, like node scripts. Annotations are amazing (not really but a very nice feature). UI could be generated from this.
Of course this isn't enough for every node but could go a long way.

@zeffii
It would make script node mk1 clearer.

@zeffii
Copy link
Contributor

zeffii commented Nov 15, 2015

yeah, I would like to commit to doing a massive overhaul of mk1. Looking back at it it seems massively bloated. I'd like to think that I could do better, after a year and half :)

But unfortunately my mother has been seriously ill for a few months and will be operated on tomorrow for the n-th time ... the outcome of which will greatly influence how much time I can commit to this.. and how clear i'll be able to think.

@ly29
Copy link
Contributor Author

ly29 commented Nov 15, 2015

I am sorry to hear about your mother. Take care.

I have been busy lately but I want to boot strap this now, the ideas have been brewing in my head.
Yeah, we do learn a lot. Have to get into it again, want at least try some of them out.

I like the fact the fact that with this we could basically have an OSL like simplicity.

@zeffii
Copy link
Contributor

zeffii commented Nov 15, 2015

Thanks @ly29 . Difficult times, but I do like the distractions and it's why i've been coding in the open a bit more again.

I'm interested to see a less complicated back-end for SN MK1, and wouldn't feel obliged to retain any form of backwards compatibility, scripts should be easy to convert as and when they are needed. Afterall the interface is what would change, the work-code mostly remains unchanged.

Having some typed interface, is kind of what sn mk1 already has, but making it explicit is something i'm all for. (Even if we have to allow custom data types... float vs list..vs np array etc :)

@ly29
Copy link
Contributor Author

ly29 commented Nov 15, 2015

Progressing slowly.

import numpy as np

def linspace(start : float = 0.0, stop : float = 1.0, count : int = 10) -> [("linspace", np.array)]:
    return np.linspace(start, stop, num)

linspace.label = "Linear space"

From this I produce a node. But no connection the other way, the code doesn't do anything more than inspect the node and producing a ui element automatically from the above.

skarmavbild 2015-11-15 kl 20 03 17

@zeffii
Copy link
Contributor

zeffii commented Nov 15, 2015

using a class factory? :)

@ly29
Copy link
Contributor Author

ly29 commented Nov 15, 2015

Yeah. Ugly thing.

def node_factory_from_func(func):
    annotations = func.__annotations__
    if not annotations:
        return None
    class_name = "SvRxNode{}".format(func.__name__)
    bases = (SvRxNode, bpy.types.Node)

    sig = inspect.signature(func)
    inputs_template = []
    for name, parameter in sig.parameters.items():
        s = Socket(annotations[name], name, default_value = parameter.default)
        inputs_template.append(s)

    ret_values = annotations["return"]

    outputs_template = [Socket(socket_type, name) for name, socket_type in ret_values]

    node_dict = {}
    node_dict["bl_idname"] = class_name
    node_dict["bl_label"] = getattr(func, "label", func.__name__)
    node_dict["bl_icon"] = 'OUTLINER_OB_EMPTY'
    node_dict["inputs_template"] = inputs_template
    node_dict["outputs_template"] = outputs_template

    node_class = type(class_name, bases, node_dict)
    return node_class

@zeffii
Copy link
Contributor

zeffii commented Nov 15, 2015

@ly29
Copy link
Contributor Author

ly29 commented Nov 15, 2015

Yeah, it was you that introduced me to type(). Nice stuff, annotations just takes it a step further.

@zeffii
Copy link
Contributor

zeffii commented Nov 15, 2015

i'm nodding my head with a big YES :) __annotations__ and inspect.signature are the way to go.

@ly29
Copy link
Contributor Author

ly29 commented Nov 16, 2015

ordered dict would be nice, but maybe not as pythonic.
I will clean things bit up a bit, mostly dead code removal, and upload to github.
Then next step is serialize the node layout followed.
Then make the back end.
It is quite rough but I think know how I want it to work.

@nortikin
Copy link
Member

@zeffii take care of mother's health, hope its to be all right after all.

@ly29
Copy link
Contributor Author

ly29 commented Nov 23, 2015

Of course there is big problem with the annotations above, np.array isn't the type of an numpy array np.ndarray is. Furthermore the type of numbers inside an numpy array isn't float or int
How much details should be exposed to the user? float, int etc are nice looking, type, it certainly beats numpy.float64, therefore I propose a series of conversions rules for types.

Sensible things like np.float64 <-> float

Below is a schedule of numpy types matched to python types.

(<class 'numpy.datetime64'>, <class 'NoneType'>)
(<class 'numpy.uint64'>, <class 'int'>)
(<class 'numpy.int64'>, <class 'int'>)
<class 'numpy.void'> could not be converted
(<class 'numpy.bool_'>, <class 'bool'>)
(<class 'numpy.timedelta64'>, <class 'int'>)
(<class 'numpy.float16'>, <class 'float'>)
(<class 'numpy.uint8'>, <class 'int'>)
(<class 'numpy.int8'>, <class 'int'>)
(<class 'numpy.complex64'>, <class 'complex'>)
(<class 'numpy.float32'>, <class 'float'>)
(<class 'numpy.uint16'>, <class 'int'>)
(<class 'numpy.int16'>, <class 'int'>)
(<class 'numpy.object_'>, <class 'int'>)
(<class 'numpy.complex128'>, <class 'complex'>)
(<class 'numpy.float64'>, <class 'float'>)
(<class 'numpy.uint32'>, <class 'int'>)
(<class 'numpy.int32'>, <class 'int'>)
(<class 'numpy.bytes_'>, <class 'bytes'>)
(<class 'numpy.complex256'>, <class 'numpy.complex256'>)
(<class 'numpy.float128'>, <class 'numpy.float128'>)
(<class 'numpy.uint64'>, <class 'int'>)
(<class 'numpy.int64'>, <class 'int'>)
(<class 'numpy.str_'>, <class 'str'>)

Type conversion code from stack overflow http://stackoverflow.com/questions/9452775/converting-numpy-dtypes-to-native-python-types

def get_type_convert(np_type):
    convert_type = type(np.zeros(1,np_type).tolist()[0])
    return (np_type, convert_type)

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