Не подтверждена Коммит ed549f19 создал по автору Ilya V. Portnov's avatar Ilya V. Portnov Зафиксировано автором GitHub
Просмотр файлов

Port Linear Approximation node to 2.80 (#2547)

* Port linear_approx node.

* Linear approximation node.

* Minor updates.

* Updates / fixes.

* Documentation.

* Unit tests for the new API.

* update geom.py
владелец c19385d4
Linear Approximation
====================
Functionality
-------------
This node tries to approximate provided set of vertices by either a plane or
a straight line. In other words, it searches for such a plane / line, that all
provided vertices have the minimum distance to it.
More technically, it searches for a plane or a line A, such that
.. image:: https://user-images.githubusercontent.com/284644/58372582-e6e25700-7f38-11e9-844f-aaa4fa2bb562.gif
The plane is represented by a point on the plane and a normal vector.
The line is represented by a point on the line and a direction vector.
That point on line or plane is calculated as a geometrical center of all
provided vertices.
Inputs
------
This node has one input: **Vertices** - the vertices to be approximated.
Parameters
----------
This node has one parameter - **Mode**. Two modes are supported:
* **Line** - approximate vertices by straight line. This is the default mode.
* **Plane** - approximate vertices by a plane.
Outputs
-------
This node has the following outputs:
* **Center** - the point on the line or plane. This is geometrical center of all input vertices.
* **Normal** - the normal vector of a plane. This output is only available in the **Plane** mode.
* **Direction** - the direction vector of a line. This output is only available in the **Line** mode.
* **Projections** - the projections of input vertices to the line or plane.
* **Diffs** - difference vectors between the input vertices and their projections to line or plane.
* **Distances** - distances between the input vertices and their projections to line or plane.
Examples of usage
-----------------
The simplest example: approximate 3D curve by a line. Here black curve is a
grease pencil, green one - it's representation in Sverchok, red line - result
of linear approximation of green line.
.. image:: https://user-images.githubusercontent.com/284644/58330560-8e378f00-7e50-11e9-9bf5-8612c420ed91.png
A simple example with plane.
.. image:: https://user-images.githubusercontent.com/284644/58274029-63472f80-7dab-11e9-9c8b-1953633cf2be.png
The node can calculate approximation for several sets of vertices at once:
.. image:: https://user-images.githubusercontent.com/284644/58273750-cd130980-7daa-11e9-8f99-3ec57b37965c.png
You can also find more examples and some discussion `in the development thread <https://github.com/nortikin/sverchok/pull/2421>`_.
......@@ -222,6 +222,7 @@
SvInterpolationNode
SvInterpolationNodeMK2
SvInterpolationNodeMK3
SvLinearApproxNode
---
SvHomogenousVectorField
SvNoiseNodeMK2
......
# ##### 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
import numpy as np
from bpy.props import EnumProperty
from mathutils import Vector
from sverchok.node_tree import SverchCustomTreeNode
from sverchok.data_structure import (updateNode)
from sverchok.utils.geom import linear_approximation
class SvLinearApproxNode(bpy.types.Node, SverchCustomTreeNode):
"""
Triggers: Linear Approximation
Tooltip: Approximate vertices with straight line or plane.
"""
bl_idname = 'SvLinearApproxNode'
bl_label = 'Linear Approximation'
bl_icon = 'OUTLINER_OB_EMPTY'
modes = [
("Line", "Line", "Straight line", 1),
("Plane", "Plane", "Plane", 2)
]
def update_mode(self, context):
self.outputs['Direction'].hide_safe = (self.mode != 'Line')
self.outputs['Normal'].hide_safe = (self.mode != 'Plane')
updateNode(self, context)
mode : EnumProperty(name = "Approximate by",
items = modes,
default = "Line",
update = update_mode)
def sv_init(self, context):
self.inputs.new('SvVerticesSocket', "Vertices")
self.outputs.new('SvVerticesSocket', "Center")
self.outputs.new('SvVerticesSocket', "Normal")
self.outputs.new('SvVerticesSocket', "Direction")
self.outputs.new('SvVerticesSocket', "Projections")
self.outputs.new('SvVerticesSocket', "Diffs")
self.outputs.new('SvStringsSocket', "Distances")
self.update_mode(context)
self.update_mode(context)
def draw_buttons(self, context, layout):
layout.prop(self, 'mode')
def process(self):
if not any(output.is_linked for output in self.outputs):
return
vertices_s = self.inputs['Vertices'].sv_get(default=[[]])
out_centers = []
out_normals = []
out_directions = []
out_projections = []
out_diffs = []
out_distances = []
for vertices in vertices_s:
approx = linear_approximation(vertices)
out_centers.append(approx.center)
if self.mode == 'Line':
line = approx.most_similar_line()
out_directions.append(tuple(line.direction.normalized()))
projections = []
diffs = []
distances = []
for vertex in vertices:
projection = line.projection_of_point(vertex)
projections.append(tuple(projection))
diff = projection - Vector(vertex)
diffs.append(tuple(diff))
distances.append(diff.length)
out_projections.append(projections)
out_diffs.append(diffs)
out_distances.append(distances)
elif self.mode == 'Plane':
plane = approx.most_similar_plane()
out_normals.append(tuple(plane.normal.normalized()))
projections = []
diffs = []
distances = list(map(float, list(plane.distance_to_points(vertices))))
projections_np = plane.projection_of_points(vertices)
vertices_np = np.array(vertices)
projections = list(map(tuple, list(projections_np)))
diffs_np = projections_np - vertices_np
diffs = list(map(tuple, list(diffs_np)))
out_projections.append(projections)
out_diffs.append(diffs)
out_distances.append(distances)
self.outputs['Center'].sv_set([out_centers])
self.outputs['Normal'].sv_set([out_normals])
self.outputs['Direction'].sv_set([out_directions])
self.outputs['Projections'].sv_set(out_projections)
self.outputs['Diffs'].sv_set(out_diffs)
self.outputs['Distances'].sv_set(out_distances)
def register():
bpy.utils.register_class(SvLinearApproxNode)
def unregister():
bpy.utils.unregister_class(SvLinearApproxNode)
import numpy as np
from sverchok.utils.testing import *
from sverchok.utils.logging import debug, info
from sverchok.utils.geom import PlaneEquation, LineEquation, linear_approximation
class PlaneTests(SverchokTestCase):
def test_plane_from_three_points(self):
p1 = (1, 0, 0)
p2 = (0, 1, 0)
p3 = (0, 0, 1)
plane = PlaneEquation.from_three_points(p1, p2, p3)
self.assertEquals(plane.a, 1)
self.assertEquals(plane.b, 1)
self.assertEquals(plane.c, 1)
self.assertEquals(plane.d, -1)
def test_nearest_to_origin(self):
p1 = (1, 0, 0)
p2 = (0, 1, 0)
p3 = (0, 0, 1)
plane = PlaneEquation.from_three_points(p1, p2, p3)
p = plane.nearest_point_to_origin()
self.assert_sverchok_data_equal(tuple(p), (0.3333, 0.3333, 0.3333), precision=4)
def test_check_yes(self):
plane = PlaneEquation.from_coordinate_plane('XY')
p = (7, 8, 0)
self.assertTrue(plane.check(p))
def test_check_no(self):
plane = PlaneEquation.from_coordinate_plane('XY')
p = (7, 8, 1)
self.assertFalse(plane.check(p))
def test_two_vectors(self):
p1 = (2, 0, 0)
p2 = (0, 1, 0)
p3 = (0, 0, 2)
plane = PlaneEquation.from_three_points(p1, p2, p3)
normal = plane.normal
v1, v2 = plane.two_vectors()
self.assertTrue(abs(normal.dot(v1)) < 1e-8)
self.assertTrue(abs(normal.dot(v2)) < 1e-8)
self.assertTrue(abs(v1.dot(v2)) < 1e-8)
def test_distance_to_point(self):
plane = PlaneEquation.from_coordinate_plane('XY')
point = (1, 2, 3)
distance = plane.distance_to_point(point)
self.assertEquals(distance, 3)
def test_distance_to_points(self):
plane = PlaneEquation.from_coordinate_plane('XY')
points = [(1, 2, 3), (4, 5, 6)]
distances = plane.distance_to_points(points)
self.assert_numpy_arrays_equal(distances, np.array([3, 6]))
def test_intersect_with_line(self):
plane = PlaneEquation.from_coordinate_plane('XY')
line = LineEquation.from_direction_and_point((1, 1, 1), (1, 1, 1))
point = plane.intersect_with_line(line)
self.assert_sverchok_data_equal(tuple(point), (0, 0, 0))
def test_intersect_with_plane(self):
plane1 = PlaneEquation.from_coordinate_plane('XY')
plane2 = PlaneEquation.from_coordinate_plane('XZ')
line = plane1.intersect_with_plane(plane2)
self.assert_sverchok_data_equal(tuple(line.direction.normalized()), (1, 0, 0))
self.assert_sverchok_data_equal(tuple(line.point), (0, 0, 0))
class LineTests(SverchokTestCase):
def test_from_two_points(self):
p1 = (1, 1, 1)
p2 = (3, 3, 3)
line = LineEquation.from_two_points(p1, p2)
self.assert_sverchok_data_equal(tuple(line.direction), (2, 2, 2))
self.assert_sverchok_data_equal(tuple(line.point), p1)
def test_check_yes(self):
p1 = (1, 1, 1)
p2 = (3, 3, 3)
line = LineEquation.from_two_points(p1, p2)
p3 = (5, 5, 5)
self.assertTrue(line.check(p3))
def test_check_no(self):
p1 = (1, 1, 1)
p2 = (3, 3, 3)
line = LineEquation.from_two_points(p1, p2)
p3 = (5, 5, 6)
self.assertFalse(line.check(p3))
def test_distance_to_point(self):
line = LineEquation.from_coordinate_axis('Z')
point = (0, 2, 0)
self.assertEquals(line.distance_to_point(point), 2)
class LinearApproximationTests(SverchokTestCase):
def test_approximate_line_1(self):
p1 = (0, 0, 0)
p2 = (1, 0, 0)
p3 = (2, 0, 0)
p4 = (3, 0, 0)
line = linear_approximation([p1, p2, p3, p4]).most_similar_line()
self.assert_sverchok_data_equal(tuple(line.direction.normalized()), (1, 0, 0), precision=5)
def test_approximate_line_2(self):
p1 = (0, -1, 0)
p2 = (1, 1, 0)
p3 = (2, -1, 0)
p4 = (3, 1, 0)
line = linear_approximation([p1, p2, p3, p4]).most_similar_line()
self.assert_sverchok_data_equal(tuple(line.direction), (0.7882054448127747, 0.6154122352600098, 0.0), precision=5)
def test_approximate_plane(self):
p1 = (0, -1, 0)
p2 = (1, 1, 0)
p3 = (2, -1, 0)
p4 = (3, 1, 0)
plane = linear_approximation([p1, p2, p3, p4]).most_similar_plane()
self.assert_sverchok_data_equal(tuple(plane.normal.normalized()), (0, 0, 1), precision=5)
Это отличие свёрнуто
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать