From 4322662218c855d58b4e4a39f51039f1aa88dee0 Mon Sep 17 00:00:00 2001 From: ly29 Date: Fri, 3 Jan 2014 19:27:20 +0100 Subject: [PATCH 01/18] New Node: SortCircleNode Sorts input geometry according to topology to make external topology more useful. Still has some problems. --- __init__.py | 4 ++ node_SortCircle.py | 112 +++++++++++++++++++++++++++++++++++++++++++++ node_s.py | 1 + 3 files changed, 117 insertions(+) create mode 100644 node_SortCircle.py diff --git a/__init__.py b/__init__.py index 97557b9b8..200f03db8 100755 --- a/__init__.py +++ b/__init__.py @@ -107,6 +107,7 @@ if "bpy" in locals(): imp.reload(node_LineConnect) imp.reload(node_Area) imp.reload(node_Range) + imp.reload(node_SortCircle) else: import node_s import node_ScalarMath @@ -161,6 +162,7 @@ else: import node_LineConnect import node_Area import node_Range + import node_SortCircle def register(): import bpy @@ -216,6 +218,7 @@ def register(): node_LineConnect.register() node_Area.register() node_Range.register() + node_SortCircle.register() if 'SVERCHOK' not in nodeitems_utils._node_categories: nodeitems_utils.register_node_categories("SVERCHOK", node_s.make_categories()) @@ -225,6 +228,7 @@ def unregister(): import bpy import nodeitems_utils + node_SortCircle.unregister() node_Range.unregister() node_Area.unregister() node_LineConnect.unregister() diff --git a/node_SortCircle.py b/node_SortCircle.py new file mode 100644 index 000000000..5849057a4 --- /dev/null +++ b/node_SortCircle.py @@ -0,0 +1,112 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +import bpy +from node_s import * +from util import * + + +class SortCircleNode(Node, SverchCustomTreeNode): + ''' SortCircleNode ''' + bl_idname = 'SortCircleNode' + bl_label = 'Sort Circle' + bl_icon = 'OUTLINER_OB_EMPTY' + + is_circle = bpy.props.BoolProperty(name='is_circle', default=True, update=updateNode) + + def draw_buttons(self, context, layout): + layout.prop(self, "is_circle", text="Circle?") + + def init(self, context): + self.inputs.new('VerticesSocket', 'vertices', 'vertices') + self.inputs.new('StringsSocket', 'edges', "edges") + self.outputs.new('VerticesSocket', 'vertices', 'vertices') + self.outputs.new('StringsSocket', 'edges', 'edges') + + + def update(self): + edge_list, vert_list = [],[] + if 'vertices' in self.inputs and self.inputs['vertices'].links and \ + type(self.inputs['vertices'].links[0].from_socket) == VerticesSocket: + if not self.inputs['vertices'].node.socket_value_update: + self.inputs['vertices'].node.update() + vert_list = eval(self.inputs['vertices'].links[0].from_socket.VerticesProperty)[0] + + if 'edges' in self.inputs and self.inputs['edges'].links and \ + type(self.inputs['edges'].links[0].from_socket) == StringsSocket: + if not self.inputs['edges'].node.socket_value_update: + self.inputs['edges'].node.update() + edge_list = eval(self.inputs['edges'].links[0].from_socket.StringsProperty)[0] + + # print("edges:",edge_list,"verts:",vert_list) + + # for i in vert_list: + # vert_list[i]=self.topologySort(vert_list[i],edge_list[i]) + + #print("edges:",edge_list,"verts:",vert_list) + + if 'vertices' in self.outputs and len(self.outputs['vertices'].links)>0: + if not self.outputs['vertices'].node.socket_value_update: + self.outputs['vertices'].node.update() + if len(vert_list) and len(edge_list): + vert_res = self.topologySort( vert_list, edge_list) + self.outputs['vertices'].VerticesProperty = str([vert_res]) + + + if 'edges' in self.outputs and len(self.outputs['edges'].links)>0: + if not self.outputs['edges'].node.socket_value_update: + self.outputs['edges'].node.update() + l=len(vert_res[0]) + if l: + if self.is_circle: + edge_res = [[i,(i+1)%l] for i in range(l)] + else: + edge_res = [[i,(i+1)%l] for i in range(l - 1)] + self.outputs['edges'].StringsProperty = str([edge_res]) + + + +# take a list of verts and edges and sorts them according according to the visual order +# makes order of the geometry follow the topology of the edges +# vert_list, edge_list + + def topologySort(self, v_l , e_l): + l,l1 = e_l[0] + res = [l] + for j in range(len(v_l)-1): + tmp=[e_l[i] for i in range(len(e_l)) if l in e_l[i] and not l1 in e_l[i] ][0] + if tmp[0] in res: + res.append(tmp[1]) + l1,l = l, tmp[1] + else: + res.append(tmp[0]) + l1,l = l, tmp[0] + + v_res = [v_l[i] for i in res] + return v_res + + + +def register(): + bpy.utils.register_class(SortCircleNode) + +def unregister(): + bpy.utils.unregister_class(SortCircleNode) + +if __name__ == "__main__": + register() diff --git a/node_s.py b/node_s.py index 41d9112bc..e2e47c8f8 100755 --- a/node_s.py +++ b/node_s.py @@ -175,6 +175,7 @@ def make_categories(): NodeItem("AdaptivePolsNode", label="Adaptive Polygons"), NodeItem("CrossSectionNode", label="Cross Section"), NodeItem("LineConnectNode", label="Lines Connection"), + NodeItem("SortCircleNode", label="Topology Sort: Circle") ]), ] return node_categories -- GitLab From c07e91f96f07ea6f9ddf14f9fe94460f1a3b19eb Mon Sep 17 00:00:00 2001 From: ly29 Date: Sat, 4 Jan 2014 09:33:58 +0100 Subject: [PATCH 02/18] Works with more than one object Added per face mode. Either calculate the area for each face in an object or the sum of all faces in object. --- node_Area.py | 57 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/node_Area.py b/node_Area.py index fe9e3cded..9fecfccd7 100644 --- a/node_Area.py +++ b/node_Area.py @@ -1,6 +1,7 @@ import bpy from node_s import * from util import * +import math class AreaNode(Node, SverchCustomTreeNode): ''' Area ''' @@ -8,39 +9,57 @@ class AreaNode(Node, SverchCustomTreeNode): bl_label = 'Area' bl_icon = 'OUTLINER_OB_EMPTY' + per_face = bpy.props.BoolProperty(name='per_face', default=True, update=updateNode) + + def init(self, context): self.inputs.new('VerticesSocket', "Vertices", "Vertices") self.inputs.new('StringsSocket', "Polygons", "Polygons") self.outputs.new('StringsSocket', "Area", "Area") - + + def draw_buttons(self, context, layout): + layout.prop(self, "per_face", text="per face") + def update(self): # inputs - if self.inputs['Vertices'].links and self.inputs['Vertices'].links: - if self.inputs['Vertices'].links and len(self.inputs['Vertices'].links)>0: - if not self.inputs['Vertices'].node.socket_value_update: - self.inputs['Vertices'].node.update() - if self.inputs['Vertices'].links[0].from_socket.VerticesProperty: - Vertices = eval(self.inputs['Vertices'].links[0].from_socket.VerticesProperty)[0] - + if 'Vertices' in self.inputs and len(self.inputs['Vertices'].links)>0: + if not self.inputs['Vertices'].node.socket_value_update: + self.inputs['Vertices'].node.update() + if self.inputs['Vertices'].links[0].from_socket.VerticesProperty: + Vertices = eval(self.inputs['Vertices'].links[0].from_socket.VerticesProperty) + else: + Vertices = [] if len(self.inputs['Polygons'].links)>0: if not self.inputs['Polygons'].node.socket_value_update: self.inputs['Polygons'].node.update() - Polygons = eval(self.inputs['Polygons'].links[0].from_socket.StringsProperty)[0] - + Polygons = eval(self.inputs['Polygons'].links[0].from_socket.StringsProperty) + else: + Polygons = [] # outputs if 'Area' in self.outputs and len(self.outputs['Area'].links)>0: - if not self.outputs['Area'].node.socket_value_update: + if not self.outputs['Area'].node.socket_value_update: self.outputs['Area'].node.update() - areas = [] - for i in range(len(Polygons)): - poly = [] - for j in Polygons[i]: - poly.append(Vertices[j]) - areas.append(round(self.area(poly),10)) - - self.outputs['Area'].StringsProperty = str([areas]) + areas = [] + for i, obj in enumerate(Polygons): + print(i, obj) + res = [] + for face in obj: + print(face) + poly = [] + for j in face: + print(i,j,":",Vertices) + poly.append(Vertices[i][j]) + res.append(self.area(poly)) + print("res",res) + if self.per_face: + areas.append([math.fsum(res)]) + else: + areas.append(res) + + print("areas",areas,"j",j,"i",i) + self.outputs['Area'].StringsProperty = str([areas]) #determinant of matrix a def det(self, a): -- GitLab From 7ed1ce2fbe15465222abadf926fad33cf47e9dc4 Mon Sep 17 00:00:00 2001 From: ly29 Date: Sat, 4 Jan 2014 09:34:27 +0100 Subject: [PATCH 03/18] Works with more than object --- node_SortCircle.py | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/node_SortCircle.py b/node_SortCircle.py index 5849057a4..b78186db9 100644 --- a/node_SortCircle.py +++ b/node_SortCircle.py @@ -45,38 +45,35 @@ class SortCircleNode(Node, SverchCustomTreeNode): type(self.inputs['vertices'].links[0].from_socket) == VerticesSocket: if not self.inputs['vertices'].node.socket_value_update: self.inputs['vertices'].node.update() - vert_list = eval(self.inputs['vertices'].links[0].from_socket.VerticesProperty)[0] + vert_list = eval(self.inputs['vertices'].links[0].from_socket.VerticesProperty) if 'edges' in self.inputs and self.inputs['edges'].links and \ type(self.inputs['edges'].links[0].from_socket) == StringsSocket: if not self.inputs['edges'].node.socket_value_update: self.inputs['edges'].node.update() - edge_list = eval(self.inputs['edges'].links[0].from_socket.StringsProperty)[0] - - # print("edges:",edge_list,"verts:",vert_list) - - # for i in vert_list: - # vert_list[i]=self.topologySort(vert_list[i],edge_list[i]) - - #print("edges:",edge_list,"verts:",vert_list) - + edge_list = eval(self.inputs['edges'].links[0].from_socket.StringsProperty) + if 'vertices' in self.outputs and len(self.outputs['vertices'].links)>0: if not self.outputs['vertices'].node.socket_value_update: - self.outputs['vertices'].node.update() + self.outputs['vertices'].node.update() + vert_res=[] if len(vert_list) and len(edge_list): - vert_res = self.topologySort( vert_list, edge_list) + for i in range(len(vert_list)): + vert_res.append( self.topologySort( vert_list[i], edge_list[i])) self.outputs['vertices'].VerticesProperty = str([vert_res]) - + if 'edges' in self.outputs and len(self.outputs['edges'].links)>0: if not self.outputs['edges'].node.socket_value_update: self.outputs['edges'].node.update() - l=len(vert_res[0]) - if l: - if self.is_circle: - edge_res = [[i,(i+1)%l] for i in range(l)] - else: - edge_res = [[i,(i+1)%l] for i in range(l - 1)] + edge_res = [] + for v_l in vert_list: + l=len(v_l) + if l: + if self.is_circle: + edge_res.append( [[i,(i+1)%l] for i in range(l)] ) + else: + edge_res.append( [[i,(i+1)%l] for i in range(l - 1)]) self.outputs['edges'].StringsProperty = str([edge_res]) -- GitLab From af503ccab49ce699b8406f3f6cc5211baba3c9e2 Mon Sep 17 00:00:00 2001 From: ly29 Date: Sat, 4 Jan 2014 09:53:47 +0100 Subject: [PATCH 04/18] Small fix removed print statements --- node_Area.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/node_Area.py b/node_Area.py index 9fecfccd7..258d26fb0 100644 --- a/node_Area.py +++ b/node_Area.py @@ -43,22 +43,18 @@ class AreaNode(Node, SverchCustomTreeNode): self.outputs['Area'].node.update() areas = [] for i, obj in enumerate(Polygons): - print(i, obj) res = [] for face in obj: - print(face) poly = [] for j in face: - print(i,j,":",Vertices) poly.append(Vertices[i][j]) res.append(self.area(poly)) - print("res",res) + if self.per_face: - areas.append([math.fsum(res)]) + areas.append(math.fsum(res)) else: areas.append(res) - - print("areas",areas,"j",j,"i",i) + self.outputs['Area'].StringsProperty = str([areas]) #determinant of matrix a -- GitLab From a52cfc6b0b3915adce5d1c0cebb14d311d9e2fd7 Mon Sep 17 00:00:00 2001 From: ly29 Date: Sat, 4 Jan 2014 17:05:39 +0100 Subject: [PATCH 05/18] Working But very ugly code. --- node_SortCircle.py | 66 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/node_SortCircle.py b/node_SortCircle.py index b78186db9..e1a6e625e 100644 --- a/node_SortCircle.py +++ b/node_SortCircle.py @@ -59,8 +59,9 @@ class SortCircleNode(Node, SverchCustomTreeNode): vert_res=[] if len(vert_list) and len(edge_list): for i in range(len(vert_list)): - vert_res.append( self.topologySort( vert_list[i], edge_list[i])) - self.outputs['vertices'].VerticesProperty = str([vert_res]) + vert_res.append(self.topologySort( vert_list[i], edge_list[i])) + #print(len(vert_res)!=len(vert_list[i])) + self.outputs['vertices'].VerticesProperty = str(vert_res) if 'edges' in self.outputs and len(self.outputs['edges'].links)>0: @@ -73,30 +74,73 @@ class SortCircleNode(Node, SverchCustomTreeNode): if self.is_circle: edge_res.append( [[i,(i+1)%l] for i in range(l)] ) else: - edge_res.append( [[i,(i+1)%l] for i in range(l - 1)]) - self.outputs['edges'].StringsProperty = str([edge_res]) + edge_res.append( [[i,i+1] for i in range(l - 1)]) + self.outputs['edges'].StringsProperty = str(edge_res) # take a list of verts and edges and sorts them according according to the visual order # makes order of the geometry follow the topology of the edges # vert_list, edge_list +# [(0, 1),(0, 2),(2, 3),(3, 4),(4, 5),(5, 6),(6, 7)] +# feels like there should be a simpler way... this a bit brute force. - def topologySort(self, v_l , e_l): + def topologySort(self, v_l , e_l): + if len(v_l) != len(e_l): + print(len(v_l),len(e_l)) + return self.topologySort2(v_l,e_l) l,l1 = e_l[0] res = [l] for j in range(len(v_l)-1): - tmp=[e_l[i] for i in range(len(e_l)) if l in e_l[i] and not l1 in e_l[i] ][0] - if tmp[0] in res: - res.append(tmp[1]) - l1,l = l, tmp[1] + tmp=[e_l[i] for i in range(len(e_l)) if l in e_l[i] and not l1 in e_l[i] ] + if len(tmp) == 0: + break + if tmp[0][0] in res: + res.append(tmp[0][1]) + l1,l = l, tmp[0][1] else: - res.append(tmp[0]) - l1,l = l, tmp[0] + res.append(tmp[0][0]) + l1,l = l, tmp[0][0] v_res = [v_l[i] for i in res] return v_res + def topologySort2(self, v_l , e_l): + l,l1 = e_l[0] + res = [l,l1] + + for j in range(len(v_l)): + tmp=[e_l[i] for i in range(len(e_l)) if l in e_l[i] and not l1 in e_l[i] ] + if not len(tmp): + break + if tmp[0][0] in res: + res.append(tmp[0][1]) + l1,l = l, tmp[0][1] + else: + res.append(tmp[0][0]) + l1,l = l, tmp[0][0] + print("res:",len(res),":", len(v_l),":", res ,":",e_l ) + if len(res) != len(v_l): + #unsorted = [i for i in range(len(v_l)) if not i in res] + print("kanske") + l = res[0] + + while len(res) < len(v_l): #non cyclic + tmp=[e_l[i] for i in range(len(e_l)) if l in e_l[i]] + print(tmp) + for i in tmp: + if not (i[0] in res and i[1] in res): + if l == i[0]: + l = i[1] + else: + l = i[0] + res.insert(0,l) + + print("res:",len(res),":", len(v_l),":", res) + + v_res = [v_l[i] for i in res] + return v_res + def register(): -- GitLab From b80871460c89c2b5323a108ce6278366a9b4fcec Mon Sep 17 00:00:00 2001 From: ly29 Date: Sat, 4 Jan 2014 19:24:31 +0100 Subject: [PATCH 06/18] Multiple objects Correct output format, can calculate area per object or per face in object. --- node_Area.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/node_Area.py b/node_Area.py index 258d26fb0..7d3895d50 100644 --- a/node_Area.py +++ b/node_Area.py @@ -45,15 +45,15 @@ class AreaNode(Node, SverchCustomTreeNode): for i, obj in enumerate(Polygons): res = [] for face in obj: - poly = [] + poly = [] for j in face: poly.append(Vertices[i][j]) res.append(self.area(poly)) - + if self.per_face: - areas.append(math.fsum(res)) + areas.extend(res) else: - areas.append(res) + areas.append(math.fsum(res)) self.outputs['Area'].StringsProperty = str([areas]) @@ -64,14 +64,14 @@ class AreaNode(Node, SverchCustomTreeNode): #unit normal vector of plane defined by points a, b, and c def unit_normal(self, a, b, c): x = self.det([[1,a[1],a[2]], - [1,b[1],b[2]], - [1,c[1],c[2]]]) + [1,b[1],b[2]], + [1,c[1],c[2]]]) y = self.det([[a[0],1,a[2]], - [b[0],1,b[2]], - [c[0],1,c[2]]]) + [b[0],1,b[2]], + [c[0],1,c[2]]]) z = self.det([[a[0],a[1],1], - [b[0],b[1],1], - [c[0],c[1],1]]) + [b[0],b[1],1], + [c[0],c[1],1]]) magnitude = (x**2 + y**2 + z**2)**.5 return (x/magnitude, y/magnitude, z/magnitude) -- GitLab From 3dcea0515c68c217e1122f836ae6e97572685f8b Mon Sep 17 00:00:00 2001 From: ly29 Date: Sat, 4 Jan 2014 19:25:08 +0100 Subject: [PATCH 07/18] Removed prints --- node_SortCircle.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/node_SortCircle.py b/node_SortCircle.py index e1a6e625e..ed83fcb33 100644 --- a/node_SortCircle.py +++ b/node_SortCircle.py @@ -87,7 +87,6 @@ class SortCircleNode(Node, SverchCustomTreeNode): def topologySort(self, v_l , e_l): if len(v_l) != len(e_l): - print(len(v_l),len(e_l)) return self.topologySort2(v_l,e_l) l,l1 = e_l[0] res = [l] @@ -121,8 +120,6 @@ class SortCircleNode(Node, SverchCustomTreeNode): l1,l = l, tmp[0][0] print("res:",len(res),":", len(v_l),":", res ,":",e_l ) if len(res) != len(v_l): - #unsorted = [i for i in range(len(v_l)) if not i in res] - print("kanske") l = res[0] while len(res) < len(v_l): #non cyclic @@ -136,7 +133,6 @@ class SortCircleNode(Node, SverchCustomTreeNode): l = i[0] res.insert(0,l) - print("res:",len(res),":", len(v_l),":", res) v_res = [v_l[i] for i in res] return v_res -- GitLab From f8b9866aeec8f27824684c5b6abd8e5a3fa34933 Mon Sep 17 00:00:00 2001 From: ly29 Date: Sun, 5 Jan 2014 18:39:10 +0100 Subject: [PATCH 08/18] Added min, max functions --- node_ScalarMath.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/node_ScalarMath.py b/node_ScalarMath.py index 489575bc6..2de170293 100644 --- a/node_ScalarMath.py +++ b/node_ScalarMath.py @@ -65,10 +65,12 @@ class ScalarMathNode(Node, SverchCustomTreeNode): ("SUB", "-", ""), ("MUL", "*", ""), ("DIV", "/", ""), - ("INTDIV", "//", ""), + ("INTDIV", "//", ""), ("POW", "**", ""), ("PI", "pi", ""), ("E", "e", ""), + ("MIN", "min", ""), + ("MAX", "max", ""), ] @@ -118,7 +120,9 @@ class ScalarMathNode(Node, SverchCustomTreeNode): 'POW': lambda x,y : x**y, 'ROUND': lambda x,y : round(x,y), 'FMOD': lambda x,y : fmod(x,y), - 'MODULO': lambda x,y : x%y + 'MODULO': lambda x,y : x%y, + 'MIN': lambda x,y : min(x,y), + 'MAX': lambda x,y : max(x,y) } constant = { -- GitLab From 7994b2352d4c6f30240ef6e2825f86c40afe1224 Mon Sep 17 00:00:00 2001 From: ly29 Date: Mon, 6 Jan 2014 21:29:13 +0100 Subject: [PATCH 09/18] 2 new functions: Cell and Noise Provides 4 new functions, Vector and Scalar noise from cell and noise (perlin) from mathutils.noise library --- node_VectorMath.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/node_VectorMath.py b/node_VectorMath.py index 4ea48fa21..c13b69b3d 100644 --- a/node_VectorMath.py +++ b/node_VectorMath.py @@ -38,7 +38,11 @@ class VectorMathNode(Node, SverchCustomTreeNode): ("LEN", "Length", ""), ("DISTANCE", "Distance", ""), ("NORMALIZE", "Normalize", ""), - ("NEG", "Negate", ""), + ("NEG", "Negate", ""), + ("NOISE-V", "Noise Vector", ""), + ("NOISE-S", "Noise Scalar", ""), + ("CELL-V", "Vector Cell noise", ""), + ("CELL-S", "Scalar Cell noise", ""), ] @@ -61,7 +65,9 @@ class VectorMathNode(Node, SverchCustomTreeNode): scalar_out = { "DOT" : (lambda u,v : u.dot(v) , 2), "DISTANCE" : (lambda u,v : (u-v).length, 2), - "LEN" : (lambda u : u.length,1) + "LEN" : (lambda u : u.length,1), + "NOISE-S" : (lambda u : mathutils.noise.noise(u), 1), + "CELL-S" : (lambda u : mathutils.noise.cell(u), 1), } vector_out = { @@ -69,7 +75,9 @@ class VectorMathNode(Node, SverchCustomTreeNode): "ADD" : (lambda u,v : u + v, 2), "SUB" : (lambda u,v : u - v, 2), "NORMALIZE" : (lambda u : u.normalized(), 1), - "NEG" : (lambda u : -u, 1) + "NEG" : (lambda u : -u, 1), + "NOISE-V" : (lambda u : mathutils.noise.noise_vector(u), 1), + "CELL-V" : (lambda u : mathutils.noise.cell_vector(u), 1), } # check and adjust outputs and input size @@ -177,8 +185,8 @@ class VectorMathNode(Node, SverchCustomTreeNode): return w.to_tuple() if type(l1) is list and type (l2) is list: max_obj = max(len(l1),len(l2)) - fullList(l1,max_obj) - fullList(l2,max_obj) + fullList(l1, max_obj) + fullList(l2, max_obj) res = [] for i in range(len(l1)): res.append( self.recurse_fxy(l1[i], l2[i],f)) -- GitLab From 3b0c13855694511fee73485117727a10b3603103 Mon Sep 17 00:00:00 2001 From: ly29 Date: Mon, 6 Jan 2014 21:29:55 +0100 Subject: [PATCH 10/18] Updated Min and max functions --- node_ScalarMath.py | 104 +++++++++++++++++++++++---------------------- 1 file changed, 53 insertions(+), 51 deletions(-) diff --git a/node_ScalarMath.py b/node_ScalarMath.py index 2de170293..b889d693f 100644 --- a/node_ScalarMath.py +++ b/node_ScalarMath.py @@ -68,11 +68,54 @@ class ScalarMathNode(Node, SverchCustomTreeNode): ("INTDIV", "//", ""), ("POW", "**", ""), ("PI", "pi", ""), - ("E", "e", ""), - ("MIN", "min", ""), - ("MAX", "max", ""), + ("E", "e", ""), + ("MIN", "min", ""), + ("MAX", "max", ""), ] + fx = { + 'SINE': sin, + 'COSINE': cos, + 'TANGENT': tan, + 'ARCSINE': asin, + 'ARCCOSINE': acos, + 'ARCTANGENT': atan, + 'SQRT': lambda x: sqrt(fabs(x)), + 'NEG': lambda x: -x, + 'DEGREES': degrees, + 'RADIANS': radians, + 'ABS': fabs, + 'FLOOR': floor, + 'CEIL': ceil, + 'EXP': exp, + 'LN': log, + 'LOG1P': log1p, + 'LOG10': log10, + 'ACOSH': acosh, + 'ASINH': asinh, + 'COSH': cosh, + 'SINH': sinh, + 'TANH': tanh + } + + fxy = { + 'ADD': lambda x,y : x+y, + 'SUB': lambda x,y : x-y, + 'DIV': lambda x,y : x/y, + 'INTDIV': lambda x,y : x//y, + 'MUL': lambda x,y : x*y, + 'POW': lambda x,y : x**y, + 'ROUND': lambda x,y : round(x,y), + 'FMOD': lambda x,y : fmod(x,y), + 'MODULO': lambda x,y : x%y, + 'MIN': lambda x,y : min(x,y), + 'MAX': lambda x,y : max(x,y) + } + + constant = { + 'PI': pi, + 'E': e + } items_=bpy.props.EnumProperty( items = mode_items, name="Function", description="Function choice", default="SINE", update=updateNode) @@ -87,56 +130,15 @@ class ScalarMathNode(Node, SverchCustomTreeNode): def update(self): - fx = { - 'SINE': sin, - 'COSINE': cos, - 'TANGENT': tan, - 'ARCSINE': asin, - 'ARCCOSINE': acos, - 'ARCTANGENT': atan, - 'SQRT': lambda x: sqrt(fabs(x)), - 'NEG': lambda x: -x, - 'DEGREES': degrees, - 'RADIANS': radians, - 'ABS': fabs, - 'FLOOR': floor, - 'CEIL': ceil, - 'EXP': exp, - 'LN': log, - 'LOG1P': log1p, - 'LOG10': log10, - 'ACOSH': acosh, - 'ASINH': asinh, - 'COSH': cosh, - 'SINH': sinh, - 'TANH': tanh - } - fxy = { - 'ADD': lambda x,y : x+y, - 'SUB': lambda x,y : x-y, - 'DIV': lambda x,y : x/y, - 'INTDIV': lambda x,y : x//y, - 'MUL': lambda x,y : x*y, - 'POW': lambda x,y : x**y, - 'ROUND': lambda x,y : round(x,y), - 'FMOD': lambda x,y : fmod(x,y), - 'MODULO': lambda x,y : x%y, - 'MIN': lambda x,y : min(x,y), - 'MAX': lambda x,y : max(x,y) - - } - constant = { - 'PI': pi, - 'E': e - } + # inputs nrInputs = 1 - if self.items_ in constant: + if self.items_ in self.constant: nrInputs = 0 - elif self.items_ in fx: + elif self.items_ in self.fx: nrInputs = 1 - elif self.items_ in fxy: + elif self.items_ in self.fxy: nrInputs = 2 self.set_inputs(nrInputs) @@ -166,16 +168,16 @@ class ScalarMathNode(Node, SverchCustomTreeNode): self.outputs['float'].node.update() result = [] if nrInputs == 0: - result = [constant[self.items_]] + result = [self.constant[self.items_]] if nrInputs == 1: if len(Number1): x = eval(Number1) - result = self.recurse_fx(x,fx[self.items_]) + result = self.recurse_fx(x,self.fx[self.items_]) if nrInputs == 2: if len(Number1) and len(Number2): x = eval(Number1) y = eval(Number2) - result = self.recurse_fxy(x,y,fxy[self.items_]) + result = self.recurse_fxy(x,y,self.fxy[self.items_]) self.outputs['float'].StringsProperty = str(result) -- GitLab From 202248864beab929656ca572b7bdcef734819158 Mon Sep 17 00:00:00 2001 From: ly29 Date: Mon, 6 Jan 2014 21:41:17 +0100 Subject: [PATCH 11/18] Vector math Angle functions from vector math library. Same as acos(u.dot(v)) --- node_VectorMath.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/node_VectorMath.py b/node_VectorMath.py index c13b69b3d..2f9a22087 100644 --- a/node_VectorMath.py +++ b/node_VectorMath.py @@ -42,7 +42,8 @@ class VectorMathNode(Node, SverchCustomTreeNode): ("NOISE-V", "Noise Vector", ""), ("NOISE-S", "Noise Scalar", ""), ("CELL-V", "Vector Cell noise", ""), - ("CELL-S", "Scalar Cell noise", ""), + ("CELL-S", "Scalar Cell noise", ""), + ("ANGLE", "Angle", ""), ] @@ -68,6 +69,8 @@ class VectorMathNode(Node, SverchCustomTreeNode): "LEN" : (lambda u : u.length,1), "NOISE-S" : (lambda u : mathutils.noise.noise(u), 1), "CELL-S" : (lambda u : mathutils.noise.cell(u), 1), + "ANGLE" : (lambda u,v : u.angle(v,0),2), + "ANGLE-SIGN": (lambda u,v : u.angle_signed(v,0),2) } vector_out = { -- GitLab From 4546662bad33c1a1d99550e5fd5e1a3619421a99 Mon Sep 17 00:00:00 2001 From: ly29 Date: Mon, 6 Jan 2014 21:43:14 +0100 Subject: [PATCH 12/18] Fixed text so looks right --- node_Random.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node_Random.py b/node_Random.py index 1abc0a0f6..4b44482e4 100755 --- a/node_Random.py +++ b/node_Random.py @@ -19,8 +19,8 @@ class RandomNode(Node, SverchCustomTreeNode): self.outputs.new('StringsSocket', "Random", "Random") def draw_buttons(self, context, layout): - layout.prop(self, "count_inner", text="number") - layout.prop(self, "seed", text="seed") + layout.prop(self, "count_inner", text="Count") + layout.prop(self, "seed", text="Seed") def update(self): -- GitLab From e9b2bccfbba444956773ed656331803f546bb847 Mon Sep 17 00:00:00 2001 From: ly29 Date: Mon, 6 Jan 2014 22:02:33 +0100 Subject: [PATCH 13/18] Voronoi2D and Delaunay Triangulation For testing purposes. Voronoi2D needs polygon output and Delaunay needs edges. --- __init__.py | 8 +- node_Voronoi2D.py | 188 ++++++++++ voronoi.py | 847 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1039 insertions(+), 4 deletions(-) create mode 100644 node_Voronoi2D.py create mode 100644 voronoi.py diff --git a/__init__.py b/__init__.py index 0cffd6433..c60b64ba3 100755 --- a/__init__.py +++ b/__init__.py @@ -96,7 +96,7 @@ if "bpy" in locals(): imp.reload(node_Line) imp.reload(node_Hilbert) imp.reload(node_HilbertImage) - imp.reload(node_Voronoi) + imp.reload(node_Voronoi2D) imp.reload(node_Plane) imp.reload(node_Circle) imp.reload(node_Cylinder) @@ -151,7 +151,7 @@ else: import node_Line import node_Hilbert import node_HilbertImage - import node_Voronoi + import node_Voronoi2D import node_Plane import node_Circle import node_Cylinder @@ -207,7 +207,7 @@ def register(): node_Line.register() node_Hilbert.register() node_HilbertImage.register() - node_Voronoi.register() + node_Voronoi2D.register() node_Plane.register() node_Circle.register() node_Cylinder.register() @@ -239,7 +239,7 @@ def unregister(): node_Cylinder.unregister() node_Circle.unregister() node_Plane.unregister() - node_Voronoi.unregister() + node_Voronoi2D.unregister() node_HilbertImage.unregister() node_Hilbert.unregister() node_Line.unregister() diff --git a/node_Voronoi2D.py b/node_Voronoi2D.py new file mode 100644 index 000000000..c817fd606 --- /dev/null +++ b/node_Voronoi2D.py @@ -0,0 +1,188 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +import bpy +from node_s import * +from util import * +from types import * +from voronoi import Site,computeVoronoiDiagram,computeDelaunayTriangulation + +class Voronoi2DNode(Node, SverchCustomTreeNode): + ''' Voronoi 2d line ''' + bl_idname = 'Voronoi2DNode' + bl_label = 'Voronoi' + bl_icon = 'OUTLINER_OB_EMPTY' + + clip = bpy.props.FloatProperty(name = 'clip', description='Clipping Distance', default=1.0, min=0, options={'ANIMATABLE'}, update=updateNode) + + def init(self, context): + self.inputs.new('VerticesSocket', "Vertices", "Vertices") + #self.inputs.new('StringsSocket', "SizeX", "SizeX") + #self.inputs.new('StringsSocket', "SizeY", "SizeY") + # self.inputs.new('StringsSocket', "Clipping", "Clipping") + self.outputs.new('VerticesSocket', "Vertices", "Vertices") + self.outputs.new('StringsSocket', "Edges", "Edges") +# self.outputs.new('StringsSocket', "Polygons", "Polygons") +# Polygon output does not work right now. + + def draw_buttons(self, context, layout): + layout.prop(self, "clip", text="Clipping") + + def update(self): + # inputs + points_in = [] + if 'Vertices' in self.inputs and len(self.inputs['Vertices'].links)>0: + if not self.inputs['Vertices'].node.socket_value_update: + self.inputs['Vertices'].node.update() + points_in = eval(self.inputs['Vertices'].links[0].from_socket.VerticesProperty) + + pts_out = [] +# polys_out = [] + edges_out = [] + for obj in points_in: + pt_list = [] + x_max = obj[0][0] + x_min = obj[0][0] + y_min = obj[0][1] + y_max = obj[0][1] + #creates points in format for voronoi library, throwing away z + for pt in obj: + x,y = pt[0],pt[1] + x_max = max(x,x_max) + x_min = min(x,x_min) + y_max = max(y,y_max) + y_min = min(x,x_min) + pt_list.append(Site(pt[0],pt[1])) + + res = computeVoronoiDiagram(pt_list) + + edges = res[2] + delta = self.clip + x_max = x_max + delta + y_max = y_max + delta + + x_min = x_min - delta + y_min = y_min - delta + + #clipping box to bounding box I think. + pts_tmp = [] + for pt in res[0]: + x,y=pt[0],pt[1] + if x < x_min: + x = x_min + if x > x_max: + x = x_max + + if y < y_min: + y = y_min + if y > y_max: + y = y_max + pts_tmp.append((x,y,0)) + + pts_out.append(pts_tmp) + + edges_out.append([ (edge[1], edge[2]) for edge in edges if -1 not in edge ]) + + + # outputs + if 'Vertices' in self.outputs and len(self.outputs['Vertices'].links)>0: + if not self.outputs['Vertices'].node.socket_value_update: + self.outputs['Vertices'].node.update() + + self.outputs['Vertices'].VerticesProperty = str(pts_out) + + + + if 'Edges' in self.outputs and len(self.outputs['Edges'].links)>0: + if not self.outputs['Edges'].node.socket_value_update: + self.outputs['Edges'].node.update() + + self.outputs['Edges'].StringsProperty = str(edges_out) + + + +# if 'Polygons' in self.outputs and len(self.outputs['Polygons'].links)>0: +# if not self.outputs['Polygons'].node.socket_value_update: +# self.outputs['Polygons'].node.update() +# self.outputs['Polygons'].StringsProperty=str([polys_out]) + + def update_socket(self, context): + self.update() + +#computeDelaunayTriangulation + +class DelaunayTriangulation2DNode(Node, SverchCustomTreeNode): + ''' DelaunayTriangulation ''' + bl_idname = 'DelaunayTriangulation2DNode' + bl_label = 'Delaunay 2D' + bl_icon = 'OUTLINER_OB_EMPTY' + + + def init(self, context): + self.inputs.new('VerticesSocket', "Vertices", "Vertices") + # self.outputs.new('StringsSocket', "Edges", "Edges") + self.outputs.new('StringsSocket', "Polygons", "Polygons") + + + def update(self): + # inputs + points_in = [] + if 'Vertices' in self.inputs and len(self.inputs['Vertices'].links)>0: + if not self.inputs['Vertices'].node.socket_value_update: + self.inputs['Vertices'].node.update() + points_in = eval(self.inputs['Vertices'].links[0].from_socket.VerticesProperty) + tris_out=[] + edges_out=[] + for obj in points_in: + pt_list = [] + + for pt in obj: + pt_list.append(Site(pt[0],pt[1])) + + # print("obj",obj,pt_list) + res = computeDelaunayTriangulation(pt_list) + + tris_out.append([tri for tri in res if -1 not in tri] ) + + if 'Edges' in self.outputs and len(self.outputs['Edges'].links)>0: + if not self.outputs['Edges'].node.socket_value_update: + self.outputs['Edges'].node.update() + + self.outputs['Edges'].StringsProperty = str(edges_out) + + if 'Polygons' in self.outputs and len(self.outputs['Polygons'].links)>0: + if not self.outputs['Polygons'].node.socket_value_update: + self.outputs['Polygons'].node.update() + + self.outputs['Polygons'].StringsProperty = str(tris_out) + + + + + def update_socket(self, context): + self.update() + +def register(): + bpy.utils.register_class(Voronoi2DNode) + bpy.utils.register_class(DelaunayTriangulation2DNode) +def unregister(): + bpy.utils.unregister_class(Voronoi2DNode) + bpy.utils.unregister_class(DelaunayTriangulation2DNode) + +if __name__ == "__main__": + register() \ No newline at end of file diff --git a/voronoi.py b/voronoi.py new file mode 100644 index 000000000..6ca1e24d9 --- /dev/null +++ b/voronoi.py @@ -0,0 +1,847 @@ +############################################################################# +# +# Voronoi diagram calculator/ Delaunay triangulator +# Translated to Python by Bill Simons +# September, 2005 +# +# Additional changes by Carson Farmer added November 2010 +# +# Calculate Delaunay triangulation or the Voronoi polygons for a set of +# 2D input points. +# +# Derived from code bearing the following notice: +# +# The author of this software is Steven Fortune. Copyright (c) 1994 by AT&T +# Bell Laboratories. +# Permission to use, copy, modify, and distribute this software for any +# purpose without fee is hereby granted, provided that this entire notice +# is included in all copies of any software which is or includes a copy +# or modification of this software and in all copies of the supporting +# documentation for such software. +# THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED +# WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY +# REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY +# OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. +# +# Comments were incorporated from Shane O'Sullivan's translation of the +# original code into C++ (http://mapviewer.skynet.ie/voronoi.html) +# +# Steve Fortune's homepage: http://netlib.bell-labs.com/cm/cs/who/sjf/index.html +# +############################################################################# + +# python 2 to 3 using 2to3 by Linus Yng, added Site.__lt__ and cmp, modified output +# downloaded from http://svn.osgeo.org/qgis/trunk/qgis/python/plugins/fTools/tools/voronoi.py + +def usage(): + print(""" +voronoi - compute Voronoi diagram or Delaunay triangulation + +voronoi [-t -p -d] [filename] + +Voronoi reads from filename (or standard input if no filename given) for a set +of points in the plane and writes either the Voronoi diagram or the Delaunay +triangulation to the standard output. Each input line should consist of two +real numbers, separated by white space. + +If option -t is present, the Delaunay triangulation is produced. +Each output line is a triple i j k, which are the indices of the three points +in a Delaunay triangle. Points are numbered starting at 0. + +If option -t is not present, the Voronoi diagram is produced. +There are four output record types. + +s a b indicates that an input point at coordinates a b was seen. +l a b c indicates a line with equation ax + by = c. +v a b indicates a vertex at a b. +e l v1 v2 indicates a Voronoi segment which is a subsegment of line number l + with endpoints numbered v1 and v2. If v1 or v2 is -1, the line + extends to infinity. + +Other options include: + +d Print debugging info + +p Produce output suitable for input to plot (1), rather than the forms + described above. + +On unsorted data uniformly distributed in the unit square, voronoi uses about +20n+140 bytes of storage. + +AUTHOR +Steve J. Fortune (1987) A Sweepline Algorithm for Voronoi Diagrams, +Algorithmica 2, 153-174. +""") + +############################################################################# +# +# For programmatic use two functions are available: +# +# computeVoronoiDiagram(points) +# +# Takes a list of point objects (which must have x and y fields). +# Returns a 3-tuple of: +# +# (1) a list of 2-tuples, which are the x,y coordinates of the +# Voronoi diagram vertices +# (2) a list of 3-tuples (a,b,c) which are the equations of the +# lines in the Voronoi diagram: a*x + b*y = c +# (3) a list of 3-tuples, (l, v1, v2) representing edges of the +# Voronoi diagram. l is the index of the line, v1 and v2 are +# the indices of the vetices at the end of the edge. If +# v1 or v2 is -1, the line extends to infinity. +# +# computeDelaunayTriangulation(points): +# +# Takes a list of point objects (which must have x and y fields). +# Returns a list of 3-tuples: the indices of the points that form a +# Delaunay triangle. +# +############################################################################# +import math +import sys +import getopt +TOLERANCE = 1e-9 +BIG_FLOAT = 1e38 + + +def cmp(x,y): + return x.__cmp__(y) + +#------------------------------------------------------------------ +class Context(object): + def __init__(self): + self.doPrint = 0 + self.debug = 0 + self.plot = 0 + self.triangulate = False + self.vertices = [] # list of vertex 2-tuples: (x,y) + self.lines = [] # equation of line 3-tuple (a b c), for the equation of the line a*x+b*y = c + self.edges = [] # edge 3-tuple: (line index, vertex 1 index, vertex 2 index) if either vertex index is -1, the edge extends to infiinity + self.triangles = [] # 3-tuple of vertex indices + self.polygons = {} # a dict of site:[edges] pairs + + def circle(self,x,y,rad): + pass + + def clip_line(self,edge): + pass + + def line(self,x0,y0,x1,y1): + pass + + def outSite(self,s): + if(self.debug): + print("site (%d) at %f %f" % (s.sitenum, s.x, s.y)) + elif(self.triangulate): + pass + elif(self.plot): + self.circle (s.x, s.y, cradius) + elif(self.doPrint): + print("s %f %f" % (s.x, s.y)) + + def outVertex(self,s): + self.vertices.append((s.x,s.y)) + if(self.debug): + print("vertex(%d) at %f %f" % (s.sitenum, s.x, s.y)) + elif(self.triangulate): + pass + elif(self.doPrint and not self.plot): + print("v %f %f" % (s.x,s.y)) + + def outTriple(self,s1,s2,s3): + self.triangles.append((s1.sitenum, s2.sitenum, s3.sitenum)) + if(self.debug): + print("circle through left=%d right=%d bottom=%d" % (s1.sitenum, s2.sitenum, s3.sitenum)) + elif(self.triangulate and self.doPrint and not self.plot): + print("%d %d %d" % (s1.sitenum, s2.sitenum, s3.sitenum)) + + def outBisector(self,edge): + self.lines.append((edge.a, edge.b, edge.c)) + if(self.debug): + print("line(%d) %gx+%gy=%g, bisecting %d %d" % (edge.edgenum, edge.a, edge.b, edge.c, edge.reg[0].sitenum, edge.reg[1].sitenum)) + elif(self.triangulate): + if(self.plot): + self.line(edge.reg[0].x, edge.reg[0].y, edge.reg[1].x, edge.reg[1].y) + elif(self.doPrint and not self.plot): + print("l %f %f %f" % (edge.a, edge.b, edge.c)) + + def outEdge(self,edge): + sitenumL = -1 + if edge.ep[Edge.LE] is not None: + sitenumL = edge.ep[Edge.LE].sitenum + sitenumR = -1 + if edge.ep[Edge.RE] is not None: + sitenumR = edge.ep[Edge.RE].sitenum + if edge.reg[0].sitenum not in self.polygons: + self.polygons[edge.reg[0].sitenum] = [] + if edge.reg[1].sitenum not in self.polygons: + self.polygons[edge.reg[1].sitenum] = [] + self.polygons[edge.reg[0].sitenum].append((edge.edgenum,sitenumL,sitenumR)) + self.polygons[edge.reg[1].sitenum].append((edge.edgenum,sitenumL,sitenumR)) + self.edges.append((edge.edgenum,sitenumL,sitenumR)) + if(not self.triangulate): + if self.plot: + self.clip_line(edge) + elif(self.doPrint): + print("e %d" % edge.edgenum, end=' ') + print(" %d " % sitenumL, end=' ') + print("%d" % sitenumR) + +#------------------------------------------------------------------ +def voronoi(siteList,context): + try: + edgeList = EdgeList(siteList.xmin,siteList.xmax,len(siteList)) + priorityQ = PriorityQueue(siteList.ymin,siteList.ymax,len(siteList)) + siteIter = siteList.iterator() + + bottomsite = next(siteIter) + context.outSite(bottomsite) + newsite = next(siteIter) + minpt = Site(-BIG_FLOAT,-BIG_FLOAT) + while True: + if not priorityQ.isEmpty(): + minpt = priorityQ.getMinPt() + + if (newsite and (priorityQ.isEmpty() or cmp(newsite,minpt) < 0)): + # newsite is smallest - this is a site event + context.outSite(newsite) + + # get first Halfedge to the LEFT and RIGHT of the new site + lbnd = edgeList.leftbnd(newsite) + rbnd = lbnd.right + + # if this halfedge has no edge, bot = bottom site (whatever that is) + # create a new edge that bisects + bot = lbnd.rightreg(bottomsite) + edge = Edge.bisect(bot,newsite) + context.outBisector(edge) + + # create a new Halfedge, setting its pm field to 0 and insert + # this new bisector edge between the left and right vectors in + # a linked list + bisector = Halfedge(edge,Edge.LE) + edgeList.insert(lbnd,bisector) + + # if the new bisector intersects with the left edge, remove + # the left edge's vertex, and put in the new one + p = lbnd.intersect(bisector) + if p is not None: + priorityQ.delete(lbnd) + priorityQ.insert(lbnd,p,newsite.distance(p)) + + # create a new Halfedge, setting its pm field to 1 + # insert the new Halfedge to the right of the original bisector + lbnd = bisector + bisector = Halfedge(edge,Edge.RE) + edgeList.insert(lbnd,bisector) + + # if this new bisector intersects with the right Halfedge + p = bisector.intersect(rbnd) + if p is not None: + # push the Halfedge into the ordered linked list of vertices + priorityQ.insert(bisector,p,newsite.distance(p)) + + newsite = next(siteIter) + + elif not priorityQ.isEmpty(): + # intersection is smallest - this is a vector (circle) event + + # pop the Halfedge with the lowest vector off the ordered list of + # vectors. Get the Halfedge to the left and right of the above HE + # and also the Halfedge to the right of the right HE + lbnd = priorityQ.popMinHalfedge() + llbnd = lbnd.left + rbnd = lbnd.right + rrbnd = rbnd.right + + # get the Site to the left of the left HE and to the right of + # the right HE which it bisects + bot = lbnd.leftreg(bottomsite) + top = rbnd.rightreg(bottomsite) + + # output the triple of sites, stating that a circle goes through them + mid = lbnd.rightreg(bottomsite) + context.outTriple(bot,top,mid) + + # get the vertex that caused this event and set the vertex number + # couldn't do this earlier since we didn't know when it would be processed + v = lbnd.vertex + siteList.setSiteNumber(v) + context.outVertex(v) + + # set the endpoint of the left and right Halfedge to be this vector + if lbnd.edge.setEndpoint(lbnd.pm,v): + context.outEdge(lbnd.edge) + + if rbnd.edge.setEndpoint(rbnd.pm,v): + context.outEdge(rbnd.edge) + + + # delete the lowest HE, remove all vertex events to do with the + # right HE and delete the right HE + edgeList.delete(lbnd) + priorityQ.delete(rbnd) + edgeList.delete(rbnd) + + + # if the site to the left of the event is higher than the Site + # to the right of it, then swap them and set 'pm' to RIGHT + pm = Edge.LE + if bot.y > top.y: + bot,top = top,bot + pm = Edge.RE + + # Create an Edge (or line) that is between the two Sites. This + # creates the formula of the line, and assigns a line number to it + edge = Edge.bisect(bot, top) + context.outBisector(edge) + + # create a HE from the edge + bisector = Halfedge(edge, pm) + + # insert the new bisector to the right of the left HE + # set one endpoint to the new edge to be the vector point 'v' + # If the site to the left of this bisector is higher than the right + # Site, then this endpoint is put in position 0; otherwise in pos 1 + edgeList.insert(llbnd, bisector) + if edge.setEndpoint(Edge.RE - pm, v): + context.outEdge(edge) + + # if left HE and the new bisector don't intersect, then delete + # the left HE, and reinsert it + p = llbnd.intersect(bisector) + if p is not None: + priorityQ.delete(llbnd); + priorityQ.insert(llbnd, p, bot.distance(p)) + + # if right HE and the new bisector don't intersect, then reinsert it + p = bisector.intersect(rrbnd) + if p is not None: + priorityQ.insert(bisector, p, bot.distance(p)) + else: + break + + he = edgeList.leftend.right + while he is not edgeList.rightend: + context.outEdge(he.edge) + he = he.right + Edge.EDGE_NUM = 0 + except Exception as err: + print("#Voronoi error#") + print(str(err)) + +#------------------------------------------------------------------ +def isEqual(a,b,relativeError=TOLERANCE): + # is nearly equal to within the allowed relative error + norm = max(abs(a),abs(b)) + return (norm < relativeError) or (abs(a - b) < (relativeError * norm)) + +#------------------------------------------------------------------ +class Site(object): + def __init__(self,x=0.0,y=0.0,sitenum=0): + self.x = x + self.y = y + self.sitenum = sitenum + + def dump(self): + print("Site #%d (%g, %g)" % (self.sitenum,self.x,self.y)) + + + def __lt__(self, other): + if self.y < other.y: + return 1 + elif self.y > other.y: + return 0 + elif self.x < other.x: + return 1 + elif self.x > other.x: + return 0 + + def __str__(self): + return str((self.x,self.y)) + + def __cmp__(self,other): + if self.y < other.y: + return -1 + elif self.y > other.y: + return 1 + elif self.x < other.x: + return -1 + elif self.x > other.x: + return 1 + else: + return 0 + + def distance(self,other): + dx = self.x - other.x + dy = self.y - other.y + return math.sqrt(dx*dx + dy*dy) + +#------------------------------------------------------------------ +class Edge(object): + LE = 0 + RE = 1 + EDGE_NUM = 0 + DELETED = {} # marker value + + def __init__(self): + self.a = 0.0 + self.b = 0.0 + self.c = 0.0 + self.ep = [None,None] + self.reg = [None,None] + self.edgenum = 0 + + def dump(self): + print("(#%d a=%g, b=%g, c=%g)" % (self.edgenum,self.a,self.b,self.c)) + print("ep",self.ep) + print("reg",self.reg) + + def setEndpoint(self, lrFlag, site): + self.ep[lrFlag] = site + if self.ep[Edge.RE - lrFlag] is None: + return False + return True + + @staticmethod + def bisect(s1,s2): + newedge = Edge() + newedge.reg[0] = s1 # store the sites that this edge is bisecting + newedge.reg[1] = s2 + + # to begin with, there are no endpoints on the bisector - it goes to infinity + # ep[0] and ep[1] are None + + # get the difference in x dist between the sites + dx = float(s2.x - s1.x) + dy = float(s2.y - s1.y) + adx = abs(dx) # make sure that the difference in positive + ady = abs(dy) + + # get the slope of the line + newedge.c = float(s1.x * dx + s1.y * dy + (dx*dx + dy*dy)*0.5) + if adx > ady : + # set formula of line, with x fixed to 1 + newedge.a = 1.0 + newedge.b = dy/dx + newedge.c /= dx + else: + # set formula of line, with y fixed to 1 + newedge.b = 1.0 + newedge.a = dx/dy + newedge.c /= dy + + newedge.edgenum = Edge.EDGE_NUM + Edge.EDGE_NUM += 1 + return newedge + + +#------------------------------------------------------------------ +class Halfedge(object): + def __init__(self,edge=None,pm=Edge.LE): + self.left = None # left Halfedge in the edge list + self.right = None # right Halfedge in the edge list + self.qnext = None # priority queue linked list pointer + self.edge = edge # edge list Edge + self.pm = pm + self.vertex = None # Site() + self.ystar = BIG_FLOAT + + def dump(self): + print("Halfedge--------------------------") + print("left: ", self.left) + print("right: ", self.right) + print("edge: ", self.edge) + print("pm: ", self.pm) + print("vertex: ", end=' ') + if self.vertex: self.vertex.dump() + else: print("None") + print("ystar: ", self.ystar) + + + def __cmp__(self,other): + if self.ystar > other.ystar: + return 1 + elif self.ystar < other.ystar: + return -1 + elif self.vertex.x > other.vertex.x: + return 1 + elif self.vertex.x < other.vertex.x: + return -1 + else: + return 0 + + def leftreg(self,default): + if not self.edge: + return default + elif self.pm == Edge.LE: + return self.edge.reg[Edge.LE] + else: + return self.edge.reg[Edge.RE] + + def rightreg(self,default): + if not self.edge: + return default + elif self.pm == Edge.LE: + return self.edge.reg[Edge.RE] + else: + return self.edge.reg[Edge.LE] + + + # returns True if p is to right of halfedge self + def isPointRightOf(self,pt): + e = self.edge + topsite = e.reg[1] + right_of_site = pt.x > topsite.x + + if(right_of_site and self.pm == Edge.LE): + return True + + if(not right_of_site and self.pm == Edge.RE): + return False + + if(e.a == 1.0): + dyp = pt.y - topsite.y + dxp = pt.x - topsite.x + fast = 0; + if ((not right_of_site and e.b < 0.0) or (right_of_site and e.b >= 0.0)): + above = dyp >= e.b * dxp + fast = above + else: + above = pt.x + pt.y * e.b > e.c + if(e.b < 0.0): + above = not above + if (not above): + fast = 1 + if (not fast): + dxs = topsite.x - (e.reg[0]).x + above = e.b * (dxp*dxp - dyp*dyp) < dxs*dyp*(1.0+2.0*dxp/dxs + e.b*e.b) + if(e.b < 0.0): + above = not above + else: # e.b == 1.0 + yl = e.c - e.a * pt.x + t1 = pt.y - yl + t2 = pt.x - topsite.x + t3 = yl - topsite.y + above = t1*t1 > t2*t2 + t3*t3 + + if(self.pm==Edge.LE): + return above + else: + return not above + + #-------------------------- + # create a new site where the Halfedges el1 and el2 intersect + def intersect(self,other): + e1 = self.edge + e2 = other.edge + if (e1 is None) or (e2 is None): + return None + + # if the two edges bisect the same parent return None + if e1.reg[1] is e2.reg[1]: + return None + + d = e1.a * e2.b - e1.b * e2.a + if isEqual(d,0.0): + return None + + xint = (e1.c*e2.b - e2.c*e1.b) / d + yint = (e2.c*e1.a - e1.c*e2.a) / d + if(cmp(e1.reg[1],e2.reg[1]) < 0): + he = self + e = e1 + else: + he = other + e = e2 + + rightOfSite = xint >= e.reg[1].x + if((rightOfSite and he.pm == Edge.LE) or + (not rightOfSite and he.pm == Edge.RE)): + return None + + # create a new site at the point of intersection - this is a new + # vector event waiting to happen + return Site(xint,yint) + + + +#------------------------------------------------------------------ +class EdgeList(object): + def __init__(self,xmin,xmax,nsites): + if xmin > xmax: xmin,xmax = xmax,xmin + self.hashsize = int(2*math.sqrt(nsites+4)) + + self.xmin = xmin + self.deltax = float(xmax - xmin) + self.hash = [None]*self.hashsize + + self.leftend = Halfedge() + self.rightend = Halfedge() + self.leftend.right = self.rightend + self.rightend.left = self.leftend + self.hash[0] = self.leftend + self.hash[-1] = self.rightend + + def insert(self,left,he): + he.left = left + he.right = left.right + left.right.left = he + left.right = he + + def delete(self,he): + he.left.right = he.right + he.right.left = he.left + he.edge = Edge.DELETED + + # Get entry from hash table, pruning any deleted nodes + def gethash(self,b): + if(b < 0 or b >= self.hashsize): + return None + he = self.hash[b] + if he is None or he.edge is not Edge.DELETED: + return he + + # Hash table points to deleted half edge. Patch as necessary. + self.hash[b] = None + return None + + def leftbnd(self,pt): + # Use hash table to get close to desired halfedge + bucket = int(((pt.x - self.xmin)/self.deltax * self.hashsize)) + + if(bucket < 0): + bucket =0; + + if(bucket >=self.hashsize): + bucket = self.hashsize-1 + + he = self.gethash(bucket) + if(he is None): + i = 1 + while True: + he = self.gethash(bucket-i) + if (he is not None): break; + he = self.gethash(bucket+i) + if (he is not None): break; + i += 1 + + # Now search linear list of halfedges for the corect one + if (he is self.leftend) or (he is not self.rightend and he.isPointRightOf(pt)): + he = he.right + while he is not self.rightend and he.isPointRightOf(pt): + he = he.right + he = he.left; + else: + he = he.left + while (he is not self.leftend and not he.isPointRightOf(pt)): + he = he.left + + # Update hash table and reference counts + if(bucket > 0 and bucket < self.hashsize-1): + self.hash[bucket] = he + return he + + +#------------------------------------------------------------------ +class PriorityQueue(object): + def __init__(self,ymin,ymax,nsites): + self.ymin = ymin + self.deltay = ymax - ymin + self.hashsize = int(4 * math.sqrt(nsites)) + self.count = 0 + self.minidx = 0 + self.hash = [] + for i in range(self.hashsize): + self.hash.append(Halfedge()) + + def __len__(self): + return self.count + + def isEmpty(self): + return self.count == 0 + + def insert(self,he,site,offset): + he.vertex = site + he.ystar = site.y + offset + last = self.hash[self.getBucket(he)] + next = last.qnext + while((next is not None) and cmp(he,next) > 0): + last = next + next = last.qnext + he.qnext = last.qnext + last.qnext = he + self.count += 1 + + def delete(self,he): + if (he.vertex is not None): + last = self.hash[self.getBucket(he)] + while last.qnext is not he: + last = last.qnext + last.qnext = he.qnext + self.count -= 1 + he.vertex = None + + def getBucket(self,he): + bucket = int(((he.ystar - self.ymin) / self.deltay) * self.hashsize) + if bucket < 0: bucket = 0 + if bucket >= self.hashsize: bucket = self.hashsize-1 + if bucket < self.minidx: self.minidx = bucket + return bucket + + def getMinPt(self): + while(self.hash[self.minidx].qnext is None): + self.minidx += 1 + he = self.hash[self.minidx].qnext + x = he.vertex.x + y = he.ystar + return Site(x,y) + + def popMinHalfedge(self): + curr = self.hash[self.minidx].qnext + self.hash[self.minidx].qnext = curr.qnext + self.count -= 1 + return curr + + +#------------------------------------------------------------------ +class SiteList(object): + def __init__(self,pointList): + self.__sites = [] + self.__sitenum = 0 + + self.__xmin = pointList[0].x + self.__ymin = pointList[0].y + self.__xmax = pointList[0].x + self.__ymax = pointList[0].y + for i,pt in enumerate(pointList): + self.__sites.append(Site(pt.x,pt.y,i)) + if pt.x < self.__xmin: self.__xmin = pt.x + if pt.y < self.__ymin: self.__ymin = pt.y + if pt.x > self.__xmax: self.__xmax = pt.x + if pt.y > self.__ymax: self.__ymax = pt.y + self.__sites.sort() + + def setSiteNumber(self,site): + site.sitenum = self.__sitenum + self.__sitenum += 1 + + class Iterator(object): + def __init__(this,lst): this.generator = (s for s in lst) + def __iter__(this): return this + def __next__(this): + try: + return next(this.generator) + except StopIteration: + return None + + def iterator(self): + return SiteList.Iterator(self.__sites) + + def __iter__(self): + return SiteList.Iterator(self.__sites) + + def __len__(self): + return len(self.__sites) + + def _getxmin(self): return self.__xmin + def _getymin(self): return self.__ymin + def _getxmax(self): return self.__xmax + def _getymax(self): return self.__ymax + xmin = property(_getxmin) + ymin = property(_getymin) + xmax = property(_getxmax) + ymax = property(_getymax) + + + +# siteList = SiteList(points) +# context = Context() +# voronoi(siteList,context) +# return (context.vertices,context.lines,context.edges) + + + +#------------------------------------------------------------------ +def computeVoronoiDiagram(points): + """ Takes a list of point objects (which must have x and y fields). + Returns a 3-tuple of: + + (1) a list of 2-tuples, which are the x,y coordinates of the + Voronoi diagram vertices + (2) a list of 3-tuples (a,b,c) which are the equations of the + lines in the Voronoi diagram: a*x + b*y = c + (3) a list of 3-tuples, (l, v1, v2) representing edges of the + Voronoi diagram. l is the index of the line, v1 and v2 are + the indices of the vetices at the end of the edge. If + v1 or v2 is -1, the line extends to infinity. + """ + +# siteList = SiteList(points) +# context = Context() +# voronoi(siteList,context) +# return (context.vertices,context.lines,context.edges) + + + siteList = SiteList(points) + context = Context() + context.triangulate = True + voronoi(siteList,context) + return (context.vertices,context.polygons,context.edges) + +#------------------------------------------------------------------ +def computeDelaunayTriangulation(points): + """ Takes a list of point objects (which must have x and y fields). + Returns a list of 3-tuples: the indices of the points that form a + Delaunay triangle. + """ +# original function in comment +# siteList = SiteList(points) +# context = Context() +# context.triangulate = true +# voronoi(siteList,context) +# return context.triangles + + siteList = SiteList(points) + context = Context() + context.triangulate = True + voronoi(siteList,context) + return context.triangles + +#----------------------------------------------------------------------------- +# if __name__=="__main__": +# try: +# optlist,args = getopt.getopt(sys.argv[1:],"thdp") +# except getopt.GetoptError: +# usage() +# sys.exit(2) +# +# doHelp = 0 +# c = Context() +# c.doPrint = 1 +# for opt in optlist: +# if opt[0] == "-d": c.debug = 1 +# if opt[0] == "-p": c.plot = 1 +# if opt[0] == "-t": c.triangulate = 1 +# if opt[0] == "-h": doHelp = 1 +# +# if not doHelp: +# pts = [] +# fp = sys.stdin +# if len(args) > 0: +# fp = open(args[0],'r') +# for line in fp: +# fld = line.split() +# x = float(fld[0]) +# y = float(fld[1]) +# pts.append(Site(x,y)) +# if len(args) > 0: fp.close() +# +# if doHelp or len(pts) == 0: +# usage() +# sys.exit(2) +# +# sl = SiteList(pts) +# voronoi(sl,c) + -- GitLab From 7845598cfe42c6ba57c291735da2a46096bbb200 Mon Sep 17 00:00:00 2001 From: ly29 Date: Mon, 6 Jan 2014 22:43:37 +0100 Subject: [PATCH 14/18] Menu updates Removes CircleSort. Found another bug. Add Voronoi2D and Delaunay --- node_s.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node_s.py b/node_s.py index e2e47c8f8..465551a9d 100755 --- a/node_s.py +++ b/node_s.py @@ -150,7 +150,7 @@ def make_categories(): NodeItem("SphereNode", label="Sphere"), NodeItem("HilbertNode", label="Hilbert"), NodeItem("HilbertImageNode", label="Hilbert image"), - NodeItem("VoronoiNode", label="Voronoi"), + NodeItem("Voronoi2DNode", label="Voronoi"), NodeItem("ImageNode", label="Image"), ]), SverchNodeCategory("SVERCHOK_V", "SVERCHOK vector", items=[ @@ -175,7 +175,7 @@ def make_categories(): NodeItem("AdaptivePolsNode", label="Adaptive Polygons"), NodeItem("CrossSectionNode", label="Cross Section"), NodeItem("LineConnectNode", label="Lines Connection"), - NodeItem("SortCircleNode", label="Topology Sort: Circle") + NodeItem("DelaunayTriangulation2DNode", label="Delaunay Tri 2D ") ]), ] return node_categories -- GitLab From 14916a426060cc40d1092a99e4887e2cff845e89 Mon Sep 17 00:00:00 2001 From: ly29 Date: Mon, 6 Jan 2014 23:03:31 +0100 Subject: [PATCH 15/18] cleanup --- __init__.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/__init__.py b/__init__.py index c60b64ba3..359d6e96a 100755 --- a/__init__.py +++ b/__init__.py @@ -107,7 +107,6 @@ if "bpy" in locals(): imp.reload(node_LineConnect) imp.reload(node_Area) imp.reload(node_Range) - imp.reload(node_SortCircle) else: import node_s import node_ScalarMath @@ -162,7 +161,6 @@ else: import node_LineConnect import node_Area import node_Range - import node_SortCircle def register(): import bpy @@ -218,7 +216,6 @@ def register(): node_LineConnect.register() node_Area.register() node_Range.register() - node_SortCircle.register() if 'SVERCHOK' not in nodeitems_utils._node_categories: nodeitems_utils.register_node_categories("SVERCHOK", node_s.make_categories()) @@ -228,7 +225,6 @@ def unregister(): import bpy import nodeitems_utils - node_SortCircle.unregister() node_Range.unregister() node_Area.unregister() node_LineConnect.unregister() -- GitLab From 8dcaaa8a9bb3bc39ce7a9ec2366f3e3e8a976033 Mon Sep 17 00:00:00 2001 From: ly29 Date: Mon, 6 Jan 2014 23:16:31 +0100 Subject: [PATCH 16/18] Version info We should find a better solution for this. --- README | 2 +- __init__.py | 2 +- node_Tools.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README b/README index b0ab9c0fe..e50df5cad 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -Sverchok parametric tools 0.2.0 +Sverchok parametric tools 0.2.7 Install as blender addon Home http://nikitron.cc.ua/blend_scripts.html diff --git a/__init__.py b/__init__.py index 359d6e96a..bf093e999 100755 --- a/__init__.py +++ b/__init__.py @@ -28,7 +28,7 @@ bl_info = { "name": "Sverchok", "author": "Nedovizin Alexander, Gorodetskiy Nikita", - "version": (0, 2, 6), + "version": (0, 2, 7), "blender": (2, 6, 9), "location": "Nodes > CustomNodesTree > Add user nodes", "description": "Do parametric node-based geometry programming", diff --git a/node_Tools.py b/node_Tools.py index 8b694e5f7..28a071c5a 100644 --- a/node_Tools.py +++ b/node_Tools.py @@ -48,7 +48,7 @@ class SverchokToolsMenu(bpy.types.Panel): box.operator(SverchokUpdateAll.bl_idname, text="UPDATE") box = layout.box() col = box.column(align=True) - col.label(text="Sverchok v_0.2.6") + col.label(text="Sverchok v_0.2.7") row = col.row(align=True) row.operator('wm.url_open', text='Help!').url = 'http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Nodes/Sverchok' row.operator('wm.url_open', text='Home!').url = 'http://nikitron.cc.ua/blend_scripts.html' -- GitLab From 71c530f07fa4651d48301c30bd09ab9d119f26ef Mon Sep 17 00:00:00 2001 From: ly29 Date: Mon, 6 Jan 2014 23:55:49 +0100 Subject: [PATCH 17/18] Text change Removed function added by mistake --- node_Area.py | 2 +- node_VectorMath.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/node_Area.py b/node_Area.py index 7d3895d50..3bffaae7b 100644 --- a/node_Area.py +++ b/node_Area.py @@ -18,7 +18,7 @@ class AreaNode(Node, SverchCustomTreeNode): self.outputs.new('StringsSocket', "Area", "Area") def draw_buttons(self, context, layout): - layout.prop(self, "per_face", text="per face") + layout.prop(self, "per_face", text="Count faces") def update(self): # inputs diff --git a/node_VectorMath.py b/node_VectorMath.py index 2f9a22087..66870c34d 100644 --- a/node_VectorMath.py +++ b/node_VectorMath.py @@ -43,7 +43,7 @@ class VectorMathNode(Node, SverchCustomTreeNode): ("NOISE-S", "Noise Scalar", ""), ("CELL-V", "Vector Cell noise", ""), ("CELL-S", "Scalar Cell noise", ""), - ("ANGLE", "Angle", ""), + ("ANGLE", "Angle", ""), ] @@ -70,7 +70,6 @@ class VectorMathNode(Node, SverchCustomTreeNode): "NOISE-S" : (lambda u : mathutils.noise.noise(u), 1), "CELL-S" : (lambda u : mathutils.noise.cell(u), 1), "ANGLE" : (lambda u,v : u.angle(v,0),2), - "ANGLE-SIGN": (lambda u,v : u.angle_signed(v,0),2) } vector_out = { -- GitLab From ff5c9d7c5f1fc881b5aeb2c4cde939b5d91e777e Mon Sep 17 00:00:00 2001 From: ly29 Date: Tue, 7 Jan 2014 00:00:22 +0100 Subject: [PATCH 18/18] Node not ready --- node_SortCircle.py | 149 --------------------------------------------- 1 file changed, 149 deletions(-) delete mode 100644 node_SortCircle.py diff --git a/node_SortCircle.py b/node_SortCircle.py deleted file mode 100644 index ed83fcb33..000000000 --- a/node_SortCircle.py +++ /dev/null @@ -1,149 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -import bpy -from node_s import * -from util import * - - -class SortCircleNode(Node, SverchCustomTreeNode): - ''' SortCircleNode ''' - bl_idname = 'SortCircleNode' - bl_label = 'Sort Circle' - bl_icon = 'OUTLINER_OB_EMPTY' - - is_circle = bpy.props.BoolProperty(name='is_circle', default=True, update=updateNode) - - def draw_buttons(self, context, layout): - layout.prop(self, "is_circle", text="Circle?") - - def init(self, context): - self.inputs.new('VerticesSocket', 'vertices', 'vertices') - self.inputs.new('StringsSocket', 'edges', "edges") - self.outputs.new('VerticesSocket', 'vertices', 'vertices') - self.outputs.new('StringsSocket', 'edges', 'edges') - - - def update(self): - edge_list, vert_list = [],[] - if 'vertices' in self.inputs and self.inputs['vertices'].links and \ - type(self.inputs['vertices'].links[0].from_socket) == VerticesSocket: - if not self.inputs['vertices'].node.socket_value_update: - self.inputs['vertices'].node.update() - vert_list = eval(self.inputs['vertices'].links[0].from_socket.VerticesProperty) - - if 'edges' in self.inputs and self.inputs['edges'].links and \ - type(self.inputs['edges'].links[0].from_socket) == StringsSocket: - if not self.inputs['edges'].node.socket_value_update: - self.inputs['edges'].node.update() - edge_list = eval(self.inputs['edges'].links[0].from_socket.StringsProperty) - - if 'vertices' in self.outputs and len(self.outputs['vertices'].links)>0: - if not self.outputs['vertices'].node.socket_value_update: - self.outputs['vertices'].node.update() - vert_res=[] - if len(vert_list) and len(edge_list): - for i in range(len(vert_list)): - vert_res.append(self.topologySort( vert_list[i], edge_list[i])) - #print(len(vert_res)!=len(vert_list[i])) - self.outputs['vertices'].VerticesProperty = str(vert_res) - - - if 'edges' in self.outputs and len(self.outputs['edges'].links)>0: - if not self.outputs['edges'].node.socket_value_update: - self.outputs['edges'].node.update() - edge_res = [] - for v_l in vert_list: - l=len(v_l) - if l: - if self.is_circle: - edge_res.append( [[i,(i+1)%l] for i in range(l)] ) - else: - edge_res.append( [[i,i+1] for i in range(l - 1)]) - self.outputs['edges'].StringsProperty = str(edge_res) - - - -# take a list of verts and edges and sorts them according according to the visual order -# makes order of the geometry follow the topology of the edges -# vert_list, edge_list -# [(0, 1),(0, 2),(2, 3),(3, 4),(4, 5),(5, 6),(6, 7)] -# feels like there should be a simpler way... this a bit brute force. - - def topologySort(self, v_l , e_l): - if len(v_l) != len(e_l): - return self.topologySort2(v_l,e_l) - l,l1 = e_l[0] - res = [l] - for j in range(len(v_l)-1): - tmp=[e_l[i] for i in range(len(e_l)) if l in e_l[i] and not l1 in e_l[i] ] - if len(tmp) == 0: - break - if tmp[0][0] in res: - res.append(tmp[0][1]) - l1,l = l, tmp[0][1] - else: - res.append(tmp[0][0]) - l1,l = l, tmp[0][0] - - v_res = [v_l[i] for i in res] - return v_res - - def topologySort2(self, v_l , e_l): - l,l1 = e_l[0] - res = [l,l1] - - for j in range(len(v_l)): - tmp=[e_l[i] for i in range(len(e_l)) if l in e_l[i] and not l1 in e_l[i] ] - if not len(tmp): - break - if tmp[0][0] in res: - res.append(tmp[0][1]) - l1,l = l, tmp[0][1] - else: - res.append(tmp[0][0]) - l1,l = l, tmp[0][0] - print("res:",len(res),":", len(v_l),":", res ,":",e_l ) - if len(res) != len(v_l): - l = res[0] - - while len(res) < len(v_l): #non cyclic - tmp=[e_l[i] for i in range(len(e_l)) if l in e_l[i]] - print(tmp) - for i in tmp: - if not (i[0] in res and i[1] in res): - if l == i[0]: - l = i[1] - else: - l = i[0] - res.insert(0,l) - - - v_res = [v_l[i] for i in res] - return v_res - - - -def register(): - bpy.utils.register_class(SortCircleNode) - -def unregister(): - bpy.utils.unregister_class(SortCircleNode) - -if __name__ == "__main__": - register() -- GitLab