From 3c7f65ae71570804cfd03f9169ffd658c529c1eb Mon Sep 17 00:00:00 2001 From: Ilya Portnov Date: Sat, 9 May 2020 17:55:41 +0500 Subject: [PATCH 1/4] Support Curve, Surface, ScalarField, VectorField sockets in the SNLite node. --- utils/snlite_importhelper.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/utils/snlite_importhelper.py b/utils/snlite_importhelper.py index 0f02f7068..61a1edfdf 100644 --- a/utils/snlite_importhelper.py +++ b/utils/snlite_importhelper.py @@ -27,7 +27,14 @@ TRIPPLE_QUOTES = '"""' UNPARSABLE = None, None, None, None sock_dict = { - 'v': 'SvVerticesSocket', 's': 'SvStringsSocket', 'm': 'SvMatrixSocket', 'o': 'SvObjectSocket' + 'v': 'SvVerticesSocket', + 's': 'SvStringsSocket', + 'm': 'SvMatrixSocket', + 'o': 'SvObjectSocket', + 'C': 'SvCurveSocket', + 'S': 'SvSurfaceSocket', + 'SF': 'SvScalarFieldSocket', + 'VF': 'SvVectorFieldSocket' } -- GitLab From 97af09e625bd8e75fa4addad7435464c41abef74 Mon Sep 17 00:00:00 2001 From: Ilya Portnov Date: Sat, 9 May 2020 17:56:08 +0500 Subject: [PATCH 2/4] Initial version of documentation for the SNLite node. --- docs/nodes/generator/script1_lite.rst | 207 +++++++++++++++++++++++++- 1 file changed, 203 insertions(+), 4 deletions(-) diff --git a/docs/nodes/generator/script1_lite.rst b/docs/nodes/generator/script1_lite.rst index 666358e4b..02b60a9c1 100644 --- a/docs/nodes/generator/script1_lite.rst +++ b/docs/nodes/generator/script1_lite.rst @@ -1,6 +1,205 @@ -SNLite Docs ------------ +Script Node Lite +================ -There's no rst for this at present, please read the preliminary (but comprehensive) docs on : +Functionality +------------- + +This node allows the user to write arbitrary node logic in Python. It is useful +when there is no standard node in Sverchok for your particular purpose; you may +want to use this if you want some complex piece of logic to be used in a couple +of your own projects, or if you are prototyping a new standard node for +Sverchok itself. + +The script node implementation is intended for quick, non serious, experimentation. + +Features +-------- + +- easy template importing directly to node, or to text block (the latter is useful for making changes to a template before loading... as is needed with the `custom_draw_complex` example ) + +.. image:: https://cloud.githubusercontent.com/assets/619340/25421401/ff29eed2-2a5c-11e7-9594-f85295178f57.png + +- doesn't force an output socket, but an input socket is expected (else how does it update?!) +- does allow you to set some defaults : numbers, and lists of stuff +- does allow you to set the level of nestedness; means you don't need to unwrap/index stuff via code +- does allow you to declare an override to the node's draw_button function, well... think of it more like an `append` to the draw code. There's an example of how you might make a light weight Curve mapping node by using the ui component of a RGB curve from a material node tree. it's a little convoluted, but it's usefulness musn't be dismissed. +- has the option to **auto inject** the list of variable references as `parameters` much like javascript does for 'arguments` inside a function. In essence implemented like this :: + + parameters = eval("[" + ", ".join([i.name for i in self.inputs]) + "]") + + +To enable this feature add the word "inject" on its own line in the header. If you have 3 input sockets, called `radius, amplitude, num_verts`, then the variable `parameters` will be :: + + parameters = [radius, amplitude, num_verts] + +This is handy for inner functions that are arranged to take arguments in exactly that order. See ``_ for an example use. Usually you'll use the 'vectorize' function with this to zip through each pair of arguments. see https://github.com/nortikin/sverchok/issues/942#issuecomment-263912890 + +- added a helper function `from sverchok.utils.snlite_utils import vectorize` , and this is made available to scripts without the need to import it now. Also see https://github.com/nortikin/sverchok/issues/942#issuecomment-263912890 + +- added an include directive:: + + """ + ... + include + """ + +The include directive ensures the dependency is also stored in the gist when exported as json. The file named in angle brackets must be present in the current .blend file's text blocks. + +- added a default enum to make the custom draw a bit more useful. For the time being there is only one custom enum, the default is "A","B" . called `self.custom_enum`. No spaces in the elements, yes spaces between the elements.:: + + """ + enum = word1 word2 word3 + """ + +- add `ddir` (`dunderless dir`) to local namespace. `ddir(object, filter_str="some_string")` . filter_str is optional.:: + + def ddir(content, filter_str=None): + vals = [] + if not filter_str: + vals = [n for n in dir(content) if not n.startswith('__')] + else: + vals = [n for n in dir(content) if not n.startswith('__') and filter_str in n] + return vals + +- add `bmesh_from_pydata` and `pydata_from_bmesh` locally, so you don't have to import. +- add operator callback. See: https://github.com/nortikin/sverchok/issues/942#issuecomment-300162017 :: + + """ + in verts v + """ + + def my_operator(self, context): + print(self, context, self.inputs['verts'].sv_get()) + return {'FINISHED'} + + self.make_operator('my_operator') + + def ui(self, context, layout): + cb_str = 'node.scriptlite_custom_callback' + layout.operator(cb_str, text='show me').cb_name='my_operator' + +- `statefull` (like Processing's setup() ): see this `Reaction Diffusion thread / example `_. +- 'reloading / imports' : see `importlib example here `_, this is especially useful for working with more complex code where you define classes outside of the snlite main script. + +Syntax +------ + +The syntax might look something like this:: + + """ (tripple quote marks to demark the header) + in socketname type default=x nested=n + in socketname2 type default=x nested=n + out socketname type # (optional) + """ + < any python code > + +This tripple quoted area (a "directive comment", or *header*) must be the first thing in the ``.py`` file. It helps declare sockets and defaults and is a space to enable certain options (more about this later). The above example header can be written slightly less verbose:: + + """ + in socketname type d=x n=i + in socketname2 type d=x n=i + out socketname type + """ + < any python code > + ``` + +A few things to notice: + - i've dropped the words ``default`` and ``nested`` in favour or ``d`` and ``n``, but you'll also see examples where I just write ``in socketname type .=200 .=2`` , the ``d`` and ``n`` don't mean anything, the only real requirement there is that there's a single character directly to the left of the ``=``. + - Socket names will be injected into the local scope, for example: + - if you have an input socket called 'normals', then there will be a variable called normals available to read from. + - if you have an output socket called 'edges_out', then that variable is also automatically available for you to insert data into - behind the scene snlite will do `edges_out = []` prior to executing your code. At the end of your code SNLite will read whatever the content of your `edges_out` is and use that as the output values for that socket. + +- **inputs**:: + + ``` + direction socketname sockettype default nestedness + in radius s .=1.2 .=2 + ``` + +- `direction` ``in`` means "make an input". +- `socketname` means "give this socket a name / identifier" +- `sockettype` declares what kind of socket is to be used. The supported types are: + - Vertices (``v``) + - Strings/Lists (``s``) + - Matrices (``m``) + - Curves (``C``) + - Surfaces (``S``) + - Scalar fields (``SF``) + - Vector fields (``VF``) + - Objects (``o``) +- `default` is where you give a default initialization value. A list, tuple, float, or int.. + - **warning**: don't include any spaces in the iterables - this will break parsing +- `nestedness` deserves some explanation. In sverchok every data structure is nested in some way. + +Some familiarity with python or the concept of sublists (lists of lists) is needed to understand this. It's harder to explain than to use. + +.. image:: https://cloud.githubusercontent.com/assets/619340/23399114/639cdc34-fd9f-11e6-8aa2-0238f2020373.png + + +- `n=2` means ``named_input.sv_get()[0][0]`` - means you only want a single value. :: + + named_input = [[20, 30, 40], .....] # or [[20]] + value_to_use = named_input[0][0] # 20 + +- `n=1` means ``named_input.sv_get()[0]`` + - You would use `n=1` if you only ever plan to work with the first incoming sublist. This will essentially ignore the rest of the incoming data on that socket. +- `n=0` means ``named_input.sv_get()`` + - Generally you would use this if you plan to do something with each sublist coming in, for example if the input contains several lists of verts like here: + +.. image:: https://cloud.githubusercontent.com/assets/619340/20454350/d1c8861e-ae3e-11e6-9de6-501f07a58606.png + + +- **outputs**:: + + direction socketname sockettype + out verts v + +- `direction` ``out`` means "make an output". +- `socketname` means "give this socket a name / identifier" +- `sockettype` declares what kind of socket: Vertices (v), Strings/Lists (s), Matrices (m), Objects (o) + + There's no _default_ or _nested_ value for output sockets, generally speaking the default inputs will suffice to generate a default outputs. + +Learn by example, the best way to get a feel for what works and doesn't is to have a look at the existing examples in several places: + + - this thread: https://github.com/nortikin/sverchok/issues/942 + - in ``node_scripts/SNLite_templates`` + - the ``draw_buttons_ext`` (Right side panel of the NodeView -> Properties) + +The templates don't have much defensive code, and some nodes that expect input +will turn _red_ until they get input via a socket. You can add code to defend +against this, but I find it useful to be notified quickly if the input is +unexpected, the node will gracefully fail. + +Inputs / Outputs +---------------- + +All inputs and outputs of this node are defined in the script. + +Parameters +---------- + +This node has two states: + +1. When no script is loaded, it shows: + * a drop-down box, where you have to select a Blender's text block with script text; + * and a "Plug" button. + + When you select the script and press "Plug", the script is loaded, and the node changes it's appearance. + + +2. When a script is loaded, the node displays all inputs and parameters defined by the script; Additionally, the following buttons are shown: + + * **Animate Node**. When checked, the node is updated during animation playback, on each frame change event. + * **Update Node**. Click this to manually trigger execution of the node. + * **Reload**. Click this to parse and load the script text again - this makes sense if you've changed the script. + * **Clear**. Reset the node to the state when no script was loaded, so you will be able to select another script. + +Examples of usage +----------------- + +Please refer to the initial thread: https://github.com/nortikin/sverchok/issues/942. + +In the N panel of the node there is a drop-down menu allowing you to select one +of example scripts which are distributed with Sverchok. -https://github.com/nortikin/sverchok/issues/942 -- GitLab From 5a44aa977c48cc607e925c6285e53d619c74948d Mon Sep 17 00:00:00 2001 From: Ilya Portnov Date: Sat, 9 May 2020 18:00:06 +0500 Subject: [PATCH 3/4] Minor update in the documentation [skip ci] --- docs/nodes/generator/script1_lite.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/nodes/generator/script1_lite.rst b/docs/nodes/generator/script1_lite.rst index 02b60a9c1..cb7bcdd1a 100644 --- a/docs/nodes/generator/script1_lite.rst +++ b/docs/nodes/generator/script1_lite.rst @@ -111,10 +111,8 @@ A few things to notice: - **inputs**:: - ``` direction socketname sockettype default nestedness in radius s .=1.2 .=2 - ``` - `direction` ``in`` means "make an input". - `socketname` means "give this socket a name / identifier" @@ -182,6 +180,7 @@ Parameters This node has two states: 1. When no script is loaded, it shows: + * a drop-down box, where you have to select a Blender's text block with script text; * and a "Plug" button. -- GitLab From 738ac69e0c5b28739504b7489551bf89eef133f3 Mon Sep 17 00:00:00 2001 From: Ilya Portnov Date: Sat, 9 May 2020 18:24:47 +0500 Subject: [PATCH 4/4] Add an example of custom Scalar Field generation. --- .../demo/erdos_scalar_field.py | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 node_scripts/SNLite_templates/demo/erdos_scalar_field.py diff --git a/node_scripts/SNLite_templates/demo/erdos_scalar_field.py b/node_scripts/SNLite_templates/demo/erdos_scalar_field.py new file mode 100644 index 000000000..5dab532c9 --- /dev/null +++ b/node_scripts/SNLite_templates/demo/erdos_scalar_field.py @@ -0,0 +1,69 @@ +""" +in h_in s +out field_out SF +""" + +# This script shows an example of generating a custom Scalar Field object. +# This may be useful when the formula defining the field is too complex +# to be typed in the "Scalar Field Formula" node. + +import math +import numpy as np + +from sverchok.utils.field.scalar import SvScalarField + +# You can implement some auxiliary funcitons +# to be used in your scalar field definition. +def erdos(z, n): + return abs(z**n - 1)**2 - 1 + +def lemn3(x, y): + return erdos(x + y*1j, 3) + +# To make your own scalar field, you have to +# subclass a SvScalarField class. +# +# You have to define two methods in it: evaluate and evaluate_grid. +# +class MyField(SvScalarField): + def __init__(self, h): + # the field can have any parameters you want + self.h = h + + def evaluate(self, x, y, z): + # This method must calculate one real value for given x,y and z coordinates. + # If you've already implemented evalaute_grid, you may simply write: + # + # def evaluate(self, x, y, z): + # return self.evaluate_grid(np.array([x]), np.array([y]), np.array([z]))[0] + # + # but employing all the numpy's vectorization magic for one single point + # may have some performance overhead. + w = x + y*1j + h = self.h + return lemn3(x,y)**2 + (16*abs(w)**4 + 1)*(z*z - h*h) + + def evaluate_grid(self, xs, ys, zs): + # This method must calculate a numpy array of shape (n,) + # given three numpy arrays xs, ys and zs of the same shape. + # If you've already implemented evaluate() method, and do not + # want to dig into numpy magic, or you do not care about + # performance, or your algorithm is just impossible to vectorize, + # you can write simply: + # + # def evaluate_grid(self, xs, ys, zs): + # return np.vectorize(self.evaluate, signature='(),(),()->()`)(xs, ys, zs) + # + ws = xs + ys*1j + h = self.h + return lemn3(xs,ys)**2 + (16*abs(ws)**4 + 1)*(zs*zs - h*h) + +# And now you return an instance of your class for each set of the input parameters. + +field_out = [] +if h_in: + for hs in h_in: + for h in hs: + field = MyField(h) + field_out.append(field) + -- GitLab