From ff2d54611c0353dea1a584af2f2a7ac2512bcbe4 Mon Sep 17 00:00:00 2001 From: satabol Date: Tue, 10 Oct 2023 22:57:11 +0300 Subject: [PATCH] Performance some generators: - generator/circle.py - generator/ngon.py - generator/sphere.py - generator/torus_mk2.py - generators_extended/ellipse_mk3.py - generators_extended/hilbert3d.py - generators_extended/ring_mk2.py - generators_extended/spiral_mk2.py - generators_extended/super_ellipsoid.py - generators_extended/torus_knot_mk2.py --- nodes/generator/circle.py | 23 +- nodes/generator/ngon.py | 94 +++--- nodes/generator/sphere.py | 111 ++++--- nodes/generator/torus_mk2.py | 174 +++++----- nodes/generators_extended/ellipse_mk3.py | 32 +- nodes/generators_extended/hilbert3d.py | 6 +- nodes/generators_extended/ring_mk2.py | 130 ++++---- nodes/generators_extended/spiral_mk2.py | 318 ++++++++++--------- nodes/generators_extended/super_ellipsoid.py | 164 +++++++--- nodes/generators_extended/torus_knot_mk2.py | 125 ++++---- 10 files changed, 644 insertions(+), 533 deletions(-) diff --git a/nodes/generator/circle.py b/nodes/generator/circle.py index b115dc9f53..d6a74f7c78 100644 --- a/nodes/generator/circle.py +++ b/nodes/generator/circle.py @@ -24,7 +24,6 @@ from sverchok.node_tree import SverchCustomTreeNode from sverchok.data_structure import (fullList, match_long_repeat, updateNode) import numpy as np -from datetime import datetime class SvCircleNode(SverchCustomTreeNode, bpy.types.Node): @@ -62,8 +61,8 @@ def make_verts(self, Angle, Vertices, Radius): else: theta = Angle/Vertices - steps = np.arange(Vertices, dtype=np.int32) - _theta = steps*theta + _n1 = np.arange(Vertices, dtype=np.int32) + _theta = _n1*theta _x = Radius * np.cos(np.radians(_theta)) _y = Radius * np.sin(np.radians(_theta)) @@ -82,19 +81,19 @@ def make_verts(self, Angle, Vertices, Radius): def make_edges(self, Angle, Vertices): - steps = np.arange(Vertices, dtype=np.int32) - arr_edges = np.zeros((Vertices-1, 2), 'i' ) - arr_edges[:,0] = steps[:-1] - arr_edges[:,1] = steps[1:] + _n = np.arange(Vertices, dtype=np.int32) + _edges = np.column_stack( (_n[:-1], _n[1:]) ) if Angle < 360 and self.mode_ == 1: - arr_edges = np.vstack( (arr_edges, (Vertices-1, Vertices)) ) - arr_edges = np.vstack( (arr_edges, (Vertices, 0 )) ) + # Close circle like Packman (throw center of circle) + _edges = np.vstack( (_edges, (Vertices-1, Vertices) )) + _edges = np.vstack( (_edges, (Vertices , 0) )) else: - arr_edges = np.vstack( (arr_edges, (Vertices-1, 0)) ) + # Close circle from last point to first point + _edges = np.vstack( (_edges, (Vertices-1, 0))) - _listEdg = arr_edges.tolist() - return _listEdg + _list_edges = _edges.tolist() + return _list_edges def make_faces(self, Angle, Vertices): diff --git a/nodes/generator/ngon.py b/nodes/generator/ngon.py index ca9b2d7dc2..354d9771ff 100644 --- a/nodes/generator/ngon.py +++ b/nodes/generator/ngon.py @@ -21,6 +21,7 @@ import bpy from bpy.props import BoolProperty, IntProperty, FloatProperty, EnumProperty +import numpy as np from sverchok.node_tree import SverchCustomTreeNode from sverchok.data_structure import (updateNode, rotate_list, list_match_modes, list_match_func) @@ -28,55 +29,62 @@ def make_verts(nsides, radius, rand_r, rand_phi, rand_seed, divs): if rand_r or rand_phi: random.seed(rand_seed) - - vertices = [] dphi = (2*pi)/nsides - prev_vertex = None - first_vertex = None - for i in range(nsides): - phi = dphi * i - # randomize radius if necessary - if not rand_r: - rr = radius - else: - rr = random.uniform(radius - rand_r, radius + rand_r) - # randomize angle if necessary - if rand_phi: - phi = random.uniform(phi - rand_phi, phi + rand_phi) - x = rr*cos(phi) - y = rr*sin(phi) - next_vertex = (x, y, 0) - if prev_vertex is not None and divs > 1: - prev_x, prev_y, prev_z = prev_vertex - alphas = [float(i)/divs for i in range(1, divs)] - mid_vertices = [((1-alpha)*prev_x + alpha*x, (1-alpha)*prev_y + alpha*y, 0) for alpha in alphas] - vertices.extend(mid_vertices) - vertices.append(next_vertex) - prev_vertex = next_vertex - if first_vertex is None: - first_vertex = next_vertex - - if divs > 1 and first_vertex is not None and prev_vertex is not None: - x, y, z = first_vertex - prev_x, prev_y, prev_z = prev_vertex - alphas = [float(i)/divs for i in range(1, divs)] - mid_vertices = [((1-alpha)*prev_x + alpha*x, (1-alpha)*prev_y + alpha*y, 0) for alpha in alphas] - vertices.extend(mid_vertices) - return vertices + + _phi = np.arange(nsides)*dphi + if rand_phi: + np.random.seed( int(rand_seed*1000) ) + _phi = _phi + np.array( np.random.uniform(-rand_phi, +rand_phi, nsides) ) + + _rr = np.ones( nsides )*radius + if rand_r: + np.random.seed( int(rand_seed*1000) ) + _rr = _rr + np.array( np.random.uniform(-rand_r, +rand_r, nsides) ) + + _x = _rr*np.cos(_phi) + _y = _rr*np.sin(_phi) + + if divs>1: + # divs stright lines, not arcs + _x0 = _x + _y0 = _y + _x1 = np.roll(_x0, -1) + _y1 = np.roll(_y0, -1) + _dx = (_x1-_x0)/divs + _dy = (_y1-_y0)/divs + _x_divs = np.repeat( _x0, divs) + _y_divs = np.repeat( _y0, divs) + _dx = np.repeat( _dx, divs) + _dy = np.repeat( _dy, divs) + _id = np.meshgrid( np.arange(divs), np.arange(nsides), indexing = 'xy')[0].flatten() + _xd = _x_divs+_id*_dx + _yd = _y_divs+_id*_dy + _verts = np.column_stack( (_xd, _yd, np.zeros_like(_xd) ) ) + pass + else: + _verts = np.column_stack( (_x, _y, np.zeros_like(_x) )) + + _list_verts = _verts.tolist() + return _list_verts def make_edges(nsides, shift, divs): - vs = range(nsides*divs) - edges = list( zip( vs, rotate_list(vs, shift+1) ) ) - return edges -def make_faces(nsides, shift, divs): + _n = np.arange(nsides*divs) + _edges = np.column_stack( (_n, np.roll(_n, -1-shift )) ) + _list_edges = _edges.tolist() + return _list_edges + +def make_faces(nsides, shift, divs, r, dr, dphi): # for now, do not return faces if star factor # is not zero - the face obviously would be degraded. + # This actual for random phi too and some cases of random r + # to the future version of node + # if not shift or dphi or dr and (r-dr)<0: if shift: return [] - vs = range(nsides*divs) - face = list(vs) - return [face] + _faces = np.arange(nsides*divs) + _list_faces = _faces.tolist() + return [_list_faces] class SvNGonNode(SverchCustomTreeNode, bpy.types.Node): ''' NGon. [default] @@ -99,7 +107,7 @@ class SvNGonNode(SverchCustomTreeNode, bpy.types.Node): sides_: IntProperty(name='N Sides', description='Number of polygon sides', default=5, min=3, update=updateNode) - divisions : IntProperty(name='Divisions', description = "Number of divisions to divide each side to", + divisions : IntProperty(name='Divisions', description = "Number of divisions to divide each side to (lines not arcs)", default=1, min=1, update=updateNode) rand_seed_: FloatProperty(name='Seed', description='Random seed', @@ -167,7 +175,7 @@ def process(self): self.outputs['Edges'].sv_set(edges) if self.outputs['Polygons'].is_linked: - faces = [make_faces(n, shift, divs) for r, n, s, dr, dphi, shift, divs in zip(*parameters)] + faces = [make_faces(n, shift, divs, r, dr, dphi) for r, n, s, dr, dphi, shift, divs in zip(*parameters)] self.outputs['Polygons'].sv_set(faces) diff --git a/nodes/generator/sphere.py b/nodes/generator/sphere.py index e6fbf768d7..0056676603 100644 --- a/nodes/generator/sphere.py +++ b/nodes/generator/sphere.py @@ -14,31 +14,37 @@ from sverchok.node_tree import SverchCustomTreeNode from sverchok.data_structure import updateNode, list_match_modes, list_match_func from sverchok.utils.decorators_compilation import jit, njit +import numpy as np # from numba.typed import List # @njit(cache=True) def make_sphere_verts_combined(U, V, Radius): + + N1 = U # X + N2 = V # Y + n1_i = np.arange(N1, dtype=np.int16) + _n1 = np.repeat( [np.array(n1_i)], N2-2, axis = 0) # index n1 + n2_i = np.arange(1, N2-1, dtype=np.int16) + _n2 = np.repeat( [np.array(n2_i)], N1, axis = 0).T # index n2 + theta = radians(360 / U) phi = radians(180 / (V-1)) - pts = [] - pts = [[0, 0, Radius]] - for i in range(1, V-1): - pts_u = [] - sin_phi_i = sin(phi * i) - for j in range(U): - X = Radius * cos(theta * j) * sin_phi_i - Y = Radius * sin(theta * j) * sin_phi_i - Z = Radius * cos(phi * i) - pts_u.append([X, Y, Z]) - pts.extend(pts_u) - - pts.append([0, 0, -Radius]) - return pts + _theta = theta*_n1 + _phi = phi*_n2 + _X = Radius * np.cos(_theta) * np.sin(_phi) + _Y = Radius * np.sin(_theta) * np.sin(_phi) + _Z = Radius * np.cos(_phi) + _verts = np.dstack((_X, _Y, _Z)) + _verts = _verts.reshape(-1, 3) + list_verts = [[0,0,Radius]] + list_verts.extend(_verts.tolist()) + list_verts.append([0,0,-Radius]) + return list_verts def make_sphere_verts_separate(U, V, Radius): - theta = radians(360/U) - phi = radians(180/(V-1)) + theta = radians(360 / U) + phi = radians(180 / (V-1)) pts = [] pts = [[[0, 0, Radius] for i in range(U)]] @@ -66,32 +72,61 @@ def sphere_verts(U, V, Radius, Separate): # @njit(cache=True) def sphere_edges(U, V): - nr_pts = U*V-(U-1)*2 - listEdg = [] - for i in range(V-2): - listEdg.extend([[j+1+U*i, j+2+U*i] for j in range(U-1)]) - listEdg.append([U*(i+1), U*(i+1)-U+1]) - listEdg.extend([[i+1, i+1+U] for i in range(U*(V-3))]) - listEdg.extend([[0, i+1] for i in range(U)]) - listEdg.extend([[nr_pts-1, i+nr_pts-U-1] for i in range(U)]) - listEdg.reverse() - return listEdg + + N1 = U # X + N2 = V # Y + steps = np.arange(N1*(N2-2) ) + 1 # skip first verts at [0,0,Radius] and finish before [0,0,-Radius] + + + arr_verts = np.array ( np.split(steps, (N2-2) ) ) # split array vertically + arr_verts = np.hstack( (arr_verts, np.array([arr_verts[:,0]]).T ) ) # append first row to bottom to horizontal circle + + _arr_h_edges = np.zeros((N2-2, N1, 2), 'i' ) + _arr_h_edges[:, :, 0] = arr_verts[ : , :-1 ] # hor_edges + _arr_h_edges[:, :, 1] = arr_verts[ : , 1: ] # hor_edges + _arr_h_edges = _arr_h_edges.reshape(-1,2) + + _arr_v_edges = np.zeros((N2-2-1, N1, 2), 'i' ) # -1: vert edges except top and bottom point and less than 1 than exists vertcal points + _arr_v_edges[:, :, 0] = arr_verts[ :-1, :-1] # hor_edges + _arr_v_edges[:, :, 1] = arr_verts[1: , :-1] # hor_edges + _arr_v_edges = _arr_v_edges.reshape(-1,2) + + _edges = np.concatenate( + ( + _arr_h_edges, + _arr_v_edges, + np.dstack( ( arr_verts[0:1, :-1]-arr_verts[0:1, :-1] , arr_verts[ 0: 1, :-1]) ).reshape(-1,2), # self subtract to get array of 0 appropriate length + np.dstack( ( arr_verts[0:1, :-1]-arr_verts[0:1, :-1] + N1*(N2-2)+1, arr_verts[-1: , :-1]) ).reshape(-1,2), + ) ) + _list_edges = _edges.tolist() + return _list_edges # @njit(cache=True) def sphere_faces(U, V): - nr_pts = U*V-(U-1)*2 - listPln = [] - for i in range(V-3): - listPln.append([U*i+2*U, 1+U*i+U, 1+U*i, U*i+U]) - listPln.extend([[1+U*i+j+U, 2+U*i+j+U, 2+U*i+j, 1+U*i+j] for j in range(U-1)]) - - for i in range(U-1): - listPln.append([1+i, 2+i, 0]) - listPln.append([i+nr_pts-U, i+nr_pts-1-U, nr_pts-1]) - listPln.append([U, 1, 0]) - listPln.append([nr_pts-1-U, nr_pts-2, nr_pts-1]) - return listPln + N1 = U # X + N2 = V # Y + steps = np.arange(N1*(N2-2) ) + 1 # skip first verts at [0,0,Radius] and finish before [0,0,-Radius] + _arr_middle_verts = np.array ( np.split(steps, (N2-2) ) ) # split array vertically + _arr_middle_verts = np.hstack( (_arr_middle_verts, np.array([_arr_middle_verts[:,0]]).T ) ) # append first row to bottom to horizontal circle + + _arr_middle_faces = np.zeros((N2-3, N1, 4), 'i' ) + _arr_middle_faces[:, :, 0] = _arr_middle_verts[1: , :-1 ] + _arr_middle_faces[:, :, 1] = _arr_middle_verts[1: , 1: ] + _arr_middle_faces[:, :, 2] = _arr_middle_verts[ :-1, 1: ] + _arr_middle_faces[:, :, 3] = _arr_middle_verts[ :-1, :-1 ] + _arr_middle_faces = _arr_middle_faces.reshape(-1,4) + + _arr_faces_top_bottom = np.concatenate( + ( + np.dstack( ( _arr_middle_verts[ 0:1, :-1], _arr_middle_verts[ :1, 1: ], np.zeros_like(_arr_middle_verts[0,:-1]) + 0) ).reshape(-1,3), # top triangled faces + np.dstack( ( np.zeros_like(_arr_middle_verts[-1,:-1]) + N1*(N2-2)+1, _arr_middle_verts[ -1: ,1: ], _arr_middle_verts[ -1:, :-1]) ).reshape(-1,3), # bottom triangled faces + ) + ) + _arr_faces_top_bottom = _arr_faces_top_bottom.reshape(-1,3) + _list_faces = _arr_middle_faces.tolist() + _list_faces.extend( _arr_faces_top_bottom ) + return _list_faces class SphereNode(SverchCustomTreeNode, bpy.types.Node): '''UV Sphere. [default] diff --git a/nodes/generator/torus_mk2.py b/nodes/generator/torus_mk2.py index a746bdf27a..0184086de8 100644 --- a/nodes/generator/torus_mk2.py +++ b/nodes/generator/torus_mk2.py @@ -25,12 +25,11 @@ from sverchok.data_structure import updateNode, list_match_modes, list_match_func from sverchok.utils.sv_transform_helper import AngleUnits, SvAngleHelper -def sign(x): return 1 if x >= 0 else -1 +import numpy as np epsilon = 1e-10 # used to avoid division by zero - -def torus_verts(R, r, N1, N2, rPhase, sPhase, rExponent, sExponent, sTwist, Separate): +def torus_verts(R, r, N1, N2, rPhase, sPhase, rExponent, sExponent, sTwist, Separate, normals_is_linked): ''' R : major radius r : minor radius @@ -42,68 +41,63 @@ def torus_verts(R, r, N1, N2, rPhase, sPhase, rExponent, sExponent, sTwist, Sepa sExponent : spin exponent sTwist : spin twist ''' - list_verts = [] - list_norms = [] - # angle increments (cached outside of the loop for performance) da1 = 2 * pi / N1 da2 = 2 * pi / N2 - for n1 in range(N1): - a1 = n1 * da1 - theta = a1 + rPhase # revolution angle - sin_theta = sin(theta) # cached for performance - cos_theta = cos(theta) # cached for performance - # 1, 0, any. 1 is a default value of rExponent so go first - if rExponent==1: - pow_cos_theta = cos_theta - pow_sin_theta = sin_theta - elif rExponent==0: - pow_cos_theta = 1 if cos_theta >= 0 else -1 # sign(cos_theta) - pow_sin_theta = 1 if sin_theta >= 0 else -1 # sign(sin_theta) - else: - pow_cos_theta = pow(abs(cos_theta), rExponent) * (1 if cos_theta >= 0 else -1) # sign(cos_theta) - pow_sin_theta = pow(abs(sin_theta), rExponent) * (1 if sin_theta >= 0 else -1) # sign(sin_theta) - cx = R * cos_theta # torus tube center - cy = R * sin_theta # torus tube center - - twist_angle = da2 * n1 / N1 * sTwist - - loop_verts = [] - add_vert = loop_verts.append - for n2 in range(N2): - a2 = n2 * da2 - phi = a2 + sPhase + twist_angle # spin angle + twist - sin_phi = sin(phi) # cached for performance - cos_phi = cos(phi) # cached for performance - - if sExponent==1: - pow_cos_phi = cos_phi - pow_sin_phi = sin_phi - elif sExponent==0: - pow_cos_phi = 1 if cos_phi >= 0 else -1 # sign(cos_phi) - pow_sin_phi = 1 if sin_phi >= 0 else -1 # sign(sin_phi) - else: - pow_cos_phi = pow(abs(cos_phi), sExponent) * (1 if cos_phi >= 0 else -1) # sign(cos_phi) - pow_sin_phi = pow(abs(sin_phi), sExponent) * (1 if sin_phi >= 0 else -1) # sign(sin_phi) - - x = (R + r * pow_cos_phi) * pow_cos_theta - y = (R + r * pow_cos_phi) * pow_sin_theta - z = r * pow_sin_phi - - # append vertex to loop - add_vert([x, y, z]) - - # append normal - norm = [x - cx, y - cy, z] - list_norms.append(norm) - - if Separate: - list_verts.append(loop_verts) - else: - list_verts.extend(loop_verts) - - return list_verts, list_norms + n1_i = np.arange(N1, dtype=np.int16) + _n1 = np.repeat( [np.array(n1_i)], N2, axis = 0).T # index n1 + n2_i = np.arange(N2, dtype=np.int16) + _n2 = np.repeat( [np.array(n2_i)], N1, axis = 0) # index n2 + # revolution angle + _theta = _n1 * da1 + rPhase + _sin_theta = np.sin(_theta) + _cos_theta = np.cos(_theta) + _twist_angle = da2 * _n1 / N1 * sTwist + _phi = da2 * _n2 + sPhase + _twist_angle + _sin_phi = np.sin(_phi) + _cos_phi = np.cos(_phi) + if rExponent==1: + _pow_sin_theta = _sin_theta + _pow_cos_theta = _cos_theta + elif rExponent==0: + _pow_sin_theta = np.where(_sin_theta>=0, 1, -1) + _pow_cos_theta = np.where(_cos_theta>=0, 1, -1) + else: + _pow_sin_theta = np.power(np.abs(_sin_theta), rExponent) * np.where(_sin_theta>=0, 1, -1) + _pow_cos_theta = np.power(np.abs(_cos_theta), rExponent) * np.where(_cos_theta>=0, 1, -1) + + _cx = R * _cos_theta # torus tube center + _cy = R * _sin_theta # torus tube center + + if sExponent==1: + _pow_sin_phi = _sin_phi + _pow_cos_phi = _cos_phi + elif sExponent==0: + _pow_sin_phi = np.where(_sin_phi>=0, 1, -1) + _pow_cos_phi = np.where(_cos_phi>=0, 1, -1) + else: + _pow_sin_phi = np.power(np.abs(_sin_phi), sExponent) * np.where(_sin_phi>=0, 1, -1) + _pow_cos_phi = np.power(np.abs(_cos_phi), sExponent) * np.where(_cos_phi>=0, 1, -1) + + # verices coordinates + _x = (R + r * _pow_cos_phi) * _pow_cos_theta + _y = (R + r * _pow_cos_phi) * _pow_sin_theta + _z = r * _pow_sin_phi + + _verts = np.dstack( (_x, _y, _z) ) + _verts = _verts.reshape(-1,3) + if Separate: + # TODO: property do not work. Noting visible + _verts = np.array(np.split(_verts, N1)) + _list_verts = _verts.tolist() + + _list_norms = [] + if normals_is_linked: + _norm = np.dstack( (_x - _cx, _y - _cy, _z) ) + _list_norms = _norm.reshape(-1,3).tolist() + + return _list_verts, _list_norms def torus_edges(N1, N2, t): @@ -112,23 +106,19 @@ def torus_edges(N1, N2, t): N2 : minor sections - number of spin sections around the torus tube t : spin twist - number of twists (start-end vertex shift) ''' - list_edges = [] - add_edge = list_edges.append - # spin loop EDGES : around the torus tube - for n1 in range(N1): - for n2 in range(N2 - 1): - add_edge([N2 * n1 + n2, N2 * n1 + n2 + 1]) - add_edge([N2 * n1 + N2 - 1, N2 * n1 + 0]) - # revolution loop EDGES : around the torus center - for n1 in range(N1 - 1): - for n2 in range(N2): - add_edge([N2 * n1 + n2, N2 * (n1 + 1) + n2]) - for n2 in range(N2): - add_edge([N2 * (N1 - 1) + n2, N2 * 0 + (n2 + t) % N2]) + steps = np.arange(0, N1*N2) + arr_verts = np.array(np.split(steps, N1)) - return list_edges + arr_verts = np.vstack( (arr_verts, np.roll(arr_verts[:1], -t) ) ) # append first row to bottom to vertically circle with twist + arr_verts = np.hstack( (arr_verts, np.array([arr_verts[:,0]]).T ) ) # append first row to bottom to horizontal circle + hspin_edges = np.dstack( (arr_verts[:N1,:N2], arr_verts[ :N1 ,1:N2+1] )) + vspin_edges = np.dstack( (arr_verts[:N1,:N2], arr_verts[1:N1+1, :N2] )) + hs_edges = np.concatenate( (hspin_edges, vspin_edges )) + hs_edges = np.concatenate( np.concatenate( (hspin_edges, vspin_edges ), axis=0), axis=0) # remove exis + hs_edges_list = hs_edges.tolist() + return hs_edges_list def torus_polygons(N1, N2, t): ''' @@ -136,17 +126,22 @@ def torus_polygons(N1, N2, t): N2 : minor sections - number of spin sections around the torus tube t : spin twist - number of twists (start-end vertex shift) ''' - list_polys = [] - add_poly = list_polys.append - for n1 in range(N1 - 1): - for n2 in range(N2 - 1): - add_poly([N2 * n1 + n2, N2 * (n1 + 1) + n2, N2 * (n1 + 1) + n2 + 1, N2 * n1 + n2 + 1]) - add_poly([N2 * n1 + N2 - 1, N2 * (n1 + 1) + N2 - 1, N2 * (n1 + 1) + 0, N2 * n1 + 0]) - for n2 in range(N2 - 1): - add_poly([N2 * (N1 - 1) + n2, N2 * 0 + (n2 + t) % N2, N2 * 0 + (n2 + 1 + t) % N2, N2 * (N1 - 1) + n2 + 1]) - add_poly([N2 * (N1 - 1) + N2 - 1, N2 * 0 + (N2 - 1 + t) % N2, N2 * 0 + (0 + t) % N2, N2 * (N1 - 1) + 0]) + arr_faces = np.zeros((N1, N2, 4), 'i' ) + + steps = np.arange(0, N1*N2) + arr_verts = np.array(np.split(steps, N1)) + arr_verts = np.vstack( (arr_verts, np.roll(arr_verts[:1], -t) ) ) # append first row to bottom to vertically circle + arr_verts = np.hstack( (arr_verts, np.array([arr_verts[:,0]]).T ) ) # append first column to right to horizontal circle + + arr_faces[:, :, 0] = arr_verts[ :-1, 1: ] + arr_faces[:, :, 1] = arr_verts[1: , 1: ] + arr_faces[:, :, 2] = arr_verts[1: , :-1] + arr_faces[:, :, 3] = arr_verts[ :-1, :-1] + hs_faces = arr_faces.reshape(-1,4) # remove exis + hs_edges_list = hs_faces.tolist() + + return hs_edges_list - return list_polys class SvTorusNodeMK2(SverchCustomTreeNode, bpy.types.Node, SvAngleHelper): @@ -365,16 +360,19 @@ def process(self): verts_list = [] norms_list = [] verts_cache = dict() + normals_is_linked = self.outputs['Normals'].is_linked for R, r, n1, n2, rP, sP, rE, sE, sT in zip(*parameters): if (R, r, n1, n2, rP, sP, rE, sE, sT) in verts_cache: verts, norms = verts_cache[(R, r, n1, n2, rP, sP, rE, sE, sT)] else: - verts, norms = verts_cache[(R, r, n1, n2, rP, sP, rE, sE, sT)] = torus_verts(R, r, n1, n2, rP * au, sP * au, rE, sE, sT, self.Separate) + verts, norms = verts_cache[(R, r, n1, n2, rP, sP, rE, sE, sT)] = torus_verts(R, r, n1, n2, rP * au, sP * au, rE, sE, sT, self.Separate, normals_is_linked) verts_list.append(verts) - norms_list.append(norms) + if normals_is_linked: + norms_list.append(norms) verts_cache.clear() self.outputs['Vertices'].sv_set(verts_list) - self.outputs['Normals'].sv_set(norms_list) + if normals_is_linked: + self.outputs['Normals'].sv_set(norms_list) if self.outputs['Edges'].is_linked: edges_list = [] diff --git a/nodes/generators_extended/ellipse_mk3.py b/nodes/generators_extended/ellipse_mk3.py index dd7bff4822..04b081f48a 100644 --- a/nodes/generators_extended/ellipse_mk3.py +++ b/nodes/generators_extended/ellipse_mk3.py @@ -24,6 +24,7 @@ from sverchok.utils.sv_transform_helper import AngleUnits, SvAngleHelper from math import sin, cos, pi, sqrt +import numpy as np centering_items = [("F1", "F1", "Ellipse focal point 1", 1), ("C", "C", "Ellipse center point", 2), @@ -312,22 +313,25 @@ def make_ellipse(self, a, b, N, phase, rotation, scale, ex, ey): exx = 2.0 / (ex + epsilon) eyy = 2.0 / (ey + epsilon) - add_vert = verts.append - for n in range(N): - theta = delta * n + phase - cost = cos(theta) - sint = sin(theta) - x = -cx + a * pow(abs(cost), exx) * sign(cost) - y = -cy + b * pow(abs(sint), eyy) * sign(sint) - # apply in-plane rotation - xx = x * coss - y * sins - yy = x * sins + y * coss - add_vert((xx, yy, 0)) + _arr_indexes = np.arange(N, dtype=np.int32) + _theta = _arr_indexes * delta + phase + _cos_theta = np.cos(_theta) + _sin_theta = np.sin(_theta) + _x = -cx + a*np.power(np.abs(_cos_theta), exx) * np.where(_cos_theta>=0, 1, -1) + _y = -cy + b*np.power(np.abs(_sin_theta), eyy) * np.where(_sin_theta>=0, 1, -1) + # apply in-plane rotation + xx = _x*coss - _y*sins + yy = _x*sins + _y*coss + + _verts = np.column_stack((xx, yy)) + _verts = np.insert(_verts, 2, [0], axis=1) + list_verts = _verts.tolist() edges = get_edge_loop(N) - polys = [list(range(N))] - - return verts, edges, polys, f1, f2 + _arr_indexes = np.arange(N, dtype=np.int32) + polys = [_arr_indexes.tolist()] + + return list_verts, edges, polys, f1, f2 def process(self): outputs = self.outputs diff --git a/nodes/generators_extended/hilbert3d.py b/nodes/generators_extended/hilbert3d.py index 80727c4f13..5df2468c07 100644 --- a/nodes/generators_extended/hilbert3d.py +++ b/nodes/generators_extended/hilbert3d.py @@ -87,7 +87,11 @@ def process(self): if edges_socket.is_linked: listEdg = [] for ve in verts: - listEdg.append([(i, i+1) for i in range(len(ve) - 1)]) + len_ve = len(ve) + _n = np.arange(len_ve, dtype=np.int32) + _edges = np.column_stack( (_n[:-1], _n[1:]) ) + listEdg.append(_edges.tolist()) + edges_socket.sv_set(listEdg) diff --git a/nodes/generators_extended/ring_mk2.py b/nodes/generators_extended/ring_mk2.py index 0f45c65f87..acb7066811 100644 --- a/nodes/generators_extended/ring_mk2.py +++ b/nodes/generators_extended/ring_mk2.py @@ -19,12 +19,12 @@ import bpy from bpy.props import IntProperty, FloatProperty, BoolProperty, EnumProperty -from math import sin, cos, pi, radians +from math import sin, cos, pi, radians, tau from sverchok.node_tree import SverchCustomTreeNode from sverchok.data_structure import updateNode, match_long_repeat from sverchok.utils.sv_transform_helper import AngleUnits, SvAngleHelper - +import numpy as np def ring_verts(separate, u, r1, r2, N1, N2, a1, a2, p): """ @@ -38,7 +38,6 @@ def ring_verts(separate, u, r1, r2, N1, N2, a1, a2, p): a2 : ending angle p : radial section phase """ - # use an extra section if the ring is open (start & end angles differ) i = open_ring = abs((a2-a1) % (2*pi)) > 1e-5 @@ -47,30 +46,26 @@ def ring_verts(separate, u, r1, r2, N1, N2, a1, a2, p): s2 = 2 / (N2 - 1) # caching outside the loop - list_verts = [] - - for n1 in range(N1*(1+u) + i): # for each RADIAL section around the center - theta = a1 + n1 * da + p # RADIAL section angle - sin_theta = sin(theta) # caching - cos_theta = cos(theta) # caching - - verts = [] - for n2 in range(N2): # for each CIRCULAR section away from center - t = n2*s2 - 1 # interpolation factor : [-1, +1] - r = r1 + t*r2 # radius range : [r1-r2, r1+r2] - x = r * cos_theta - y = r * sin_theta - # append vertex at this (radial, circular) index to the list - verts.append([x, y, 0.0]) + N1_gen = (N1*(1+u) + i) # N1 after apply params + #_verts_indexes = np.arange( N1_gen * N2, dtype=np.int32 ).reshape(N2, N1_gen) # Circular, Radial + _n1 = np.repeat( [np.arange(N1_gen, dtype=np.int32)], N2, axis = 0) # Circular + _n2 = np.repeat( [np.arange(N2 , dtype=np.int32)], N1_gen, axis = 0).T # Radial + _theta = a1 + _n1 * da + p # RADIAL section angle + _sin_theta = np.sin(_theta) # caching + _cos_theta = np.cos(_theta) # caching - if separate: - list_verts.append(verts) - else: - list_verts.extend(verts) - - return list_verts + _t2 = _n2*s2 - 1 # interpolation factor : [-1, +1] + _r = r1 + _t2*r2 # radius range : [r1-r2, r1+r2] + _x = _r * _cos_theta + _y = _r * _sin_theta + #_verts = np.dstack((_x, _y, np.zeros_like(_x) )).reshape(-1, 3) + _verts = np.dstack((_x.T, _y.T, np.zeros_like(_x).T )).reshape(-1,3) + if separate: + _verts = _verts.reshape(-1,N2,3) + _list_verts = _verts.tolist() + return _list_verts def ring_edges(N1, N2, a1, a2, u): """ @@ -81,28 +76,38 @@ def ring_edges(N1, N2, a1, a2, u): u : circular section subdivisions """ - # use an extra section if the ring is open (start & end angles differ) - i = open_ring = abs((a2-a1) % (2*pi)) > 1e-5 + closed_ring = abs((a2-a1) % tau) < 1e-5 + #closed_ring = abs((a2 - a1)%tau) < 1e-5 # or abs((a2%tau - a1%tau)%tau-tau)<1e-5 - list_edges = [] + if closed_ring: + #arr_verts = np.arange( N1*(u+1)*N2, dtype=np.int32 ).reshape(N2, N1*(u+1)) + arr_verts = np.arange( N1*(u+1)*N2, dtype=np.int32 ).T.reshape(N1*(u+1), N2).T + arr_verts = np.hstack( (arr_verts, np.array([arr_verts[:,0]]).T ) ) # append first row to bottom to horizontal circle + # horizintal edges are cicled + _arr_h_edges = np.zeros((N2, N1*(u+1), 2), 'i' ) + _arr_h_edges[:, :, 0] = arr_verts[ : , :-1 ] # hor_edges + _arr_h_edges[:, :, 1] = arr_verts[ : , 1: ] # hor_edges - # radial EDGES (away from center) - for n1 in range(N1 + i): # for each RADIAL section around the center - for n2 in range(N2 - 1): # for each CIRCULAR section away from center - list_edges.append([N2 * n1*(1+u) + n2, N2 * n1*(1+u) + n2 + 1]) + _arr_v_edges = np.zeros((N2-1, N1*(u+1), 2), 'i' ) # -1: vert edges except bottom points + _arr_v_edges[:, :, 0] = arr_verts[ :-1, :-1] + _arr_v_edges[:, :, 1] = arr_verts[1: , :-1] + else: + arr_verts = np.arange( (N1*(u+1)+1)*N2, dtype=np.int32 ).T.reshape(N1*(u+1)+1, N2).T + _arr_h_edges = np.zeros((N2, N1*(u+1), 2), 'i' ) + _arr_h_edges[:, :, 0] = arr_verts[ : , :-1 ] # hor_edges + _arr_h_edges[:, :, 1] = arr_verts[ : , 1: ] # hor_edges - # circular EDGES (around the center) : edges are ordered radially - for n1 in range(N1*(1+u) - 1 + i): # for each RADIAL section around the center - for n2 in range(N2): # for each CIRCULAR section away from center - list_edges.append([N2 * n1 + n2, N2 * (n1 + 1) + n2]) + _arr_v_edges = np.zeros((N2-1, N1*(u+1)+1, 2), 'i' ) # -1: vert edges except bottom points + _arr_v_edges[:, :, 0] = arr_verts[ :-1, : ] + _arr_v_edges[:, :, 1] = arr_verts[1: , : ] - # close the ring ? => use the start vertices to close the last edges in the ring - if not open_ring: - for n2 in range(N2): - list_edges.append([N2 * (N1*(1+u) - 1) + n2, n2]) - return list_edges + _arr_h_edges = _arr_h_edges.reshape(-1,2) + _arr_v_edges = _arr_v_edges.reshape(-1,2) + _edges = np.concatenate( ( _arr_h_edges, _arr_v_edges, ) ) + _list_edges = _edges.tolist() + return _list_edges def ring_polygons(N1, N2, a1, a2, u): """ @@ -115,28 +120,29 @@ def ring_polygons(N1, N2, a1, a2, u): Note: the vertex order is consistent with face normal along positive Z """ - # use an extra section if the ring is open (start & end angles differ) - i = open_ring = abs((a2-a1) % (2*pi)) > 1e-5 - - list_polys = [] - - for n1 in range(N1 - 1 + i): # RADIAL (around the center) - for n2 in range(N2 - 1): # CIRCULAR (away from center) - arc1 = [N2*(n1*(1+u) + iu) + n2 for iu in reversed(range(2+u))] - arc2 = [N2*(n1*(1+u) + iu) + n2 + 1 for iu in range(2+u)] - face = arc2 + arc1 - list_polys.append(face) - - # close the ring ? => use the start vertices to close the last faces in the ring - if not open_ring: - for n2 in range(N2 - 1): # CIRCULAR (away from center) - arc1 = [N2*((N1-1)*(1+u) + iu) + n2 for iu in reversed(range(1+u))] - arc2 = [N2*((N1-1)*(1+u) + iu) + n2 + 1 for iu in range(1+u)] - face = arc2 + [n2+1, n2] + arc1 - list_polys.append(face) - - return list_polys - + closed_ring = abs((a2-a1) % tau) < 1e-5 + + if closed_ring: + arr_verts = np.arange( N1*(u+1)*N2, dtype=np.int32 ).T.reshape(N1*(u+1), N2).T + arr_verts = np.hstack( (arr_verts, np.array([arr_verts[:,0]]).T ) ) # append first row to bottom to horizontal circle + # faces are cicled horizintally + _arr_faces = np.zeros((N2-1, N1*(u+1), 4), 'i' ) + _arr_faces[:, :, 0] = arr_verts[1: , :-1 ] + _arr_faces[:, :, 1] = arr_verts[1: , 1: ] + _arr_faces[:, :, 2] = arr_verts[ :-1, 1: ] + _arr_faces[:, :, 3] = arr_verts[ :-1, :-1 ] + else: + arr_verts = np.arange( (N1*(u+1)+1)*N2, dtype=np.int32 ).T.reshape(N1*(u+1)+1, N2).T + _arr_faces = np.zeros((N2-1, N1*(u+1), 4), 'i' ) + _arr_faces[:, :, 0] = arr_verts[1: , :-1 ] + _arr_faces[:, :, 1] = arr_verts[1: , 1: ] + _arr_faces[:, :, 2] = arr_verts[ :-1, 1: ] + _arr_faces[:, :, 3] = arr_verts[ :-1, :-1 ] + + _arr_faces = _arr_faces.reshape(-1,4) + + _list_faces = _arr_faces.tolist() + return _list_faces class SvRingNodeMK2(SverchCustomTreeNode, bpy.types.Node, SvAngleHelper): """ diff --git a/nodes/generators_extended/spiral_mk2.py b/nodes/generators_extended/spiral_mk2.py index e70ce085bb..1818a60256 100644 --- a/nodes/generators_extended/spiral_mk2.py +++ b/nodes/generators_extended/spiral_mk2.py @@ -18,6 +18,7 @@ import bpy from bpy.props import IntProperty, FloatProperty, BoolProperty, EnumProperty +import numpy as np from math import sin, cos, pi, sqrt, exp, atan, log import re @@ -81,7 +82,7 @@ def make_archimedean_spiral(settings): phase : phase the spiral around its center flip : flip the spiral direction (default is CLOCKWISE) ''' - + eR, iR, exponent, turns, N, scale, height, phase, flip = settings sign = -1 if flip else 1 # flip direction ? @@ -95,22 +96,19 @@ def make_archimedean_spiral(settings): N = N * turns # total number of points in the spiral - verts = [] - norms = [] - add_vert = verts.append - add_norm = norms.append - for n in range(N + 1): - t = n / N # t : [0, 1] - phi = max_phi * t + phase - r = (iR + dR * (t + epsilon) ** ex) * scale # essentially: r = a * t ^ (1/b) - x = r * cos(phi) - y = r * sin(phi) - z = height * t - add_vert([x, y, z]) - - edges = get_edge_list(N) + _n = np.arange(N, dtype=np.int32) + _verts = np.empty((N,3), dtype=np.float32) + _t = _n/N + _phi = max_phi*_t+phase + _r = (iR + dR * np.power(_t + epsilon, ex)) * scale # essentially: r = a * t ^ (1/b) + _verts[:,0] = _r*np.cos(_phi) + _verts[:,1] = _r*np.sin(_phi) + _verts[:,2] = height * _t + _edges = np.zeros((N-1,2), dtype=np.int32) + _edges[:,0] = _n[ :-1] + _edges[:,1] = _n[1: ] - return verts, edges, norms + return _verts, _edges, [] def make_logarithmic_spiral(settings): @@ -134,24 +132,19 @@ def make_logarithmic_spiral(settings): N = N * turns # total number of points in the spiral - verts = [] - norms = [] - add_vert = verts.append - add_norm = norms.append - for n in range(N + 1): - t = n / N # t : [0, 1] - phi = max_phi * t - r = eR * exp(exponent * phi) * scale # essentially: r = a * e ^ (b*t) - pho = phi * sign + phase # final angle : cached for performance - x = r * sin(pho) - y = r * cos(pho) - z = height * t - add_vert([x, y, z]) - - edges = get_edge_list(N) - - return verts, edges, norms + _n = np.arange(N, dtype=np.int32) + _verts = np.empty((N,3), dtype=np.float32) + _t = _n/N + _phi = max_phi*_t + _r = eR * scale * np.exp(exponent * _phi) + _verts[:,0] = _r*np.sin(_phi*sign+phase) + _verts[:,1] = _r*np.cos(_phi*sign+phase) + _verts[:,2] = height * _t + _edges = np.zeros((N-1,2), dtype=np.int32) + _edges[:,0] = _n[ :-1] + _edges[:,1] = _n[1: ] + return _verts, _edges, [] def make_spherical_spiral(settings): ''' @@ -176,28 +169,26 @@ def make_spherical_spiral(settings): max_phi = 2 * pi * turns * sign N = N * turns # total number of points in the spiral - es = prepareExponentialSettings(2, exponent + 1e-5) # used for easing - - verts = [] - norms = [] - add_vert = verts.append - add_norm = norms.append - for n in range(N + 1): - t = n / N # t : [0, 1] - phi = max_phi * t + phase - a = ExponentialEaseInOut(t, es) # ease theta variation - theta = -pi / 2 + pi * a - RxCosTheta = (iR + eR * cos(theta)) * scale # cached for performance - x = cos(phi) * RxCosTheta - y = sin(phi) * RxCosTheta - z = eR * sin(theta) - add_vert([x, y, z]) - - edges = get_edge_list(N) - - return verts, edges, norms - + b, e, m, s = es + + _n = np.arange(N+1, dtype=np.int32) + _verts = np.empty((N+1,3), dtype=np.float32) + _t = _n/N + _phi = max_phi*_t + phase + _a = np.where(_t<0.5, # ExponentialEaseInOut + 0.5 * (np.power(b, e * (2*_t - 1) ) - m) * s, # ExponentialEaseIn + 0.5 + 0.5 * (1 - (np.power(b, e * (1 - (2*_t - 1)-1)) - m) * s ) ) # ExponentialEaseOut + _theta = -pi / 2 + pi * _a + _RxCosTheta = (iR + eR * np.cos(_theta)) * scale + _verts[:,0] = np.cos(_phi) * _RxCosTheta + _verts[:,1] = np.sin(_phi) * _RxCosTheta + _verts[:,2] = eR * np.sin(_theta) + + _edges = np.zeros((N,2), dtype=np.int32) + _edges[:,0] = _n[ :-1] + _edges[:,1] = _n[1: ] + return _verts, _edges, [] def make_ovoidal_spiral(settings): ''' @@ -228,26 +219,27 @@ def make_ovoidal_spiral(settings): es = prepareExponentialSettings(2, exponent + 1e-5) # used for easing - verts = [] - norms = [] - add_vert = verts.append - add_norm = norms.append - for n in range(N + 1): - t = n / N # t : [0, 1] - phi = max_phi * t + phase - a = ExponentialEaseInOut(t, es) # ease theta variation - theta = -pi / 2 + pi * a - h = 0.5 * height * sin(theta) # [-H/2, +H/2] - r = sqrt(eR2 - h * h) - dR # [0 -> iR -> 0] - x = r * cos(phi) * scale - y = r * sin(phi) * scale - z = h * scale - add_vert([x, y, z]) + b, e, m, s = es - edges = get_edge_list(N) + _n = np.arange(N+1, dtype=np.int32) + _verts = np.empty((N+1,3), dtype=np.float32) + _t = _n/N + _phi = max_phi*_t + phase - return verts, edges, norms + _a = np.where(_t<0.5, # ExponentialEaseInOut + 0.5 * (np.power(b, e * (2*_t - 1) ) - m) * s, # ExponentialEaseIn + 0.5 + 0.5 * (1 - (np.power(b, e * (1 - (2*_t - 1)-1)) - m) * s ) ) # ExponentialEaseOut + _theta = -pi / 2 + pi * _a + _h = 0.5 * height * np.sin(_theta) # [-H/2, +H/2] + _r = np.sqrt(eR2 - _h * _h) - dR # [0 -> iR -> 0] + _verts[:,0] = _r * np.cos(_phi) * scale + _verts[:,1] = _r * np.sin(_phi) * scale + _verts[:,2] = _h * scale + _edges = np.zeros((N,2), dtype=np.int32) + _edges[:,0] = _n[ :-1] + _edges[:,1] = _n[1: ] + return _verts, _edges, [] def make_cornu_spiral(settings): ''' @@ -261,7 +253,7 @@ def make_cornu_spiral(settings): TODO : refine the math (smoother curve, adaptive res, faster computation) ''' - + eR, iR, exponent, turns, N, scale, height, phase, flip = settings sign = -1 if flip else 1 # flip direction ? @@ -270,59 +262,64 @@ def make_cornu_spiral(settings): L = iR * turns # length S = eR * scale # overall scale - es = prepareExponentialSettings(2, exponent + 1e-5) # used for easing - - verts1 = [] # positive spiral verts - verts2 = [] # negative spiral verts - norms = [] - add_vert1 = verts1.append - add_vert2 = verts2.append - add_norm = norms.append - l1 = 0 - x = 0 - y = 0 - for n in range(N + 1): - t = n / N # t = [0,1] - - a = QuadraticEaseOut(t) - # a = ExponentialEaseOut(t, es) - - l = L * a # l = [0, +L] - - r = x * x + y * y - # print("r=", r) - # M = 100 + int(300 * pow(r, exponent)) # integral steps - M = 100 + int(100 * a) # integral steps - l2 = l - - # integral from l1 to l2 - u = l1 - du = (l2 - l1) / M - for m in range(M + 1): - u = u + du # u = [l1, l2] - phi = u * u * pi / 2 - x = x + cos(phi) * du - y = y + sin(phi) * du - l1 = l2 - - # scale and flip - xx = x * S - yy = y * S * sign - - # rotate by phase amount - px = xx * cos(phase) - yy * sin(phase) - py = xx * sin(phase) + yy * cos(phase) - pz = height * t - - add_vert1([px, py, pz]) # positive spiral verts - add_vert2([-px, -py, -pz]) # netative spiral verts - - verts = verts2[::-1] + verts1 - - edges = get_edge_list(N) - - return verts, edges, norms - + cos_phase = cos(phase) + sin_phase = sin(phase) + + _n = np.arange(N+1, dtype=np.int32) + _verts = np.empty((N+1,3), dtype=np.float32) + _t = _n/N + _a = _t * (2 - _t) # a = ExponentialEaseOut(t, es) + _M = 100 + (100*_a).astype(dtype=np.int32) # integral steps (pricise of integral calculation) + _l = L * _a # l = [0, +L] + _l1 = np.roll(_l, 1) + _l1[0] = 0 + _du = (_l - _l1) / _M + + # # integral from l1 to l2 + # It is just ariphmetic progression. Can be numpify with vectorize + # u = l1 + # du = (l - l1) / M + # for m in range(M + 1): + # u = u + du # u = [l1, l2] + # phi = u * u * pi / 2 + # x = x + cos(phi) * du + # y = y + sin(phi) * du + + def integral_l12(M, u, du0): + pi_2 = pi/2 + _arr_n = np.arange(M+1, dtype=np.float32) + _arr_u = _arr_n*du0+u + _arr_phi = _arr_u*_arr_u * pi_2 + _cos_phi = np.cos(_arr_phi)*du0 + _sin_phi = np.sin(_arr_phi)*du0 + sum_cos = np.sum(_cos_phi) + sum_sin = np.sum(_sin_phi) + + return sum_cos, sum_sin + + integral_l12_vect = np.vectorize(integral_l12) + _dx, _dy = integral_l12_vect(_M, _l1, _du) + _cumsum_x = np.cumsum(_dx) + _cumsum_y = np.cumsum(_dy) + + # scale and flip + _xx = _cumsum_x * S + _yy = _cumsum_y * S * sign + + # rotate by phase amount + _px = _xx * cos_phase - _yy * sin_phase + _py = _xx * sin_phase + _yy * cos_phase + _pz = height * _t + + _p1 = np.dstack( (_px, _py, _pz) ).reshape(-1, 3) # positive spiral verts + _verts = np.concatenate( (np.flip(-_p1, axis=0), _p1)) + _verts = _verts.reshape(-1,3) + + _edges = np.zeros((N,2), dtype=np.int32) + _edges[:,0] = _n[ :-1] + _edges[:,1] = _n[1: ] + + return _verts, _edges, [] def make_exo_spiral(settings): ''' @@ -349,23 +346,24 @@ def make_exo_spiral(settings): es = prepareExponentialSettings(11, exponent + 1e-5) # used for easing - verts = [] - norms = [] - add_vert = verts.append - add_norm = norms.append - for n in range(N + 1): - t = n / N # t : [0, 1] - a = ExponentialEaseInOut(t, es) # ease radius variation (SIGMOID) - r = (iR + (eR - iR) * a) * scale - phi = max_phi * t + phase - x = r * cos(phi) - y = r * sin(phi) - z = height * t - add_vert([x, y, z]) + b, e, m, s = es - edges = get_edge_list(N) + _n = np.arange(N+1, dtype=np.int32) + _verts = np.empty((N+1,3), dtype=np.float32) + _t = _n/N + _a = np.where(_t<0.5, # ExponentialEaseInOut + 0.5 * (np.power(b, e * (2*_t - 1) ) - m) * s, # ExponentialEaseIn + 0.5 + 0.5 * (1 - (np.power(b, e * (1 - (2*_t - 1)-1)) - m) * s ) ) # ExponentialEaseOut + _r = (iR + (eR - iR) * _a) * scale + _phi = max_phi*_t + phase + _verts[:,0] = _r * np.cos(_phi) + _verts[:,1] = _r * np.sin(_phi) + _verts[:,2] = height * _t - return verts, edges, norms + _edges = np.zeros((N,2), dtype=np.int32) + _edges[:,0] = _n[ :-1] + _edges[:,1] = _n[1: ] + return _verts, _edges, [] def make_spirangle_spiral(settings): @@ -380,6 +378,7 @@ def make_spirangle_spiral(settings): phase : phase the spiral around its center flip : flip the spiral direction (default is CLOCKWISE) ''' + # TODO: numpify eR, iR, exponent, turns, N, scale, height, phase, flip = settings @@ -418,6 +417,7 @@ def normalize_spiral(verts, normalize_eR, eR, iR, scale): ''' Normalize the spiral (XY) to either exterior or interior radius ''' + _verts = np.array(verts, dtype = np.float64) if normalize_eR: # normalize to exterior radius (ending radius) psx = verts[-1][0] # x coordinate of the last point in the spiral psy = verts[-1][1] # y coordinate of the last point in the spiral @@ -429,12 +429,8 @@ def normalize_spiral(verts, normalize_eR, eR, iR, scale): r = sqrt(psx * psx + psy * psy) ss = iR / r * scale if iR != 0 else 1 - for n in range(len(verts)): - verts[n][0] *= ss - verts[n][1] *= ss - - return verts - + _verts = _verts * ss + return _verts class SvSpiralNodeMK2(SverchCustomTreeNode, bpy.types.Node, SvAngleHelper): """ @@ -621,8 +617,12 @@ def process(self): edges_list = [] for R, r, e, t, n, s, h, p, a in zip(*parameters): p = p * au - arm_verts = [] - arm_edges = [] + if self.separate: + _arm_verts = np.empty((0,0,3), dtype=np.int32) + _arm_edges = np.empty((0,0,2), dtype=np.int32) + else: + _arm_verts = np.empty((0,3), dtype=np.int32) + _arm_edges = np.empty((0,2), dtype=np.int32) for i in range(a): # generate each arm pa = p + 2 * pi / a * i settings = [R, r, e, t, n, s, h, pa, f] # spiral settings @@ -633,16 +633,20 @@ def process(self): normalize_spiral(verts, self.normalize == "ER", R, r, s) if self.separate: - arm_verts.append(verts) - arm_edges.append(edges) + # unvisibled (4 levels. Visible is 3 levels) + _arm_verts = np.concatenate( (_arm_verts, verts[np.newaxis, :,:] ) ) if _arm_verts.size else verts[np.newaxis, :,:] + _arm_edges = np.concatenate( (_arm_edges, edges[np.newaxis, :,:] ) ) if _arm_edges.size else edges[np.newaxis, :,:] else: # join the arms - o = len(arm_verts) - edges = [[i1 + o, i2 + o] for (i1, i2) in edges] - arm_verts.extend(verts) - arm_edges.extend(edges) - - verts_list.append(arm_verts) - edges_list.append(arm_edges) + o = len(_arm_verts) + edges = np.array(edges) + edges = edges+o + _arm_verts = np.vstack((_arm_verts, verts)) + _arm_edges = np.vstack((_arm_edges, edges)) + + _list_arm_verts = _arm_verts.tolist() + _list_arm_edges = _arm_edges.tolist() + verts_list.append(_list_arm_verts) + edges_list.append(_list_arm_edges) self.outputs['Vertices'].sv_set(verts_list) self.outputs['Edges'].sv_set(edges_list) diff --git a/nodes/generators_extended/super_ellipsoid.py b/nodes/generators_extended/super_ellipsoid.py index 555b0190c7..d4a8406b22 100644 --- a/nodes/generators_extended/super_ellipsoid.py +++ b/nodes/generators_extended/super_ellipsoid.py @@ -21,6 +21,7 @@ from sverchok.node_tree import SverchCustomTreeNode from sverchok.data_structure import (match_long_repeat, updateNode) +import numpy as np from math import sin, cos, pi @@ -48,7 +49,7 @@ def sign(x): return 1 if x >= 0 else -1 } -def make_verts(sx, sy, sz, xp, xm, np, nm): +def make_verts(sx, sy, sz, xp, xm, npar, nm): """ Generate the super-ellipsoid vertices for the given parameters sx : scale along x @@ -59,27 +60,75 @@ def make_verts(sx, sy, sz, xp, xm, np, nm): np : number of parallels (= number of points in a meridian) nm : number of meridians (= number of points in a parallel) """ - verts = [] - for p in range(np): - a = (pi / 2 - epsilon) * (2 * p / (np - 1) - 1) - cos_a = cos(a) - sin_a = sin(a) - pow_ca = pow(abs(cos_a), xm) * sign(cos_a) - pow_sa = pow(abs(sin_a), xm) * sign(sin_a) - for m in range(nm): - b = pi * (2 * m / nm - 1) - cos_b = cos(b) - sin_b = sin(b) - pow_cb = pow(abs(cos_b), xp) * sign(cos_b) - pow_sb = pow(abs(sin_b), xp) * sign(sin_b) - - x = sx * pow_ca * pow_cb - y = sy * pow_ca * pow_sb - z = sz * pow_sa - verts.append([x, y, z]) - - return verts - + + n1_i = np.arange(npar, dtype=np.int16) + _p = np.repeat( [np.array(n1_i)], nm, axis = 0).T # index n1 + n2_i = np.arange(nm, dtype=np.int16) + _m = np.repeat( [np.array(n2_i)], npar, axis = 0) # index n2 + _a = (pi / 2 - epsilon) * (2 * _p / (npar - 1) - 1) + _cos_a = np.cos(_a) + _sin_a = np.sin(_a) + _pow_ca = np.power(np.abs(_cos_a), xm) * np.where(_cos_a>=0, 1, -1) + _pow_sa = np.power(np.abs(_sin_a), xm) * np.where(_sin_a>=0, 1, -1) + + _b = pi * (2 * _m / nm - 1) + _cos_b = np.cos(_b) + _sin_b = np.sin(_b) + _pow_cb = np.power(np.abs(_cos_b), xp) * np.where(_cos_b>=0, 1, -1) + _pow_sb = np.power(np.abs(_sin_b), xp) * np.where(_sin_b>=0, 1, -1) + _x = sx * _pow_ca * _pow_cb + _y = sy * _pow_ca * _pow_sb + _z = sz * _pow_sa + _verts = np.dstack( (_x, _y, _z) ) + _verts = _verts.reshape(-1,3) + _list_verts = _verts.tolist() + + return _list_verts + +def make_edges_polys(is_edges, is_polys, P, M, cap_bottom, cap_top): + """ + Generate the super-ellipsoid edges and polygons for the given parameters + is_edges: generate edges + is_polys: generate polys + P : number of parallels (= number of points in a meridian) + M : number of meridians (= number of points in a parallel) + cap_bottom : turn on/off the bottom cap generation + cap_top : turn on/off the top cap generation + """ + list_edges = None + list_polys = None + + N1 = P + N2 = M + + steps = np.arange(0, N1*N2) # generate array of indices + arr_verts = np.array(np.split(steps, N1)) # split array of indices by parallels + arr_verts = np.hstack( (arr_verts, np.array([arr_verts[:,0]]).T ) ) # append first column to the left to horizontal circle of edges and faces + + if is_edges: + hspin_edges = np.dstack( (arr_verts[:N1 ,:N2], arr_verts[ :N1,1:N2+1] )) + vspin_edges = np.dstack( (arr_verts[:N1-1,:N2], arr_verts[1:N1, :N2] )) + hs_edges = np.concatenate( (hspin_edges, vspin_edges ), axis=0) # combine horisontal end vertical edges + hs_edges = hs_edges.reshape(-1,2) + list_edges = hs_edges.tolist() + + if is_polys: + arr_faces = np.zeros((N1-1, N2, 4), 'i' ) + arr_faces[:, :, 0] = arr_verts[ :-1, 1: ] + arr_faces[:, :, 1] = arr_verts[1: , 1: ] + arr_faces[:, :, 2] = arr_verts[1: , :-1] + arr_faces[:, :, 3] = arr_verts[ :-1, :-1] + hs_faces = arr_faces.reshape(-1,4) # remove exis + list_polys = hs_faces.tolist() + if cap_bottom: + cap_b = np.flip( np.arange(M) ) + list_polys.append(cap_b) + + if cap_top: + cap_t = np.arange(M)+(N1-1)*N2 + list_polys.append(cap_t) + + return list_edges, list_polys def make_edges(P, M): """ @@ -87,21 +136,22 @@ def make_edges(P, M): P : number of parallels (= number of points in a meridian) M : number of meridians (= number of points in a parallel) """ - edge_list = [] - - # generate PARALLELS edges (close paths) - for i in range(P): # for every point on a meridian - for j in range(M - 1): # for every point on a parallel (minus last) - edge_list.append([i * M + j, i * M + j + 1]) - edge_list.append([(i + 1) * M - 1, i * M]) # close the path - # generate MERIDIANS edges (open paths) - for j in range(M): # for every point on a parallel - for i in range(P - 1): # for every point on a meridian (minus last) - edge_list.append([i * M + j, (i + 1) * M + j]) + N1 = P + N2 = M + steps = np.arange(0, N1*N2) + arr_verts = np.array(np.split(steps, N1)) - return edge_list + #arr_verts = np.vstack( (arr_verts, np.roll(arr_verts[:1], -t) ) ) # append first row to bottom to vertically circle with twist + arr_verts = np.hstack( (arr_verts, np.array([arr_verts[:,0]]).T ) ) # append first column to the left to horizontal circle + hspin_edges = np.dstack( (arr_verts[:N1 ,:N2], arr_verts[ :N1,1:N2+1] )) + vspin_edges = np.dstack( (arr_verts[:N1-1,:N2], arr_verts[1:N1, :N2] )) + hs_edges = np.concatenate( (hspin_edges, vspin_edges )) + hs_edges = np.concatenate( np.concatenate( (hspin_edges, vspin_edges ), axis=0), axis=0) # remove exis + hs_edges_list = hs_edges.tolist() + + return hs_edges_list def make_polys(P, M, cap_bottom, cap_top): """ @@ -111,23 +161,31 @@ def make_polys(P, M, cap_bottom, cap_top): cap_bottom : turn on/off the bottom cap generation cap_top : turn on/off the top cap generation """ - poly_list = [] - - for i in range(P - 1): - for j in range(M - 1): - poly_list.append([i * M + j, i * M + j + 1, (i + 1) * M + j + 1, (i + 1) * M + j]) - poly_list.append([(i + 1) * M - 1, i * M, (i + 1) * M, (i + 2) * M - 1]) + N1 = P + N2 = M + + arr_faces = np.zeros((N1-1, N2, 4), 'i' ) + + steps = np.arange(0, N1*N2) + arr_verts = np.array(np.split(steps, N1)) + arr_verts = np.hstack( (arr_verts, np.array([arr_verts[:,0]]).T ) ) # append first column to the left to horizontal circle + + arr_faces[:, :, 0] = arr_verts[ :-1, 1: ] + arr_faces[:, :, 1] = arr_verts[1: , 1: ] + arr_faces[:, :, 2] = arr_verts[1: , :-1] + arr_faces[:, :, 3] = arr_verts[ :-1, :-1] + hs_faces = arr_faces.reshape(-1,4) # remove exis + hs_faces_list = hs_faces.tolist() if cap_bottom: - cap = [j for j in reversed(range(M))] - poly_list.append(cap) + cap_b = np.flip( np.arange(M) ) + hs_faces_list.append(cap_b) if cap_top: - cap = [(P - 1) * M + j for j in range(M)] - poly_list.append(cap) - - return poly_list + cap_t = np.arange(M)+(N1-1)*N2 + hs_faces_list.append(cap_t) + return hs_faces_list class SvSuperEllipsoidNode(SverchCustomTreeNode, bpy.types.Node): """ @@ -272,12 +330,14 @@ def process(self): if verts_output_linked: verts = make_verts(sx, sy, sz, xp, xm, np, nm) verts_list.append(verts) - if edges_output_linked: - edges = make_edges(np, nm) - edges_list.append(edges) - if polys_output_linked: - polys = make_polys(np, nm, self.cap_bottom, self.cap_top) - polys_list.append(polys) + + if edges_output_linked or polys_output_linked: + edges, polys = make_edges_polys(edges_output_linked, polys_output_linked, np, nm, self.cap_bottom, self.cap_top) + + if edges_output_linked: + edges_list.append(edges) + if polys_output_linked: + polys_list.append(polys) # outputs if verts_output_linked: diff --git a/nodes/generators_extended/torus_knot_mk2.py b/nodes/generators_extended/torus_knot_mk2.py index 6a3bf002f1..e3a7258be2 100644 --- a/nodes/generators_extended/torus_knot_mk2.py +++ b/nodes/generators_extended/torus_knot_mk2.py @@ -27,7 +27,7 @@ from sverchok.node_tree import SverchCustomTreeNode from sverchok.data_structure import updateNode, match_long_repeat, get_edge_loop from sverchok.utils.sv_transform_helper import AngleUnits, SvAngleHelper - +import numpy as np def make_torus_knot(flags, settings, link_index=0): ''' @@ -94,73 +94,66 @@ def make_torus_knot(flags, settings, link_index=0): r_phase += 2 * pi * p * shift s_phase += 2 * pi * q * shift - # create the list of verts, edges, normals and tangents for the current link - verts = [] - edges = [] - norms = [] - tangs = [] - - for n in range(N): - # t = 2*pi / links * n/N with: da = 2*pi/links/N => t = n * da - t = n * da - theta = p * t + r_phase # revolution angle - phi = q * t + s_phase # spin angle - - # cache values to improve performance - sin_theta = sin(theta) - cos_theta = cos(theta) - sin_phi = sin(phi) - cos_phi = cos(phi) - - # compute vertex coordinates - x = (R + r * cos_phi) * cos_theta - y = (R + r * cos_phi) * sin_theta - z = r * sin_phi * h - - # append VERTEX - verts.append([x, y, z]) - - # append NORMAL - if compute_normals: - nx = x - R * cos_theta - ny = y - R * sin_theta - nz = z - if normalize_normals: - nn = sqrt(nx * nx + ny * ny + nz * nz) - if nn == 0: - norm = [nx, ny, nz] - else: - norm = [nx / nn, ny / nn, nz / nn] # normalize the normal - else: - norm = [nx, ny, nz] - - norms.append(norm) - - # append TANGENT - if compute_tangents: - qxr, pxr, pxR = [q * r, p * r, p * R] # cached for performance - tx = a * (- qxr * sin_phi * cos_theta - - pxr * cos_phi * sin_theta - - pxR * sin_theta) - ty = a * (- qxr * sin_phi * sin_theta - + pxr * cos_phi * cos_theta - + pxR * cos_theta) - tz = a * qxr * h * cos_phi - if normalize_tangents: - tn = sqrt(tx * tx + ty * ty + tz * tz) - if tn == 0: - tang = [tx, ty, tz] - else: - tang = [tx / tn, ty / tn, tz / tn] # normalize the tangent - else: - tang = [tx, ty, tz] - - tangs.append(tang) + _n = np.arange(N, dtype=np.int32) + _t = _n*da + _theta = p * _t + r_phase # revolution angle + _phi = q * _t + s_phase # spin angle + _sin_theta = np.sin(_theta) + _cos_theta = np.cos(_theta) + _sin_phi = np.sin(_phi) + _cos_phi = np.cos(_phi) + + # compute vertex coordinates + _x = (R + r * _cos_phi) * _cos_theta + _y = (R + r * _cos_phi) * _sin_theta + _z = r * _sin_phi * h + + _verts = np.dstack((_x, _y, _z)).reshape(-1, 3) + + _list_norm = [] + # append NORMAL + if compute_normals: + _nx = _x - R * _cos_theta + _ny = _y - R * _sin_theta + _nz = _z + if normalize_normals: + _nn = np.sqrt ( _nx*_nx + _ny*_ny + _nz*_nz) + _norm_x = np.where ( _nn==0, _nx, _nx/_nn ) + _norm_y = np.where ( _nn==0, _ny, _ny/_nn ) + _norm_z = np.where ( _nn==0, _nz, _nz/_nn ) + _norm = np.dstack( (_norm_x, _norm_y, _norm_z)).reshape(-1,3) + else: + _norm = np.dstack((_nx, _ny, _nz)).reshape(-1, 3) + _list_norm = _norm.tolist() + + _list_tange = [] + # append TANGENT + if compute_tangents: + qxr, pxr, pxR = [q * r, p * r, p * R] # cached for performance + _tx = a * ( - qxr * _sin_phi * _cos_theta + - pxr * _cos_phi * _sin_theta + - pxR * _sin_theta) + _ty = a * ( - qxr * _sin_phi * _sin_theta + + pxr * _cos_phi * _cos_theta + + pxR * _cos_theta) + _tz = a * qxr * h * _cos_phi + if normalize_tangents: + _tn = np.sqrt (_tx*_tx + _ty*_ty + _tz*_tz) + _tang_x = np.where ( _tn==0, _tx, _tx/_tn ) # normalize the tangent + _tang_y = np.where ( _tn==0, _ty, _ty/_tn ) # normalize the tangent + _tang_z = np.where ( _tn==0, _tz, _tz/_tn ) # normalize the tangent + _tang = np.dstack((_tang_x, _tang_y, _tang_z)).reshape(-1, 3) + else: + _tang = np.dstack((_tx, _ty, _tz)).reshape(-1, 3) + _list_tange = _tang.tolist() - # generate the EDGEs - edges = get_edge_loop(N) + _verts_indexes = np.arange(N, dtype=np.int32) + _edges = np.column_stack( (_verts_indexes, np.roll(_verts_indexes, -1)) ) - return verts, edges, norms, tangs + _list_verts = _verts.tolist() + _list_edges = _edges.tolist() + + return _list_verts, _list_edges, _list_norm, _list_tange class SvTorusKnotNodeMK2(SverchCustomTreeNode, bpy.types.Node, SvAngleHelper):