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

ScriptNode1 - Lite #942

Closed
zeffii opened this issue Oct 31, 2016 · 68 comments

Comments

Projects
4 participants
@zeffii
Copy link
Collaborator

commented Oct 31, 2016

todo (in no particular order)

  • operator function requires a docstring as first line, else the line count (for hoisting) isn't correct. i'd like to fix that but it's proven a bit tricky. (or i've had insufficient time to tackle it)

  • min/max on float/int sockets

  • add bgl refresh if imported:

    def free(self):
        v3dBGL.callback_disable(node_id(self))
    
    # reset n_id on copy
    def copy(self, node):
        self.n_id = ''
    

SNLite Documentation

image

Script Node Lite

The script node implementation is intended for quick, non serious, experimentation. It can do many of the things ScriptNode MK1 can do, but it currently lacks support for a few things -- these are listed in the issues section. Unlike SN MK3 this implementation has no illusions of becoming a first class citizen of the node tree, there's a bit of overhead per update.

Justification

I still like SN1 a lot, but I don't like adding all the unwrapping and indexing code just to get the node to preprocess/precompile -- that was never part of the vision. A coders perogative is to make their life as easy as possible. Sometimes that means spending a bit of time making tools. SNlite isn't perfect but i'll write scripts with this until SN MK3 has had time to mature.

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

  • 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 #942 (comment) for an example use. Usually you'll use the 'vectorize' function with this to zip through each pair of arguments. see #942 (comment)

  • 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 #942 (comment)

  • added an include directive

    """
    ...
    include <your_text_name>
    """

    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: #942 (comment)

    """
    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:

      • Vertices (v)
      • Strings/Lists (s)
      • Matrices (m)
      • 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

      • 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
  • 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: #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.


I don't want to get in the way of SN MK3, but I do want to make another attempt at a simpler SN, this time more like the do with x feature in EvalKnieval (RIP).

allowing scripts to be written like this:

"""
in verts v .=[[]] n=1
in radius s .=0.3 n=2
out edges s
"""

import mathutils

# just use first list?
size = len(verts)
kd = mathutils.kdtree.KDTree(size)
for i, xyz in enumerate(verts):
    kd.insert(xyz, i)
kd.balance()

edges = [[]]
edge_set = set()
r = radius
for idx, vtx in enumerate(verts):
    n_list = kd.find_range(vtx, r)
    for co, index, dist in n_list:
        if index == idx:
            continue
        edge_set.add(tuple(sorted([idx, index])))

edges[0] = list(edge_set)

@zeffii zeffii self-assigned this Oct 31, 2016

@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Nov 2, 2016

now it reads

"""
in   n_petals s        default=8       nested=2
in   vp_petal s        default=10      nested=2
in   profile_radius s  ;=2.3           n=2
in   amp s             default=1.0     nested=2
in   origin v          defautt=(0,0,0) n=2
out  verts v
out  edges s
"""

from math import sin, cos, radians, pi
from mathutils import Vector, Euler

# create flower
n_verts = n_petals * vp_petal
section_angle = 360.0 / n_verts
position = (2 * (pi / (n_verts / n_petals)))
x, y, z = origin[:3]

Verts = []
Edges = []

for i in range(n_verts):
    difference = amp * cos(i * position)
    arm = profile_radius + difference
    ampline = Vector((arm, 0.0, 0.0))

    rad_angle = radians(section_angle * i)
    myEuler = Euler((0.0, 0.0, rad_angle), 'XYZ')
    ampline.rotate(myEuler)
    Verts.append((ampline.x+x, ampline.y+y, 0.0+z))

# makes edge keys, ensure cyclic
if Verts:
    i = 0
    Edges.extend([[i, i + 1] for i in range(n_verts - 1)])
    Edges.append([len(Edges), 0])

verts = [Verts]
edges = [Edges]
@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Nov 4, 2016

inscribing a quad using bezier_lerp

image

"""
in   quads   v   .=[]   n=0
in   points  s   .=4    n=2
out  verts   v
out  edges   s
"""

print('---')

from mathutils.geometry import interpolate_bezier as bezlerp
from mathutils import Vector, Euler

Verts = []
Edges = []

def mid(A, B):
    return (A[0]+B[0])/2, (A[1]+B[1])/2, (A[2]+B[2])/2

for quad in quads:
    if len(quad) == 4:
        sverts, sedges = [], []
        for s in range(4):

            knot1 = mid(quad[s%4], quad[(s+1)%4])
            ctrl_1 = mid(knot1, quad[(s+1)%4])
            knot2 =  mid(quad[(s+1)%4], quad[(s+2)%4])       
            ctrl_2 = mid(knot2, quad[(s+1)%4])

            arc_verts = bezlerp(knot1, ctrl_1, ctrl_2, knot2, max(3, points))
            sverts.extend([v[:] for v in arc_verts[:-1]])

        arc_edges = [(n, n+1) for n in range(len(sverts)-1)]
        arc_edges.append([len(sverts)-1, 0])
        Edges.append([arc_edges])
        Verts.append([sverts])

verts = [Verts]
edges = [Edges]

no point sharing a gist, iosjon support isn't added yet

image

@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Nov 6, 2016

I needed to call exec(self.script_str, locals(), locals()) as per http://stackoverflow.com/a/29979633/1243487

@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Nov 13, 2016

another example, tho possible this working code can be cleaned up a lot

"""
in normals v .=[] .=0
in coords v .=[] .=0
in radius s .=0.4 .=2
in num_tries s .=100 .=2
out new_verts v
"""

# taken from http://stackoverflow.com/a/29354206/1243487
# translated to python and modified for sverchok.

from math import sqrt, cos, sin, pi as M_PI
from random import uniform as rand


def crossp(u, v):
    w = [0, 0, 0]
    w[0] = (u[1] * v[2]) - (u[2] * v[1])
    w[1] = (u[2] * v[0]) - (u[0] * v[2])
    w[2] = (u[0] * v[1]) - (u[1] * v[0])
    return w  # vec3

def dotp(u, v):
    return (u[0] * v[0]) + (u[1] * v[1]) + (u[2] * v[2])  # float

def norm2(u):
    return dotp(u, u)  # float

def norm(u):
    return sqrt(norm2(u))  # float

def scale(u, s):
    return [u[0] * s, u[1] * s, u[2] * s]  # vec3

def add(u, v):
    return [u[0] + v[0], u[1] + v[1], u[2] + v[2]] # vec3

def normalize(u):
    return scale(u, 1/norm(u)) # vec3


def random_on_plane(r, n, co):
    """
    generates a random point on the plane ax + by + cz = d
    """
    n = list(n)
    co = list(co)
    d = dotp(n, co)
    xorz = [1, 0, 0] if (n[0] == 0) else [0, 0, 1]
    w = crossp(n, xorz)

    theta = rand(0, 1) * 2 * M_PI
    k = normalize(n)
    w = add(scale(w, cos(theta)), 
            scale(crossp(k, w), sin(theta)))

    if r == 0:
        w = scale(w, r/norm(w))
    else:
        rand_r = rand(0, 1) * r
        w = scale(w, rand_r/norm(w))

    if d != 0:
        t = scale(n, d / norm2(n))  # vec3
        w = add(w, t)

    return w


new_verts = []
for nlist, colist in zip(normals, coords):
    sublist = []
    for n, co in zip(nlist, colist):
        sublist.append([random_on_plane(radius, n, co) for _ in range(num_tries)])
    new_verts.append(sublist)

new_verts = [new_verts]

image

to think about:

@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Nov 19, 2016

reeegif

@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Nov 19, 2016

"""
in num_items s d=200 .=2
in radius s d=1.0 .=2
out verts v
"""
from random import random as rand
from math import sqrt, cos, sin, pi as M_PI

verts = []
for i in range(num_items):
    theta = rand() * 2 * M_PI
    r = rand() * radius
    verts.append((cos(theta)*sqrt(r), sin(theta)*sqrt(r), 0.0))

verts = [verts]

a reasonably homogenous point picking
image

@zeffii zeffii referenced this issue Nov 19, 2016

Merged

Snlite plus #971

@zeffii zeffii closed this Nov 19, 2016

@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Nov 19, 2016

radius reject, kdtree

image

"""
in num_items s d=580 .=2
in radius s d=65.0 .=2
in reject_radius s d=0.23 n=2
out verts v
"""
from random import random as rand
from math import sqrt, cos, sin, pi as M_PI
from mathutils.kdtree import KDTree

verts = []
prelim_verts = []

for i in range(num_items):
    theta = rand() * 2 * M_PI
    r = rand() * radius
    prelim_verts.append((cos(theta)*sqrt(r), sin(theta)*sqrt(r), 0.0))

size = len(prelim_verts)
kd = KDTree(size)
for i, xyz in enumerate(prelim_verts):
    kd.insert(xyz, i)
kd.balance()

for idx, vtx in enumerate(prelim_verts):
    n_list = kd.find_range(vtx, reject_radius)
    # for co, index, dist in n_list:
    if not n_list or len(n_list) == 1:
        verts.append(vtx)

verts = [verts]

someone should come up with a smarter algorithm :)

@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Nov 20, 2016

added snlite_utils for useful functors

"""
in floats_in s .=[] n=0.0
out floats_out s
ui = my_temp_material, RGB Curves
"""

import bpy

from sverchok.utils.snlite_utils import get_valid_evaluate_function as get_evaluator

evaluate = get_evaluator('my_temp_material', 'RGB Curves')

def ui(self, context, layout):
    m = bpy.data.materials.get('my_temp_material')
    if not m:
        return
    tnode = m.node_tree.nodes['RGB Curves']
    layout.template_curve_mapping(tnode, "mapping", type="NONE")


floats_out = []
for flist in floats_in:
    floats_out.append([evaluate(v) for v in flist])

The drawback is that this doesn't store the points... (yet)

@zeffii zeffii referenced this issue Nov 21, 2016

Closed

New Todo #914

17 of 18 tasks complete

@zeffii zeffii reopened this Nov 22, 2016

@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Nov 22, 2016

curvemap points for rgbcurve ui element now stored in IOJson. probably implement import tomorrow..

@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Nov 23, 2016

in the current branch, i can import this gist and it will generate the snlite with RGBCurve ui element :)

image

https://gist.github.com/59671db75f62361af3242da23275df78

"""
in floats_in s .=[] n=0.0
out floats_out s
ui = my_temp_material, RGB Curves
"""

import bpy

from sverchok.utils.snlite_utils import get_valid_evaluate_function as get_evaluator

# currently the node noame (here 'RGB Curves') must be uniqe per material.
# copying nodes within the same nodetree  does not automatically 'bump' the nodename
# to the next available. You must be aware of this current implementation limitation.
evaluate = get_evaluator('my_temp_material', 'RGB Curves')

def ui(self, context, layout):
    m = bpy.data.materials.get('my_temp_material')
    if not m:
        return
    tnode = m.node_tree.nodes['RGB Curves']
    layout.template_curve_mapping(tnode, "mapping", type="NONE")


floats_out = []
for flist in floats_in:
    floats_out.append([evaluate(v) for v in flist])
@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Nov 23, 2016

nice for fast prototyping..

"""
in verts_lists v d=[] n=1
"""

import numpy as np
import random
import math


class MBoundingBox:

    def __init__(self):
        self.xyz = None

    def add_all(self, vectors):
        self.xyz = np.array(vectors)

        x = self.xyz[:,0]
        y = self.xyz[:,1]
        z = self.xyz[:,2]

        self.width = max(x) - min(x)
        self.depth = max(y) - min(y)
        self.height = max(z) - min(z)
        self.center = (
            min(x) + (self.width / 2),
            min(y) + (self.depth / 2),
            min(z) + (self.height / 2)
        )

bb = MBoundingBox()
print(verts_lists)
bb.add_all(verts_lists)
print(bb.width, bb.depth, bb.height, bb.center)

image

@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Nov 24, 2016

image

"""
in objs_in o
out floats_out s
"""

import bpy
from bl_ui.properties_data_modifier import DATA_PT_modifiers

ob_in = objs_in[0]


def ui(self, context, layout):
    ob = ob_in
    if ob:
        mp = DATA_PT_modifiers(context)
        row = layout.row()

        if not ob.modifiers:
            row.label('No modifiers')
        else:
            for md in ob.modifiers:
                box = layout.template_modifier(md)
                if box:
                    getattr(mp, md.type)(box, ob, md)        


floats_out = []

it's a little exploratory

@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Nov 24, 2016

not possible to add modifiers yet, just a matter of adding that kind of code to the UI part..
image

for such a node to work (outside of snlite) - i think we'd need to do checks for

    obj =  ( some object list in ) [0]    # picking first
    if obj and obj == bpy.context.active_objects:
        ... show the add new modifier button
        ... show current modfier set
    else:
        ... show a button to make that object active.
        ... show message about the object not being active.

Else we end up having to copy all the modifier ui code (for each modifier ) into our code..

@nortikin

This comment has been minimized.

Copy link
Owner

commented Nov 24, 2016

О_о

@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Nov 25, 2016

image

"""
in xdim s d=3 n=2
in ydim s d=3 n=2
in zdim s d=3 n=2
in size s d=1.0 n=2
out verts v
"""

import numpy as np

verts = []
hs = size/2
x_ = np.linspace(-hs, hs, xdim)
y_ = np.linspace(-hs, hs, ydim)
z_ = np.linspace(-hs, hs, zdim)
f = np.vstack(np.meshgrid(x_,y_,z_)).reshape(3,-1).T
f = f.tolist()
verts.append(f)
@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Nov 25, 2016

3dgrid with xyz shift

"""
in xdim s d=9 n=2
in ydim s d=9 n=2
in zdim s d=9 n=2
in size s d=1.0 n=2
in xshift s d=0 n=2
in yshift s d=0 n=2
in zshift s d=0 n=2
out verts v
"""

import numpy as np

verts = []
hs = size/2
x_ = np.linspace(-hs, hs, xdim)
y_ = np.linspace(-hs, hs, ydim)
z_ = np.linspace(-hs, hs, zdim)
xshift = xshift % xdim
yshift = yshift % ydim
zshift = zshift % zdim
print(xshift, yshift, zshift)

if xshift > 0:
    x_ = np.roll(x_, xshift)
if yshift > 0:
    y_ = np.roll(y_, yshift)
if zshift > 0:
    z_ = np.roll(z_, zshift)        

f = np.vstack(np.meshgrid(x_,y_,z_)).reshape(3,-1).T
f = f.tolist()
verts.append(f)

https://gist.github.com/121b8699c87e426c7c84ad8c756fd930

@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 2, 2016

evenly spread a circle of points

"""
in num_items s d=580 .=2
in radius s d=65.0 .=2
in reject_radius s d=0.23 n=2
out verts v
"""
from random import random as rand
from math import sqrt, cos, sin, pi as M_PI
import bmesh

verts = []
prelim_verts = []

for i in range(num_items):
    theta = rand() * 2 * M_PI
    r = rand() * radius
    prelim_verts.append((cos(theta)*sqrt(r), sin(theta)*sqrt(r), 0.0))

def remove_doubles(vertices, d):
    bm = bmesh.new()
    bm_verts = [bm.verts.new(v) for v in vertices]

    bmesh.ops.remove_doubles(bm, verts=bm_verts, dist=d)
    bm.verts.index_update()

    verts = [vert.co[:] for vert in bm.verts[:]]
    bm.clear()
    bm.free()
    return verts

verts = [remove_doubles(prelim_verts, reject_radius)]

image

@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 2, 2016

https://gist.github.com/8aa8eebda4006e47aab1fcdd4a1d6ac8

using @elfnor 's script for SN1

image

hmm.. not sure why that doesnt import the snlite script, but does import the sn1..

https://gist.github.com/18fbb7cd1cf6b1d35568a0a38dbf51b7 ?
https://gist.github.com/3f84f2e35aab21b40fe28e94c222d7ed

@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 4, 2016

  1. a new directive inject will set node.inject_params to True if encountered, else defaults to False. So no more using the draw_buttons_ext..
  2. note the vectorize function is pre injected into the local namespace for the function.
"""
in petals       s d=8    n=1 
in innerradius  s d=0.5  n=1 
in outerradius  s d=1.0  n=1
in petalwidth   s d=2.0  n=1
out points      v
out faces       s
inject
"""

from add_curve_extra_objects.add_curve_aceous_galore import FlowerCurve

params = vectorize(parameters)
points = [FlowerCurve(*param_set) for param_set in zip(*params)]
faces = [[list(range(len(n)))] for n in points]
@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 10, 2016

i'll add another directive to SNLite header include <textblockname.py> ( doesn't have to end in .py, but it does have to end in .py if you want to import it as a python module )
so we can load more complex scriptnodes, and keep the main snlite script more abstract.

chop_module.py

import bmesh
from sverchok.utils.sv_bmesh_utils import pydata_from_bmesh, bmesh_from_pydata

def average(verts):
    vx, vy, vz = 0, 0, 0
    for v in verts:
        vx += v[0]
        vy += v[1]
        vz += v[2]
    numverts = len(verts) 
    return vx/numverts, vy/numverts, vz/numverts


def chop(mesh, normal, point):
    '''
    not an optimal implementation, but simple enough to be educational.s

    input:
        takes a (what it assumes is) a closed mesh as input
    outputs: 
        a collection of vert, edge, face lists, that hold 2 sublists each
        one for each section / bisection.

    '''
    verts, edges, faces = mesh
    verts_list, edges_list, faces_list = [], [], []

    def inject_mesh(bm):
        v, e, f = pydata_from_bmesh(bm)
        verts_list.append(v)
        edges_list.append(e)
        faces_list.append(f)

    def wrev(n):
        d = n[0]*2, n[1]*2, n[2]*2
        return n[0]-d[0], n[1]-d[1], n[2]-d[2]

    for n in [normal, wrev(normal)]:
        bm = bmesh_from_pydata(verts, edges, faces)
        geom_in = bm.verts[:] + bm.edges[:] + bm.faces[:]
        res = bmesh.ops.bisect_plane(
            bm, geom=geom_in, dist=0.00001,
            plane_co=point, plane_no=n, use_snap_center=False,
            clear_outer=True, clear_inner=False
        )

        # lifted from bisect
        surround = [e for e in res['geom_cut'] if isinstance(e, bmesh.types.BMEdge)]
        fres = bmesh.ops.edgenet_prepare(bm, edges=surround)
        bmesh.ops.edgeloop_fill(bm, edges=fres['edges'])        

        inject_mesh(bm)
        del bm

    return verts_list, edges_list, faces_list

and then the sn

"""
in verts v  d=[[]] .=1
in faces s  d=[[]] .=1
in seed  s  d=0    .=2
out verts_out v
out faces_out s
include <chop_module.py>       # <-- to store it in the gist/json. 
"""
import random
from random import random as rnd

from chop_module import chop, average

verts_out = []
faces_out = []


mesh = verts, [], faces
random.seed(seed)
normal = rnd(), rnd(), rnd()
point = average(verts)
verts_out, _, faces_out = chop(mesh, normal, point) 
@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 10, 2016

cross testing lowlevel matrix*vertlist code

"""
in verts v d=[[]] /=1
in matx  m d=[[]] /=1
out vout v
"""

vout = [[]]

def multiply_vectors(M, vlist):

    for i, v in enumerate(vlist):
        vlist[i] = (
            M[0][0]*v[0] + M[0][1]*v[1] + M[0][2]*v[2] + M[0][3]* 1.0,
            M[1][0]*v[0] + M[1][1]*v[1] + M[1][2]*v[2] + M[1][3]* 1.0, 
            M[2][0]*v[0] + M[2][1]*v[1] + M[2][2]*v[2] + M[2][3]* 1.0
        )

    return vlist

vout[0] = multiply_vectors(matx, verts)
@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Apr 25, 2017

calc_total_edge_len.py
"""
in verts_in v
in edges_in s
in matrices_in m
out combo_len s
"""

from mathutils import Matrix
from sverchok.utils.sv_bmesh_utils import bmesh_from_pydata


for idx, (verts, edges) in enumerate(zip(verts_in, edges_in)):

    bm = bmesh_from_pydata(verts, edges, [])
    calced_len = 0.0
    for e in bm.edges:
        calced_len += e.calc_length()

    if self.inputs['matrices_in'].is_linked:
        good_index = min(len(matrices_in), idx)
        matrix = matrices_in[good_index]
        scale = Matrix(matrix).to_scale().length / 1.7320508
        calced_len *= scale
     
    combo_len.append([calced_len])
@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Apr 25, 2017

this may expose a slight bug in Float Range node..
image

@enzyme69

This comment has been minimized.

Copy link
Collaborator

commented Apr 25, 2017

screen shot 2017-04-25 at 22 44 53

The length thing is so useful!

@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Apr 25, 2017

I'll make it so it does not need edge input. Assume verts is a sequence if edges aren't supplied.

@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Apr 25, 2017

path_length.py
"""
in verts_in v
in edges_in s
in matrices_in m
out combo_len s
"""

import math
from mathutils import Matrix
from sverchok.utils.sv_bmesh_utils import bmesh_from_pydata


matrices_linked = self.inputs['matrices_in'].is_linked
verts_linked = self.inputs['verts_in'].is_linked
edges_linked = self.inputs['edges_in'].is_linked

def get_scale_factor(idx):
    if matrices_linked:
        good_index = min(len(matrices_in), idx)
        matrix = matrices_in[good_index]
        Matrix(matrix).to_scale().length / 1.7320508
        return scale
    return 1.0


if verts_linked and edges_linked:

    for idx, (verts, edges) in enumerate(zip(verts_in, edges_in)):

        bm = bmesh_from_pydata(verts, edges, [])
        calced_len = 0.0
        for e in bm.edges:
            calced_len += e.calc_length()

        scale = get_scale_factor(idx)
        calced_len *= scale
        
        combo_len.append(calced_len)

elif verts_linked:
    for idx, verts in enumerate(verts_in):
        calced_len = 0.0
        for v_idx in range(len(verts)-1):
            vert_a, vert_b = verts[v_idx], verts[v_idx+1]
            v = vert_a[0]-vert_b[0], vert_a[1]-vert_b[1], vert_a[2]-vert_b[2]
            distance = math.sqrt((v[0] * v[0]) + (v[1] * v[1]) + (v[2] * v[2]))
            calced_len += distance

        scale = get_scale_factor(idx)
        calced_len *= scale
        
        combo_len.append(calced_len)

combo_len = [combo_len]
@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Apr 27, 2017

image

@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented May 8, 2017

added ddir

"""
in objects_in o d=[] n=0
"""

objs = objects_in
if objs and len(objs):
    pass
else:
    obj_name = self.inputs['objects_in'].object_ref
    objs = [bpy.data.objects[obj_name]]

print(ddir(objs[0], filter_str='matrix'))
@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented May 8, 2017

added custom enum

"""
in spook v
enum = one two three
"""

def ui(self, context, layout):
    layout.prop(self, 'custom_enum', expand=True)

print(self.custom_enum)

image

@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented May 9, 2017

now callback is possible.. not exactly convenient yet.. but it's possible :)

image

"""
in verts v
"""


def my_operator(self, context):
    print(self, context, self.inputs['verts'].sv_get())

ND = self.node_dict.get(hash(self))               #
if ND:                                            #
    callbacks = ND['sockets']['callbacks']        #    this will be hidden in the node eventually 
    if not 'my_operator' in callbacks:            #
        callbacks['my_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'
@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented May 9, 2017

this is now possible..

"""
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'
@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented May 9, 2017

image

"""
in objects_in o
in rnd_select s d=[] n=1
"""


def my_operator(self, context):
    import bpy
    obj_name = self.inputs['objects_in'].sv_get()[0].name
    obj = bpy.data.objects[obj_name]
    
    rnd_select = self.inputs['rnd_select'].sv_get()[0]
    for idx, poly in enumerate(obj.data.polygons):
        poly.select = rnd_select[idx] > 0.5
    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'

a litte verbose ...but whatever

@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented May 21, 2017

Selective Vertex Bevel

click details for code

"""
in verts v
in faces s
in offset s d=0.0 n=2
in segments s d=1 n=2
in profile s d=.5 m=2
in clamp_overlap s d=0 n=2
out verts2 v
out faces2 s
"""


import bmesh
import random

bevel_enum = """
	BEVEL_AMT_OFFSET,
	BEVEL_AMT_WIDTH,
	BEVEL_AMT_DEPTH,
	BEVEL_AMT_PERCENT
"""


rnd = random.random

try:
    segments = abs(segments)
    segments = min(segments, 3)
    profile = abs(profile)
    clamp_overlap = abs(clamp_overlap)
    for v, f in zip(verts, faces):
        bm = bmesh_from_pydata(v, [], f, normal_update=True)
        geom = [vert for vert in bm.verts if rnd()>0.5]
        bmesh.ops.bevel(
           bm, geom=geom, 
           offset=offset, offset_type=4, 
           segments=segments, profile=profile, 
           vertex_only=True, clamp_overlap=clamp_overlap)
        v2, _, f2 = pydata_from_bmesh(bm)
        
        verts2.append(v2)
        faces2.append(f2)
except Exception as err:
    print(repr(err))
    ...

should show this on a plane

image

if using this inside a monad-loop , limit the segments to something sane..probably 5

@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented May 30, 2017

an interesting faux-voronoi
https://gist.github.com/8cba469a1eb1c8ae2a295dc27e5fbc89

image

code snippet

"""
in verts_in v d=[] n=1
in distor s d=2.0 n=2
in sidor s d=7 n=2
out verts_out v
out edges s
out new_verts v
out new_faces s
"""

import bmesh
import mathutils
from mathutils import Vector
from collections import defaultdict

def cv_hull(coords):
    bm = bmesh.new()
    bm_verts = [bm.verts.new(v) for v in coords]
    bmesh.ops.convex_hull(bm, input=bm_verts, use_existing_faces=False)
    verts, _, faces = pydata_from_bmesh(bm)
    return verts, faces

size = len(verts_in)
kd = mathutils.kdtree.KDTree(size)
for i, xyz in enumerate(verts_in):
    kd.insert(xyz, i)
kd.balance()

edges = [[]]
edge_set = set()


locators = defaultdict(list)

for idx, vtx in enumerate(verts_in):
    n_list = kd.find_n(vtx, sidor)
    for co, index, dist in n_list:
        if index == idx:
            continue
        # edge_set.add(tuple(sorted([idx, index])))
        # locators[idx].append(co, index, dist)
        locators[idx].append((Vector(co)-Vector(vtx))/distor + Vector(vtx))

edges[0] = list(edge_set)

for k, v in locators.items():
    ___v, ___f = cv_hull(v)
    new_verts.append(___v)
    new_faces.append(___f)
@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Jun 4, 2017

increasingly i'm getting pissed off about snlite lack of feature to set the min/max of sliders. this might have to change soon.

@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Jun 30, 2017

Repository owner deleted a comment from enzyme69 Jun 30, 2017

@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Jul 26, 2017

Diamond Lattice

warning to self: R and rSphere are overwritten inside the code, slider values ignored.

"""
in R        s  d=0.5    n=2
in rSphere  s  d=0.975  n=2
out verts   v
"""

# liberated from 
# http://www.semifluid.com/2013/10/05/preparing-a-blobby-object-for-printing-with-shapeways/

import math

def drange(start, stop, step):
    r = start
    while r <= stop:
        yield r
        r += step

r = 0.25;
rSphere = r*0.975;


verts.append([])
add_vert = verts[0].append

for x in drange(-0.75/r, 0.75/r, 1):
    for y in drange(-0.75/r, 0.75/r, 1):
        for z in drange(-0.75/r, 0.75/r, 1):
            pt = (x*2*r - (y%2*r) - (z%2*r),
                      r + (math.sqrt(3)*r*y) + (z%2*r)/2,
                      r + (math.sqrt(6)*2/3*r*z));
            add_vert(pt)

image

@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Jul 26, 2017

closing thread, it has served my needs. It got long, i might open a new one for more examples. a part two.

@zeffii zeffii closed this Jul 26, 2017

@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Sep 5, 2017

here'a a statefull example: (much like setup for processing) :
#1734 (comment)

Note to self and you, the setup function is a bit sensitive to whitespace it really does expect a space / docstring/ space between the setup line..and the first code line inside setup. If you find it doesn't work, but are convinced it should.. then you should follow the example code structure for the setup function.

@DolphinDream

This comment has been minimized.

Copy link
Collaborator

commented Dec 15, 2017

Is Scripted Node Lite the newst script node ? I’m also looking for documentation. is this issue thread a good starting point ?

@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 15, 2017

yes to all

@zeffii

This comment has been minimized.

Copy link
Collaborator Author

commented Feb 24, 2018

bevelling edgenets at predetermined vertices.

image

"""
in verts v
in edges s
in offset s d=0.0 n=2
in segments s d=1 n=2
in profile s d=.5 m=2
in clamp_overlap s d=0 n=2
out verts2 v
out edges2 s
"""


import bmesh
import random

bevel_enum = """
    BEVEL_AMT_OFFSET,
    BEVEL_AMT_WIDTH,
    BEVEL_AMT_DEPTH,
    BEVEL_AMT_PERCENT
"""


rnd = random.random

try:
    segments = abs(segments)
    segments = min(segments, 5)
    profile = abs(profile)
    clamp_overlap = abs(clamp_overlap)
    for v, e in zip(verts, edges):
        bm = bmesh_from_pydata(v, e, [], normal_update=True)
        geom = [v for v in bm.verts if v.index==1]
        bmesh.ops.bevel(
           bm, geom=geom, 
           offset=offset, offset_type=4, 
           segments=segments, profile=profile, 
           vertex_only=True, clamp_overlap=clamp_overlap)
        v2, e2, _ = pydata_from_bmesh(bm)
        
        verts2.append(v2)
        edges2.append(e2)

except Exception as err:
    print(repr(err))
    ...

image

poooi2_2018_02_24_11_13.zip

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.