-
Notifications
You must be signed in to change notification settings - Fork 233
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add new PolygonSort node to sort polygons by distance, area, and norm…
…al angle
- Loading branch information
1 parent
f5424a4
commit 9992a5b
Showing
3 changed files
with
366 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
Polygon Sort | ||
============ | ||
|
||
Functionality | ||
------------- | ||
|
||
Polygon Sort node sorts the input polygons by various criteria: distance, area and normal angle. | ||
|
||
Inputs | ||
------ | ||
|
||
All parameters except **Mode** and **Descending** are vectorized and can take single of multiple input values. | ||
|
||
Parameters | ||
---------- | ||
|
||
All parameters except **Mode** and **Descending** can be given by the node or an external input. | ||
|
||
+----------------+-----------+-----------+--------------------------------------------------------+ | ||
| Param | Type | Default | Description | | ||
+================+===========+===========+========================================================+ | ||
| **Mode** | Enum | "D" | The sorting direction mode: | | ||
| | | | P : Sort by the the distance to a point P | | ||
| | | | D : Sort by the projection along a direction D | | ||
| | | | A : Sort by the area of the polygons | | ||
| | | | NP : Sort by the normal angle relative to point P | | ||
| | | | ND : Sort by the normal angle relative to direction D | | ||
+----------------+-----------+-----------+--------------------------------------------------------+ | ||
| **Descending** | Bool | False | Sort the polygons in the descending order. | | ||
+----------------+-----------+-----------+--------------------------------------------------------+ | ||
| **Verts** | Vector | none | The vertices of the input mesh to be sorted. | | ||
+----------------+-----------+-----------+--------------------------------------------------------+ | ||
| **Polys** | Polygon | none | The polygons of the input mesh to be sorted. | | ||
+----------------+-----------+-----------+--------------------------------------------------------+ | ||
| **Point P** | Vector | (1,0,0) | The reference vector: Point P. [1] | | ||
+----------------+-----------+-----------+--------------------------------------------------------+ | ||
| **Direction** | Vector | (1,0,0) | The reference vector: Direction D. [1] | | ||
+----------------+-----------+-----------+--------------------------------------------------------+ | ||
|
||
Notes: | ||
[1] : "Point P" is shown in P and NP mode and "Direction" is shown in D and ND mode. These are used for distance and normal angle calculation. The area mode (A) does not use the input sorting vector. | ||
|
||
Outputs | ||
------- | ||
|
||
**Indices** | ||
The indices of the sorted polygons. | ||
|
||
**Vertices** | ||
The input vertices. | ||
|
||
**Polygons** | ||
The sorted polygons. | ||
|
||
**Quantities** | ||
The quantity by which the polygons are sorted. Depending on the selected mode the output quantities are either Distances, Projections, Angles or Areas. | ||
|
||
Note: The output will be generated when the output sockets are connected. | ||
|
||
Modes | ||
----- | ||
The modes corespond to different sorting criteria and each one has a quanitity by which the polygons are sorted. | ||
* P : In this mode the polygons are sorted by the distance from the center of each polygon to the given point P. | ||
* D : In this mode the polygons are sorted by the projection component of polygon center vector along the given direction D. | ||
* A : In this mode the polygons are sorted by the area of the polygons. | ||
* ND : In this mode the polygons are sorted by the angle between the polygon normal and the given direction vector D. | ||
* NP : In this mode the polygons are sorted by the angle between the polygon normal and the vector pointing form the center of the polygon to the given point P. | ||
|
||
Presets | ||
------- | ||
The node provides a set of predefined sort directions: along X, Y and Z. These buttons will set the mode to "D" and the sorting vector (Direction) to one of the X, Y or Z directions: (1,0,0), (0,1,0) and (0,0,1) respectively. The preset buttons are only visible as long as the sorting vector socket is not connected. | ||
|
||
References: | ||
The algorythm to compute the area is based on descriptions found at this address: http://geomalgorithms.com/a01-_area.html#3D%20Polygons | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,289 @@ | ||
# ##### 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 BoolProperty, EnumProperty, FloatVectorProperty, StringProperty | ||
from sverchok.node_tree import SverchCustomTreeNode | ||
from sverchok.data_structure import updateNode, fullList, match_long_repeat | ||
from sverchok.utils.sv_operator_mixins import SvGenericCallbackWithParams | ||
from mathutils import Vector | ||
from math import acos, pi, sqrt | ||
|
||
modeItems = [ | ||
("P", "Point", "Sort by distance to a point", 0), | ||
("D", "Direction", "Sort by projection along a direction", 1), | ||
("A", "Area", "Sort by surface area", 2), | ||
("NP", "Normal Angle Point", "Sort by normal angle to point", 3), | ||
("ND", "Normal Angle Direction", "Sort by normal angle to direction", 4)] | ||
|
||
directions = {"X": [1, 0, 0], "Y": [0, 1, 0], "Z": [0, 0, 1]} | ||
|
||
socket_names = {"P": "Point", "D": "Direction", "A": "Area", "NP": "Normal", "ND": "Normal"} | ||
socket_props = {"P": "point_P", "D": "point_D", "A": "point_D", "NP": "point_P", "ND": "point_D"} | ||
|
||
quantity_names = {"P": "Distances", "D": "Distances", "A": "Areas", "NP": "Angles", "ND": "Angles"} | ||
|
||
|
||
def polygon_normal(verts, poly): | ||
''' The normal of the given polygon ''' | ||
v1 = Vector(verts[poly[0]]) | ||
v2 = Vector(verts[poly[1]]) | ||
v3 = Vector(verts[poly[2]]) | ||
v12 = v2 - v1 | ||
v23 = v3 - v2 | ||
normal = v12.cross(v23) | ||
normal.normalize() | ||
|
||
return list(normal) | ||
|
||
|
||
def polygon_area(verts, poly): | ||
''' The area of the given polygon ''' | ||
if len(poly) < 3: # not a plane - no area | ||
return 0 | ||
|
||
total = Vector([0, 0, 0]) | ||
N = len(poly) | ||
for i in range(N): | ||
vi1 = Vector(verts[poly[i]]) | ||
vi2 = Vector(verts[poly[(i + 1) % N]]) | ||
prod = vi1.cross(vi2) | ||
total[0] += prod[0] | ||
total[1] += prod[1] | ||
total[2] += prod[2] | ||
|
||
normal = Vector(polygon_normal(verts, poly)) | ||
area = abs(total.dot(normal)) / 2 | ||
|
||
return area | ||
|
||
|
||
def polygon_center(verts, poly): | ||
''' The center of the given polygon ''' | ||
vx = 0 | ||
vy = 0 | ||
vz = 0 | ||
for v in poly: | ||
vx = vx + verts[v][0] | ||
vy = vy + verts[v][1] | ||
vz = vz + verts[v][2] | ||
n = len(poly) | ||
vx = vx / n | ||
vy = vy / n | ||
vz = vz / n | ||
|
||
return [vx, vy, vz] | ||
|
||
|
||
def polygon_distance_P(verts, poly, P): | ||
''' The distance from the center of the polygon to the given point ''' | ||
C = polygon_center(verts, poly) | ||
CP = [C[0] - P[0], C[1] - P[1], C[2] - P[2]] | ||
distance = sqrt(CP[0] * CP[0] + CP[1] * CP[1] + CP[2] * CP[2]) | ||
|
||
return distance | ||
|
||
|
||
def polygon_distance_D(verts, poly, D): | ||
''' The projection of the polygon center vector along the given direction ''' | ||
C = polygon_center(verts, poly) | ||
distance = C[0] * D[0] + C[1] * D[1] + C[2] * D[2] | ||
|
||
return distance | ||
|
||
|
||
def polygon_normal_angle_P(verts, poly, P): | ||
''' The angle between the polygon normal and the vector from polygon center to given point ''' | ||
N = polygon_normal(verts, poly) | ||
C = polygon_center(verts, poly) | ||
V = [P[0] - C[0], P[1] - C[1], P[2] - C[2]] | ||
v1 = Vector(N) | ||
v2 = Vector(V) | ||
v1.normalize() | ||
v2.normalize() | ||
angle = acos(v1.dot(v2)) # the angle in radians | ||
|
||
return angle | ||
|
||
|
||
def polygon_normal_angle_D(verts, poly, D): | ||
''' The angle between the polygon normal and the given direction ''' | ||
N = polygon_normal(verts, poly) | ||
v1 = Vector(N) | ||
v2 = Vector(D) | ||
v1.normalize() | ||
v2.normalize() | ||
angle = acos(v1.dot(v2)) # the angle in radians | ||
|
||
return angle | ||
|
||
|
||
class SvPolygonSortNode(bpy.types.Node, SverchCustomTreeNode): | ||
""" | ||
Triggers: Polygon, Sorting | ||
Tooltip: Sort the polygons by various criteria: distance, angle, area. | ||
""" | ||
bl_idname = 'SvPolygonSortNode' | ||
bl_label = 'Polygon Sort' | ||
|
||
def sort_polygons(self, verts, polys, V): | ||
''' Sort polygons and return sorted polygons indices, poly & quantities ''' | ||
|
||
if self.mode == "D": | ||
quantities = [polygon_distance_D(verts, poly, V) for poly in polys] | ||
elif self.mode == "P": | ||
quantities = [polygon_distance_P(verts, poly, V) for poly in polys] | ||
elif self.mode == "A": | ||
quantities = [polygon_area(verts, poly) for poly in polys] | ||
elif self.mode == "NP": | ||
quantities = [polygon_normal_angle_P(verts, poly, V) for poly in polys] | ||
elif self.mode == "ND": | ||
quantities = [polygon_normal_angle_D(verts, poly, V) for poly in polys] | ||
|
||
IQ = [(i, q) for i, q in enumerate(quantities)] | ||
sortedIQs = sorted(IQ, key=lambda kv: kv[1], reverse=self.descending) | ||
|
||
sortedIndices = [IQ[0] for IQ in sortedIQs] | ||
sortedQuantities = [IQ[1] for IQ in sortedIQs] | ||
sortedPolys = [polys[i] for i in sortedIndices] | ||
|
||
return sortedIndices, sortedPolys, sortedQuantities | ||
|
||
def update_sockets(self, context): | ||
''' Swap sorting vector input socket to P/D based on selected mode ''' | ||
|
||
s = self.inputs[-1] | ||
s.name = socket_names[self.mode] | ||
s.prop_name = socket_props[self.mode] | ||
|
||
# keep the P/D props values synced when changing mode | ||
if self.mode == "P": | ||
self.point_P = self.point_D | ||
else: # self.mode == "D" | ||
self.point_D = self.point_P | ||
|
||
# update output "Quantities" socket with proper name for the mode | ||
o = self.outputs[-1] | ||
o.name = quantity_names[self.mode] | ||
|
||
def set_direction(self, operator): | ||
self.direction = operator.direction | ||
self.mode = "D" | ||
return {'FINISHED'} | ||
|
||
def update_xyz_direction(self, context): | ||
self.point_D = directions[self.direction] | ||
|
||
def update_mode(self, context): | ||
if self.mode == self.last_mode: | ||
return | ||
|
||
self.last_mode = self.mode | ||
self.update_sockets(context) | ||
updateNode(self, context) | ||
|
||
direction = StringProperty( | ||
name="Direction", default="X", update=update_xyz_direction) | ||
|
||
mode = EnumProperty( | ||
name="Mode", items=modeItems, default="D", update=update_mode) | ||
|
||
last_mode = EnumProperty( | ||
name="Last Mode", items=modeItems, default="D") | ||
|
||
point_P = FloatVectorProperty( | ||
name="Point P", description="Reference point for distance and angle calculation", | ||
size=3, default=(1, 0, 0), update=updateNode) | ||
|
||
point_D = FloatVectorProperty( | ||
name="Direction", description="Reference direction for projection and angle calculation", | ||
size=3, default=(1, 0, 0), update=updateNode) | ||
|
||
descending = BoolProperty( | ||
name="Descending", description="Sort in the descending order", | ||
default=False, update=updateNode) | ||
|
||
def sv_init(self, context): | ||
self.inputs.new('VerticesSocket', "Verts") | ||
self.inputs.new('StringsSocket', "Polys") | ||
self.inputs.new('VerticesSocket', "Direction").prop_name = "point_D" | ||
self.outputs.new('VerticesSocket', "Vertices") | ||
self.outputs.new('StringsSocket', "Polygons") | ||
self.outputs.new('StringsSocket', "Indices") | ||
self.outputs.new('StringsSocket', "Distances") | ||
|
||
def draw_buttons(self, context, layout): | ||
col = layout.column(align=False) | ||
|
||
if not self.inputs[-1].is_linked: | ||
row = col.row(align=True) | ||
for direction in "XYZ": | ||
op = row.operator("node.set_sort_direction", text=direction) | ||
op.direction = direction | ||
|
||
col = layout.column(align=True) | ||
row = col.row(align=True) | ||
row.prop(self, "mode", expand=False, text="") | ||
layout.prop(self, "descending") | ||
|
||
def process(self): | ||
if not any(s.is_linked for s in self.outputs): | ||
return | ||
|
||
inputs = self.inputs | ||
input_v = inputs["Verts"].sv_get() | ||
input_p = inputs["Polys"].sv_get() | ||
input_r = inputs[-1].sv_get()[0] # reference: direction or point | ||
|
||
params = match_long_repeat([input_v, input_p, input_r]) | ||
|
||
iList, vList, pList, qList = [], [], [], [] | ||
for v, p, r in zip(*params): | ||
indices, polys, quantities = self.sort_polygons(v, p, r) | ||
iList.append(indices) | ||
vList.append(v) | ||
pList.append(polys) | ||
qList.append(quantities) | ||
|
||
if self.outputs['Vertices'].is_linked: | ||
self.outputs['Vertices'].sv_set(vList) | ||
if self.outputs['Polygons'].is_linked: | ||
self.outputs['Polygons'].sv_set(pList) | ||
if self.outputs['Indices'].is_linked: | ||
self.outputs['Indices'].sv_set(iList) | ||
if self.outputs[-1].is_linked: | ||
self.outputs[-1].sv_set(qList) # sorting quantities | ||
|
||
|
||
class SvSetSortDirection(bpy.types.Operator, SvGenericCallbackWithParams): | ||
bl_label = "Set sort direction" | ||
bl_idname = "node.set_sort_direction" | ||
bl_description = "Set the sorting direction along X, Y or Z" | ||
|
||
direction = StringProperty(default="X") | ||
fn_name = StringProperty(default="set_direction") | ||
|
||
|
||
def register(): | ||
bpy.utils.register_class(SvSetSortDirection) | ||
bpy.utils.register_class(SvPolygonSortNode) | ||
|
||
|
||
def unregister(): | ||
bpy.utils.unregister_class(SvPolygonSortNode) | ||
bpy.utils.unregister_class(SvSetSortDirection) |