From a259204de47bddef494671477888497efea37140 Mon Sep 17 00:00:00 2001 From: Durman Date: Wed, 20 Dec 2017 11:09:52 +0400 Subject: [PATCH 1/8] Add offset line node in menu --- index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/index.md b/index.md index 0981a1ea5c..38db27cb82 100644 --- a/index.md +++ b/index.md @@ -313,3 +313,4 @@ SvSeparateMeshNodeMK2 SvIndexToMaskNode SvMultiExtrudeAlt + SvOffsetLineNode From 9148c32af33deb75fd59bea820acf3e1738ff9ed Mon Sep 17 00:00:00 2001 From: Durman Date: Wed, 20 Dec 2017 11:12:07 +0400 Subject: [PATCH 2/8] Add new node offset line node. --- nodes/modifier_change/offset_line.py | 210 +++++++++++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 nodes/modifier_change/offset_line.py diff --git a/nodes/modifier_change/offset_line.py b/nodes/modifier_change/offset_line.py new file mode 100644 index 0000000000..e2dae72123 --- /dev/null +++ b/nodes/modifier_change/offset_line.py @@ -0,0 +1,210 @@ +# ##### 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 bpy.props import FloatProperty +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode + + +class SvOffsetLineNode(bpy.types.Node, SverchCustomTreeNode): + """ + Triggers: Offset Line 2D + Tooltip: Offsetting a Line into 2D space + + Only X and Y dimensions of input points will be taken for work. + """ + bl_idname = 'SvOffsetLineNode' + bl_label = 'Offset line' + bl_icon = 'OUTLINER_OB_EMPTY' + + offset = FloatProperty( + name='offset', description='distance of offset', + default=0.1, + options={'ANIMATABLE'}, update=updateNode) + + def sv_init(self, context): + self.inputs.new('VerticesSocket', 'Vers') + self.inputs.new('StringsSocket', "Edgs") + self.inputs.new('StringsSocket', "Offset").prop_name = 'offset' + self.outputs.new('VerticesSocket', 'Vers') + self.outputs.new('StringsSocket', "Faces") + self.outputs.new('StringsSocket', "OuterEdges") + self.outputs.new('StringsSocket', "VersMask") + + def process(self): + + if not ((self.outputs['Vers'].is_linked or self.outputs['Faces'].is_linked + or self.outputs['OuterEdges'].is_linked or self.outputs['VersMask'].is_linked) + and (self.inputs['Vers'].is_linked or self.inputs['Edgs'].is_linked)): + return + + verts_in = self.inputs['Vers'].sv_get()[0] + edges_in = self.inputs['Edgs'].sv_get()[0] + shift = self.inputs['Offset'].sv_get()[0][0] + + # avoid zero offset + if not shift: + return + + verts_out = [] + faces_out = [] + + from mathutils import Vector, Matrix + from math import radians, pi, sin + from mathutils.geometry import normal + + verts_in = [Vector(v).to_2d() for v in verts_in] + + #Searching neighbours for each point + neighbours = [[] for _ in verts_in] + for edg in edges_in: + neighbours[edg[0]].append(edg) + neighbours[edg[1]].append(edg) + + #Convert to from 0 to 2pi angle + def calc_angle(p1,p2): + angle = p1.angle_signed(p2) + return angle + 2 * pi if angle < 0 else angle + + #Sorting neighbours by hour hand + for i, p_neighbs in enumerate(neighbours): + if len(p_neighbs) != 1: + angles = [0] + first_item = list(set(p_neighbs[0]) - set([i]))[0] + for another_neighb in p_neighbs[1:]: + second_item = list(set(another_neighb) - set([i]))[0] + vector1 = verts_in[first_item] - verts_in[i] + vector2 = verts_in[second_item] - verts_in[i] + angles.append(calc_angle(vector1, vector2)) + sorted_ = list(zip(angles, p_neighbs)) + sorted_.sort() + neighbours[i] = [e[1] for e in sorted_] + + def get_end_points(item_point): + second_item = list(set(neighbours[item_point][0]) - set([item_point]))[0] + vert_edg = verts_in[item_point] - verts_in[second_item] + mat_r1 = Matrix.Rotation(radians(-45),2,'X') + mat_r2 = Matrix.Rotation(radians(45),2,'X') + vert_new1 = (vert_edg * mat_r1).normalized() * shift/sin(radians(45)) + verts_in[item_point] + vert_new2 = (vert_edg * mat_r2).normalized() * shift/sin(radians(45)) + verts_in[item_point] + return [vert_new1,vert_new2] + + def get_middle_points(item_point): + points = [] + for i in range(len(neighbours[item_point])): + current_edges = (neighbours[item_point][i:] + neighbours[item_point][:i])[:2] + second_item1 = list(set(current_edges[0]) - set([item_point]))[0] + second_item2 = list(set(current_edges[1]) - set([item_point]))[0] + vert_edg1 = verts_in[second_item1] - verts_in[item_point] + vert_edg2 = verts_in[second_item2] - verts_in[item_point] + angle = calc_angle(vert_edg1, vert_edg2) / 2 + mat_r = Matrix.Rotation(angle, 2, 'X') + points.append((vert_edg1 * mat_r).normalized() * shift/sin(angle) + verts_in[item_point]) + return points + + #Seting points + findex_new_points = [0] + for i in range(len(verts_in)): + if len(neighbours[i]) == 1: + verts_out.extend(get_end_points(i)) + findex_new_points.append(findex_new_points[-1] + 2) + else: + p = get_middle_points(i) + verts_out.extend(p) + findex_new_points.append(findex_new_points[-1] + len(p)) + + findex_new_points = findex_new_points[:-1] + + #Creating faces and mark outer edges and central points + outer_edges = [] + current_index = len(verts_out) - 1 + vers_mask = [0 for _ in verts_out] + nomber_outer_points = current_index + position_old_points = [0 for _ in verts_out] + for edg in edges_in: + need_points = [] + for i in edg: + if len(neighbours[i]) <= 2: + need_points.extend([findex_new_points[i], findex_new_points[i] + 1]) + else: + position = neighbours[i].index(edg) + nomber_points = len(neighbours[i]) + variants_positions = list(range(findex_new_points[i], findex_new_points[i] + nomber_points)) + need_points.extend((variants_positions[position - 1:] + variants_positions[:position - 1])[:2]) + + vec_edg = verts_in[edg[0]] - verts_in[edg[1]] + vec_1 = verts_out[need_points[0]] - verts_in[edg[0]] + vec_2 = verts_out[need_points[1]] - verts_in[edg[0]] + vec_3 = verts_out[need_points[2]] - verts_in[edg[1]] + vec_4 = verts_out[need_points[3]] - verts_in[edg[1]] + new_vecs = [vec_1, vec_2, vec_3, vec_4] + + angles = [vec_edg.angle_signed(vec) for vec in new_vecs] + + if position_old_points[edg[0]] == 0: + verts_out.append(verts_in[edg[0]]) + vers_mask.append(1) + current_index += 1 + position_old_points[edg[0]] = current_index + + if position_old_points[edg[1]] == 0: + verts_out.append(verts_in[edg[1]]) + vers_mask.append(1) + current_index += 1 + position_old_points[edg[1]] = current_index + + n_p = need_points + if angles[0] < 0 and angles[2] < 0 or angles[0] >= 0 and angles[2] >= 0: + new_edges = [[n_p[1], position_old_points[edg[0]], position_old_points[edg[1]], n_p[3]], + [n_p[0], position_old_points[edg[0]], position_old_points[edg[1]], n_p[2]]] + + outer_edges.extend([[n_p[1],n_p[3]],[n_p[0],n_p[2]]]) + else: + new_edges = [[n_p[0], position_old_points[edg[0]], position_old_points[edg[1]], n_p[3]], + [n_p[1], position_old_points[edg[0]], position_old_points[edg[1]], n_p[2]]] + + outer_edges.extend([[n_p[0],n_p[3]],[n_p[1],n_p[2]]]) + + if len(neighbours[edg[0]]) == 1: + new_edges.append([need_points[1], position_old_points[edg[0]], need_points[0]]) + outer_edges.append([need_points[1],need_points[0]]) + if len(neighbours[edg[1]]) == 1: + new_edges.append([need_points[2], position_old_points[edg[1]], need_points[3]]) + outer_edges.append([need_points[2],need_points[3]]) + + for c,face in enumerate(new_edges): + if normal(*[verts_out[i].to_3d() for i in face])[2] < 0: + new_edges[c] = new_edges[c][::-1] + + faces_out.extend(new_edges) + + verts_out = [[i.to_3d()[:] for i in verts_out]] + faces_out = [faces_out] + + self.outputs['Vers'].sv_set(verts_out) + self.outputs['Faces'].sv_set(faces_out) + self.outputs['OuterEdges'].sv_set([outer_edges]) + self.outputs['VersMask'].sv_set([vers_mask]) + +def register(): + bpy.utils.register_class(SvOffsetLineNode) + + +def unregister(): + bpy.utils.unregister_class(SvOffsetLineNode) From 7e6dc6c6978ababa8702769b930edd20e8dfe99d Mon Sep 17 00:00:00 2001 From: Durman Date: Wed, 20 Dec 2017 13:21:19 +0400 Subject: [PATCH 3/8] Documentation of offset line node. --- docs/nodes/alpha_nodes/offset_line.rst | 55 ++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 docs/nodes/alpha_nodes/offset_line.rst diff --git a/docs/nodes/alpha_nodes/offset_line.rst b/docs/nodes/alpha_nodes/offset_line.rst new file mode 100644 index 0000000000..4d52323f6d --- /dev/null +++ b/docs/nodes/alpha_nodes/offset_line.rst @@ -0,0 +1,55 @@ +Offset line +====== + +Functionality +------------- + +This node works only in XY surface. It takes X and Y coordinate from vectors input. Use ``delete loose`` node before if your input mesh has points without edges. You will receive surface along an input mesh edges with width equal offset value. It is also available to receive outer edges and mask of new and old points. + +Inputs +------ + +This node has the following inputs: + +- **Vers** - vertices of objects - work only with first object yet. +- **Edgs** - polygons of objects - the same. +- **offset** - offset values - single value. + +Parameters +---------- + +All parameters can be given by the node or an external input. +``offset`` only one value at the moment. + ++-----------------+---------------+-------------+-------------------------------------------------------------+ +| Param | Type | Default | Description | ++=================+===============+=============+=============================================================+ +| **offset** | Float | 0.10 | offset values. | ++-----------------+---------------+-------------+-------------------------------------------------------------+ + +Outputs +------- + +This node has the following outputs: + +- **Vers** +- **Faces** +- **OuterEdges** - get outer edges, use together with ``delete loose`` node after. The list of edges is unordered. +- **VersMask** - 0 for new points and 1 for old. + +Examples of usage +----------------- + +To receive offset from input object from scene: + +.. image:: https://user-images.githubusercontent.com/28003269/34199193-5e1281a4-e586-11e7-97b8-f1984facdfcb.png + +Using of outer edges: + +.. image:: https://user-images.githubusercontent.com/28003269/34199326-dadbf508-e586-11e7-9542-7b7ff4a9521f.png + +Using of vertices mask with ``transform select`` node. + +.. image:: https://user-images.githubusercontent.com/28003269/34199698-125ed63e-e588-11e7-9e34-83c5eb33cde9.png + + From cd750a6c544eed69bfaf1a6a404af62ba05dccd1 Mon Sep 17 00:00:00 2001 From: Durman Date: Wed, 20 Dec 2017 13:25:03 +0400 Subject: [PATCH 4/8] No need in template now. --- node_scripts/SNLite_templates/line2face.py | 134 --------------------- 1 file changed, 134 deletions(-) delete mode 100644 node_scripts/SNLite_templates/line2face.py diff --git a/node_scripts/SNLite_templates/line2face.py b/node_scripts/SNLite_templates/line2face.py deleted file mode 100644 index e1bee2d36b..0000000000 --- a/node_scripts/SNLite_templates/line2face.py +++ /dev/null @@ -1,134 +0,0 @@ -""" -in verts_in v d=[] n=1 -in edges_in s d=[] n=1 -in shift s d=1.0 n=2 -out verts_out v -out edges_out s -""" - - -''' -Copyright (c) 2017 Durman -SPDX-License-Identifier: GPL3 -License-Filename: LICENSE -''' - - -from mathutils import Vector, Matrix -from math import radians, pi, sin -from mathutils.geometry import normal - -verts_in = [Vector(v).to_2d() for v in verts_in] - -#Searching neighbours for each point -neighbours = [[] for _ in range(len(verts_in))] -for edg in edges_in: - neighbours[edg[0]].append(edg) - neighbours[edg[1]].append(edg) - -#Convert to from 0 to 2pi angle -def calc_angle(p1,p2): - angle = p1.angle_signed(p2) - if angle < 0: - angle += 2*pi - return angle - -#Sorting neighbours by hour hand -for i, p_neighbs in enumerate(neighbours): - if len(p_neighbs) != 1: - angles = [0] - first_item = list(set(p_neighbs[0]) - set([i]))[0] - for another_neighb in p_neighbs[1:]: - second_item = list(set(another_neighb) - set([i]))[0] - vector1 = verts_in[first_item] - verts_in[i] - vector2 = verts_in[second_item] - verts_in[i] - angles.append(calc_angle(vector1, vector2)) - sorted = list(zip(angles, p_neighbs)) - sorted.sort() - neighbours[i] = [e[1] for e in sorted] - -def get_end_points(item_point): - second_item = list(set(neighbours[item_point][0]) - set([item_point]))[0] - vert_edg = verts_in[item_point] - verts_in[second_item] - mat_r1 = Matrix.Rotation(radians(-45),2,'X') - mat_r2 = Matrix.Rotation(radians(45),2,'X') - vert_new1 = (vert_edg * mat_r1).normalized() * shift/sin(radians(45)) + verts_in[item_point] - vert_new2 = (vert_edg * mat_r2).normalized() * shift/sin(radians(45)) + verts_in[item_point] - return [vert_new1,vert_new2] - -def get_middle_points(item_point): - points = [] - for i in range(len(neighbours[item_point])): - current_edges = (neighbours[item_point][i:] + neighbours[item_point][:i])[:2] - second_item1 = list(set(current_edges[0]) - set([item_point]))[0] - second_item2 = list(set(current_edges[1]) - set([item_point]))[0] - vert_edg1 = verts_in[second_item1] - verts_in[item_point] - vert_edg2 = verts_in[second_item2] - verts_in[item_point] - angle = calc_angle(vert_edg1, vert_edg2) / 2 - mat_r = Matrix.Rotation(angle, 2, 'X') - points.append((vert_edg1 * mat_r).normalized() * shift/sin(angle) + verts_in[item_point]) - return points - -#Seting points -findex_new_points = [0] -for i in range(len(verts_in)): - if len(neighbours[i]) == 1: - verts_out.extend(get_end_points(i)) - findex_new_points.append(findex_new_points[-1] + 2) - else: - p = get_middle_points(i) - verts_out.extend(p) - findex_new_points.append(findex_new_points[-1] + len(p)) - -findex_new_points = findex_new_points[:-1] - -#Creating faces -current_index = len(verts_out) - 1 -position_old_points = [0 for _ in range(len(verts_out))] -for edg in edges_in: - need_points = [] - for i in edg: - if len(neighbours[i]) <= 2: - need_points.extend([findex_new_points[i], findex_new_points[i] + 1]) - else: - position = neighbours[i].index(edg) - nomber_points = len(neighbours[i]) - variants_positions = list(range(findex_new_points[i], findex_new_points[i] + nomber_points)) - need_points.extend((variants_positions[position - 1:] + variants_positions[:position - 1])[:2]) - - vec_edg = verts_in[edg[0]] - verts_in[edg[1]] - vec_1 = verts_out[need_points[0]] - verts_in[edg[0]] - vec_2 = verts_out[need_points[1]] - verts_in[edg[0]] - vec_3 = verts_out[need_points[2]] - verts_in[edg[1]] - vec_4 = verts_out[need_points[3]] - verts_in[edg[1]] - new_vecs = [vec_1, vec_2, vec_3, vec_4] - - angles = [] - for vec in new_vecs: - angles.append(vec_edg.angle_signed(vec)) - - if angles[0] < 0 and angles[2] < 0 or angles[0] >= 0 and angles[2] >= 0: - new_edges = [need_points[0],need_points[2],need_points[3],need_points[1]] - else: - new_edges = [need_points[0],need_points[3],need_points[2],need_points[1]] - - if len(neighbours[edg[0]]) > 2: - if position_old_points[edg[0]] == 0: - verts_out.append(verts_in[edg[0]]) - current_index += 1 - position_old_points[edg[0]] = current_index - new_edges.append(position_old_points[edg[0]]) - - if len(neighbours[edg[1]]) > 2: - if position_old_points[edg[1]] == 0: - verts_out.append(verts_in[edg[1]]) - current_index += 1 - position_old_points[edg[1]] = current_index - new_edges.insert(2, position_old_points[edg[1]]) - - if normal(*[verts_out[i].to_3d() for i in new_edges])[2] < 0: - new_edges = new_edges[::-1] - - edges_out.append(new_edges) - -verts_out = [[i.to_3d()[:] for i in verts_out]] From fc224bd7eaf7d02a35c25220da36031190e04a8c Mon Sep 17 00:00:00 2001 From: Durman Date: Fri, 22 Dec 2017 08:53:56 +0400 Subject: [PATCH 5/8] Update offset_line.py --- nodes/modifier_change/offset_line.py | 320 +++++++++++++++------------ 1 file changed, 173 insertions(+), 147 deletions(-) diff --git a/nodes/modifier_change/offset_line.py b/nodes/modifier_change/offset_line.py index e2dae72123..bf2fc6432d 100644 --- a/nodes/modifier_change/offset_line.py +++ b/nodes/modifier_change/offset_line.py @@ -16,11 +16,157 @@ # # ##### END GPL LICENSE BLOCK ##### +from math import radians, pi, sin + import bpy from bpy.props import FloatProperty +from mathutils import Vector, Matrix +from mathutils.geometry import normal + from sverchok.node_tree import SverchCustomTreeNode from sverchok.data_structure import updateNode +TWO_PI = 2 * pi + +def calc_angle(p1,p2): + #Convert to from 0 to 2pi angle + angle = p1.angle_signed(p2) + return angle + TWO_PI if angle < 0 else angle + +def offset_edges(verts_in, edges_in, shift): + # take an input mesh (verts + edges ) and an offset property and generate the resulting geometry + + verts_out = [] + faces_out = [] + + verts_in = [Vector(v).to_2d() for v in verts_in] + + #Searching neighbours for each point + neighbours = [[] for _ in verts_in] + for edg in edges_in: + neighbours[edg[0]].append(edg) + neighbours[edg[1]].append(edg) + + #Convert to from 0 to 2pi angle + def calc_angle(p1,p2): + angle = p1.angle_signed(p2) + return angle + 2 * pi if angle < 0 else angle + + #Sorting neighbours by hour hand + for i, p_neighbs in enumerate(neighbours): + if len(p_neighbs) != 1: + angles = [0] + first_item = list(set(p_neighbs[0]) - set([i]))[0] + for another_neighb in p_neighbs[1:]: + second_item = list(set(another_neighb) - set([i]))[0] + vector1 = verts_in[first_item] - verts_in[i] + vector2 = verts_in[second_item] - verts_in[i] + angles.append(calc_angle(vector1, vector2)) + sorted_ = list(zip(angles, p_neighbs)) + sorted_.sort() + neighbours[i] = [e[1] for e in sorted_] + + shift_end_points = shift/sin(radians(45)) + def get_end_points(item_point): + second_item = list(set(neighbours[item_point][0]) - set([item_point]))[0] + vert_edg = verts_in[item_point] - verts_in[second_item] + mat_r1 = Matrix.Rotation(radians(-45),2,'X') + mat_r2 = Matrix.Rotation(radians(45),2,'X') + vert_new1 = (vert_edg * mat_r1).normalized() * shift_end_points + verts_in[item_point] + vert_new2 = (vert_edg * mat_r2).normalized() * shift_end_points + verts_in[item_point] + return [vert_new1,vert_new2] + + def get_middle_points(item_point): + points = [] + for i in range(len(neighbours[item_point])): + current_edges = (neighbours[item_point][i:] + neighbours[item_point][:i])[:2] + second_item1 = list(set(current_edges[0]) - set([item_point]))[0] + second_item2 = list(set(current_edges[1]) - set([item_point]))[0] + vert_edg1 = verts_in[second_item1] - verts_in[item_point] + vert_edg2 = verts_in[second_item2] - verts_in[item_point] + angle = calc_angle(vert_edg1, vert_edg2) / 2 + mat_r = Matrix.Rotation(angle, 2, 'X') + points.append((vert_edg1 * mat_r).normalized() * shift/sin(angle) + verts_in[item_point]) + return points + + #Seting points + findex_new_points = [0] + for i in range(len(verts_in)): + if len(neighbours[i]) == 1: + verts_out.extend(get_end_points(i)) + findex_new_points.append(findex_new_points[-1] + 2) + else: + p = get_middle_points(i) + verts_out.extend(p) + findex_new_points.append(findex_new_points[-1] + len(p)) + + findex_new_points = findex_new_points[:-1] + + #Creating faces and mark outer edges and central points + outer_edges = [] + current_index = len(verts_out) - 1 + vers_mask = [0 for _ in verts_out] + nomber_outer_points = current_index + position_old_points = [0 for _ in verts_out] + for edg in edges_in: + need_points = [] + for i in edg: + if len(neighbours[i]) <= 2: + need_points.extend([findex_new_points[i], findex_new_points[i] + 1]) + else: + position = neighbours[i].index(edg) + nomber_points = len(neighbours[i]) + variants_positions = list(range(findex_new_points[i], findex_new_points[i] + nomber_points)) + need_points.extend((variants_positions[position - 1:] + variants_positions[:position - 1])[:2]) + + vec_edg = verts_in[edg[0]] - verts_in[edg[1]] + vec_1 = verts_out[need_points[0]] - verts_in[edg[0]] + vec_2 = verts_out[need_points[1]] - verts_in[edg[0]] + vec_3 = verts_out[need_points[2]] - verts_in[edg[1]] + vec_4 = verts_out[need_points[3]] - verts_in[edg[1]] + new_vecs = [vec_1, vec_2, vec_3, vec_4] + + angles = [vec_edg.angle_signed(vec) for vec in new_vecs] + + if position_old_points[edg[0]] == 0: + verts_out.append(verts_in[edg[0]]) + vers_mask.append(1) + current_index += 1 + position_old_points[edg[0]] = current_index + + if position_old_points[edg[1]] == 0: + verts_out.append(verts_in[edg[1]]) + vers_mask.append(1) + current_index += 1 + position_old_points[edg[1]] = current_index + + n_p = need_points + if angles[0] < 0 and angles[2] < 0 or angles[0] >= 0 and angles[2] >= 0: + new_edges = [[n_p[1], position_old_points[edg[0]], position_old_points[edg[1]], n_p[3]], + [n_p[0], position_old_points[edg[0]], position_old_points[edg[1]], n_p[2]]] + + outer_edges.extend([[n_p[1],n_p[3]],[n_p[0],n_p[2]]]) + else: + new_edges = [[n_p[0], position_old_points[edg[0]], position_old_points[edg[1]], n_p[3]], + [n_p[1], position_old_points[edg[0]], position_old_points[edg[1]], n_p[2]]] + + outer_edges.extend([[n_p[0],n_p[3]],[n_p[1],n_p[2]]]) + + if len(neighbours[edg[0]]) == 1: + new_edges.append([need_points[1], position_old_points[edg[0]], need_points[0]]) + outer_edges.append([need_points[1],need_points[0]]) + if len(neighbours[edg[1]]) == 1: + new_edges.append([need_points[2], position_old_points[edg[1]], need_points[3]]) + outer_edges.append([need_points[2],need_points[3]]) + + for c,face in enumerate(new_edges): + if normal(*[verts_out[i].to_3d() for i in face])[2] < 0: + new_edges[c] = new_edges[c][::-1] + + faces_out.extend(new_edges) + + verts_out = [i.to_3d()[:] for i in verts_out] + return(verts_out, faces_out, outer_edges, vers_mask) class SvOffsetLineNode(bpy.types.Node, SverchCustomTreeNode): """ @@ -35,8 +181,7 @@ class SvOffsetLineNode(bpy.types.Node, SverchCustomTreeNode): offset = FloatProperty( name='offset', description='distance of offset', - default=0.1, - options={'ANIMATABLE'}, update=updateNode) + default=0.1, update=updateNode) def sv_init(self, context): self.inputs.new('VerticesSocket', 'Vers') @@ -49,158 +194,39 @@ def sv_init(self, context): def process(self): - if not ((self.outputs['Vers'].is_linked or self.outputs['Faces'].is_linked - or self.outputs['OuterEdges'].is_linked or self.outputs['VersMask'].is_linked) - and (self.inputs['Vers'].is_linked or self.inputs['Edgs'].is_linked)): - return - - verts_in = self.inputs['Vers'].sv_get()[0] - edges_in = self.inputs['Edgs'].sv_get()[0] - shift = self.inputs['Offset'].sv_get()[0][0] + if not (self.inputs['Vers'].is_linked or self.inputs['Edgs'].is_linked): + return - # avoid zero offset - if not shift: + if not (self.outputs['Vers'].is_linked or self.outputs['Faces'].is_linked + or self.outputs['OuterEdges'].is_linked or self.outputs['VersMask'].is_linked): return + + verts_in = self.inputs['Vers'].sv_get() + edges_in = self.inputs['Edgs'].sv_get() + shifter = self.inputs['Offset'].sv_get()[0] - verts_out = [] - faces_out = [] - - from mathutils import Vector, Matrix - from math import radians, pi, sin - from mathutils.geometry import normal - - verts_in = [Vector(v).to_2d() for v in verts_in] - - #Searching neighbours for each point - neighbours = [[] for _ in verts_in] - for edg in edges_in: - neighbours[edg[0]].append(edg) - neighbours[edg[1]].append(edg) - - #Convert to from 0 to 2pi angle - def calc_angle(p1,p2): - angle = p1.angle_signed(p2) - return angle + 2 * pi if angle < 0 else angle - - #Sorting neighbours by hour hand - for i, p_neighbs in enumerate(neighbours): - if len(p_neighbs) != 1: - angles = [0] - first_item = list(set(p_neighbs[0]) - set([i]))[0] - for another_neighb in p_neighbs[1:]: - second_item = list(set(another_neighb) - set([i]))[0] - vector1 = verts_in[first_item] - verts_in[i] - vector2 = verts_in[second_item] - verts_in[i] - angles.append(calc_angle(vector1, vector2)) - sorted_ = list(zip(angles, p_neighbs)) - sorted_.sort() - neighbours[i] = [e[1] for e in sorted_] - - def get_end_points(item_point): - second_item = list(set(neighbours[item_point][0]) - set([item_point]))[0] - vert_edg = verts_in[item_point] - verts_in[second_item] - mat_r1 = Matrix.Rotation(radians(-45),2,'X') - mat_r2 = Matrix.Rotation(radians(45),2,'X') - vert_new1 = (vert_edg * mat_r1).normalized() * shift/sin(radians(45)) + verts_in[item_point] - vert_new2 = (vert_edg * mat_r2).normalized() * shift/sin(radians(45)) + verts_in[item_point] - return [vert_new1,vert_new2] - - def get_middle_points(item_point): - points = [] - for i in range(len(neighbours[item_point])): - current_edges = (neighbours[item_point][i:] + neighbours[item_point][:i])[:2] - second_item1 = list(set(current_edges[0]) - set([item_point]))[0] - second_item2 = list(set(current_edges[1]) - set([item_point]))[0] - vert_edg1 = verts_in[second_item1] - verts_in[item_point] - vert_edg2 = verts_in[second_item2] - verts_in[item_point] - angle = calc_angle(vert_edg1, vert_edg2) / 2 - mat_r = Matrix.Rotation(angle, 2, 'X') - points.append((vert_edg1 * mat_r).normalized() * shift/sin(angle) + verts_in[item_point]) - return points - - #Seting points - findex_new_points = [0] - for i in range(len(verts_in)): - if len(neighbours[i]) == 1: - verts_out.extend(get_end_points(i)) - findex_new_points.append(findex_new_points[-1] + 2) - else: - p = get_middle_points(i) - verts_out.extend(p) - findex_new_points.append(findex_new_points[-1] + len(p)) + # verts_out, faces_out, outer_edges, vers_mask + out_geometry = [[], [], [], []] - findex_new_points = findex_new_points[:-1] + diff_shift = len(verts_in) - len(shifter) + if diff_shift > 0: + shifter.extend([shifter[-1] for _ in range(diff_shift)]) - #Creating faces and mark outer edges and central points - outer_edges = [] - current_index = len(verts_out) - 1 - vers_mask = [0 for _ in verts_out] - nomber_outer_points = current_index - position_old_points = [0 for _ in verts_out] - for edg in edges_in: - need_points = [] - for i in edg: - if len(neighbours[i]) <= 2: - need_points.extend([findex_new_points[i], findex_new_points[i] + 1]) - else: - position = neighbours[i].index(edg) - nomber_points = len(neighbours[i]) - variants_positions = list(range(findex_new_points[i], findex_new_points[i] + nomber_points)) - need_points.extend((variants_positions[position - 1:] + variants_positions[:position - 1])[:2]) - - vec_edg = verts_in[edg[0]] - verts_in[edg[1]] - vec_1 = verts_out[need_points[0]] - verts_in[edg[0]] - vec_2 = verts_out[need_points[1]] - verts_in[edg[0]] - vec_3 = verts_out[need_points[2]] - verts_in[edg[1]] - vec_4 = verts_out[need_points[3]] - verts_in[edg[1]] - new_vecs = [vec_1, vec_2, vec_3, vec_4] - - angles = [vec_edg.angle_signed(vec) for vec in new_vecs] - - if position_old_points[edg[0]] == 0: - verts_out.append(verts_in[edg[0]]) - vers_mask.append(1) - current_index += 1 - position_old_points[edg[0]] = current_index + for verts,edges,shift in zip(verts_in, edges_in, shifter): + # avoid zero offset + if not shift: + continue + geometry = offset_edges(verts, edges, shift) + _ = [out_geometry[idx].append(data) for idx, data in enumerate(geometry)] - if position_old_points[edg[1]] == 0: - verts_out.append(verts_in[edg[1]]) - vers_mask.append(1) - current_index += 1 - position_old_points[edg[1]] = current_index - - n_p = need_points - if angles[0] < 0 and angles[2] < 0 or angles[0] >= 0 and angles[2] >= 0: - new_edges = [[n_p[1], position_old_points[edg[0]], position_old_points[edg[1]], n_p[3]], - [n_p[0], position_old_points[edg[0]], position_old_points[edg[1]], n_p[2]]] - - outer_edges.extend([[n_p[1],n_p[3]],[n_p[0],n_p[2]]]) - else: - new_edges = [[n_p[0], position_old_points[edg[0]], position_old_points[edg[1]], n_p[3]], - [n_p[1], position_old_points[edg[0]], position_old_points[edg[1]], n_p[2]]] - - outer_edges.extend([[n_p[0],n_p[3]],[n_p[1],n_p[2]]]) - - if len(neighbours[edg[0]]) == 1: - new_edges.append([need_points[1], position_old_points[edg[0]], need_points[0]]) - outer_edges.append([need_points[1],need_points[0]]) - if len(neighbours[edg[1]]) == 1: - new_edges.append([need_points[2], position_old_points[edg[1]], need_points[3]]) - outer_edges.append([need_points[2],need_points[3]]) - - for c,face in enumerate(new_edges): - if normal(*[verts_out[i].to_3d() for i in face])[2] < 0: - new_edges[c] = new_edges[c][::-1] - - faces_out.extend(new_edges) - - verts_out = [[i.to_3d()[:] for i in verts_out]] - faces_out = [faces_out] + # nothing done + if not len(out_geometry[0]): + return - self.outputs['Vers'].sv_set(verts_out) - self.outputs['Faces'].sv_set(faces_out) - self.outputs['OuterEdges'].sv_set([outer_edges]) - self.outputs['VersMask'].sv_set([vers_mask]) + self.outputs['Vers'].sv_set(out_geometry[0]) + self.outputs['Faces'].sv_set(out_geometry[1]) + self.outputs['OuterEdges'].sv_set(out_geometry[2]) + self.outputs['VersMask'].sv_set(out_geometry[3]) def register(): bpy.utils.register_class(SvOffsetLineNode) From dd537ea6600d85a1a5ea2672a0d12b54eddc20e6 Mon Sep 17 00:00:00 2001 From: Durman Date: Fri, 22 Dec 2017 09:51:44 +0400 Subject: [PATCH 6/8] Update offset_line.rst --- docs/nodes/alpha_nodes/offset_line.rst | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/nodes/alpha_nodes/offset_line.rst b/docs/nodes/alpha_nodes/offset_line.rst index 4d52323f6d..b8c66c9865 100644 --- a/docs/nodes/alpha_nodes/offset_line.rst +++ b/docs/nodes/alpha_nodes/offset_line.rst @@ -11,15 +11,15 @@ Inputs This node has the following inputs: -- **Vers** - vertices of objects - work only with first object yet. -- **Edgs** - polygons of objects - the same. -- **offset** - offset values - single value. +- **Vers** - vertices of objects. +- **Edgs** - polygons of objects. +- **offset** - offset values - available single value per object. Parameters ---------- All parameters can be given by the node or an external input. -``offset`` only one value at the moment. +``offset`` only one value per object. +-----------------+---------------+-------------+-------------------------------------------------------------+ | Param | Type | Default | Description | @@ -48,8 +48,10 @@ Using of outer edges: .. image:: https://user-images.githubusercontent.com/28003269/34199326-dadbf508-e586-11e7-9542-7b7ff4a9521f.png -Using of vertices mask with ``transform select`` node. +Using of vertices mask with ``transform select`` node: .. image:: https://user-images.githubusercontent.com/28003269/34199698-125ed63e-e588-11e7-9e34-83c5eb33cde9.png +Different values for each object: +.. image:: https://user-images.githubusercontent.com/28003269/34286982-97be32ea-e6fd-11e7-9e52-5bbc78b36027.png From 409aa2291b3bd857824113a4b94cd50950677542 Mon Sep 17 00:00:00 2001 From: Durman Date: Fri, 22 Dec 2017 13:18:21 +0400 Subject: [PATCH 7/8] didn't notice :/ --- nodes/modifier_change/offset_line.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/nodes/modifier_change/offset_line.py b/nodes/modifier_change/offset_line.py index bf2fc6432d..1af38a5249 100644 --- a/nodes/modifier_change/offset_line.py +++ b/nodes/modifier_change/offset_line.py @@ -47,11 +47,6 @@ def offset_edges(verts_in, edges_in, shift): neighbours[edg[0]].append(edg) neighbours[edg[1]].append(edg) - #Convert to from 0 to 2pi angle - def calc_angle(p1,p2): - angle = p1.angle_signed(p2) - return angle + 2 * pi if angle < 0 else angle - #Sorting neighbours by hour hand for i, p_neighbs in enumerate(neighbours): if len(p_neighbs) != 1: From 7c01fa44fc00c77111c1b7dcc35a429b7cf99d51 Mon Sep 17 00:00:00 2001 From: Durman Date: Fri, 22 Dec 2017 22:38:14 +0400 Subject: [PATCH 8/8] Update offset_line.py --- nodes/modifier_change/offset_line.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nodes/modifier_change/offset_line.py b/nodes/modifier_change/offset_line.py index 1af38a5249..eb323d8af8 100644 --- a/nodes/modifier_change/offset_line.py +++ b/nodes/modifier_change/offset_line.py @@ -192,8 +192,7 @@ def process(self): if not (self.inputs['Vers'].is_linked or self.inputs['Edgs'].is_linked): return - if not (self.outputs['Vers'].is_linked or self.outputs['Faces'].is_linked - or self.outputs['OuterEdges'].is_linked or self.outputs['VersMask'].is_linked): + if not any(socket.is_linked for socket in self.outputs): return verts_in = self.inputs['Vers'].sv_get()