From e26a7f8bbe1b0d097027734dda9cf7cb9aaf9215 Mon Sep 17 00:00:00 2001 From: Ilya Portnov Date: Mon, 4 Jan 2021 22:49:24 +0500 Subject: [PATCH 1/3] "mesh smoothed surface field" node. --- index.md | 1 + nodes/field/mesh_normal_field.py | 6 ++ nodes/field/mesh_surface_field.py | 132 ++++++++++++++++++++++++++++++ utils/field/rbf.py | 54 ++++++++++++ 4 files changed, 193 insertions(+) create mode 100644 nodes/field/mesh_surface_field.py diff --git a/index.md b/index.md index 68b2a1d73..869de53ff 100644 --- a/index.md +++ b/index.md @@ -192,6 +192,7 @@ SvExScalarFieldPointNode SvAttractorFieldNodeMk2 SvExImageFieldNode + SvMeshSurfaceFieldNode SvExMeshNormalFieldNode SvExVoronoiFieldNode SvExMinimalScalarFieldNode diff --git a/nodes/field/mesh_normal_field.py b/nodes/field/mesh_normal_field.py index 1d31ed173..2ad9364f0 100644 --- a/nodes/field/mesh_normal_field.py +++ b/nodes/field/mesh_normal_field.py @@ -1,3 +1,9 @@ +# This file is part of project Sverchok. It's copyrighted by the contributors +# recorded in the version control history of the file, available from +# its original location https://github.com/nortikin/sverchok/commit/master +# +# SPDX-License-Identifier: GPL3 +# License-Filename: LICENSE import numpy as np diff --git a/nodes/field/mesh_surface_field.py b/nodes/field/mesh_surface_field.py new file mode 100644 index 000000000..7327f149f --- /dev/null +++ b/nodes/field/mesh_surface_field.py @@ -0,0 +1,132 @@ +# This file is part of project Sverchok. It's copyrighted by the contributors +# recorded in the version control history of the file, available from +# its original location https://github.com/nortikin/sverchok/commit/master +# +# SPDX-License-Identifier: GPL3 +# License-Filename: LICENSE + +import numpy as np + +import bpy +from bpy.props import FloatProperty, EnumProperty, BoolProperty, IntProperty + +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode, zip_long_repeat, get_data_nesting_level, ensure_nesting_level +from sverchok.utils.sv_bmesh_utils import bmesh_from_pydata +from sverchok.utils.field.rbf import mesh_field +from sverchok.dependencies import scipy +from sverchok.utils.dummy_nodes import add_dummy +from sverchok.utils.math import rbf_functions + +if scipy is None: + add_dummy('SvMeshSurfaceFieldNode', "Mesh Smoothed Surface Field", 'scipy') + +class SvMeshSurfaceFieldNode(bpy.types.Node, SverchCustomTreeNode): + """ + Triggers: Mesh Surface Field + Tooltip: Generate scalar field, defining a smoothed surface of the mesh + """ + bl_idname = 'SvMeshSurfaceFieldNode' + bl_label = 'Mesh Smoothed Surface Field' + bl_icon = 'OUTLINER_OB_EMPTY' + + function : EnumProperty( + name = "Function", + items = rbf_functions, + default = 'multiquadric', + update = updateNode) + + epsilon : FloatProperty( + name = "Epsilon", + default = 1.0, + min = 0.0, + update = updateNode) + + scale : FloatProperty( + name = "Scale", + default = 1.0, + update = updateNode) + + smooth : FloatProperty( + name = "Smooth", + default = 0.0, + min = 0.0, + update = updateNode) + + use_verts : BoolProperty( + name = "Use Vertices", + default = True, + update = updateNode) + + use_edges : BoolProperty( + name = "Use Edges", + default = False, + update = updateNode) + + use_faces : BoolProperty( + name = "Use Faces", + default = False, + update = updateNode) + + def draw_buttons(self, context, layout): + layout.prop(self, "function") + layout.prop(self, "use_verts") + layout.prop(self, "use_edges") + layout.prop(self, "use_faces") + + def sv_init(self, context): + self.inputs.new('SvVerticesSocket', 'Vertices') + self.inputs.new('SvStringsSocket', 'Edges') + self.inputs.new('SvStringsSocket', 'Faces') + self.inputs.new('SvStringsSocket', "Epsilon").prop_name = 'epsilon' + self.inputs.new('SvStringsSocket', "Smooth").prop_name = 'smooth' + self.inputs.new('SvStringsSocket', "Scale").prop_name = 'scale' + self.outputs.new('SvScalarFieldSocket', "Field") + + def process(self): + + if not any(socket.is_linked for socket in self.outputs): + return + + vertices_s = self.inputs['Vertices'].sv_get() + edges_s = self.inputs['Edges'].sv_get() + faces_s = self.inputs['Faces'].sv_get() + epsilon_s = self.inputs['Epsilon'].sv_get() + smooth_s = self.inputs['Smooth'].sv_get() + scale_s = self.inputs['Scale'].sv_get() + + input_level = get_data_nesting_level(vertices_s) + vertices_s = ensure_nesting_level(vertices_s, 4) + edges_s = ensure_nesting_level(edges_s, 4) + faces_s = ensure_nesting_level(faces_s, 4) + epsilon_s = ensure_nesting_level(epsilon_s, 2) + smooth_s = ensure_nesting_level(smooth_s, 2) + scale_s = ensure_nesting_level(scale_s, 2) + + nested_output = input_level > 3 + + fields_out = [] + for params in zip_long_repeat(vertices_s, edges_s, faces_s, epsilon_s, smooth_s, scale_s): + new_fields = [] + for vertices, edges, faces, epsilon, smooth, scale in zip_long_repeat(*params): + bm = bmesh_from_pydata(vertices, edges, faces, normal_update=True) + field = mesh_field(bm, self.function, smooth, epsilon, scale, + use_verts = self.use_verts, + use_edges = self.use_edges, + use_faces = self.use_faces) + new_fields.append(field) + if nested_output: + fields_out.append(new_fields) + else: + fields_out.extend(new_fields) + + self.outputs['Field'].sv_set(fields_out) + +def register(): + if scipy is not None: + bpy.utils.register_class(SvMeshSurfaceFieldNode) + +def unregister(): + if scipy is not None: + bpy.utils.unregister_class(SvMeshSurfaceFieldNode) + diff --git a/utils/field/rbf.py b/utils/field/rbf.py index cc3b65826..e2bcf92a1 100644 --- a/utils/field/rbf.py +++ b/utils/field/rbf.py @@ -1,9 +1,19 @@ +# This file is part of project Sverchok. It's copyrighted by the contributors +# recorded in the version control history of the file, available from +# its original location https://github.com/nortikin/sverchok/commit/master +# +# SPDX-License-Identifier: GPL3 +# License-Filename: LICENSE import numpy as np from mathutils import Matrix, Vector from sverchok.utils.field.scalar import SvScalarField from sverchok.utils.field.vector import SvVectorField +from sverchok.dependencies import scipy + +if scipy is not None: + from scipy.interpolate import Rbf ################## # # @@ -74,6 +84,50 @@ class SvBvhRbfNormalVectorField(SvVectorField): R = vectors.T return R[0], R[1], R[2] +def mesh_field(bm, function, smooth, epsilon, scale, use_verts=True, use_edges=False, use_faces=False): + src_points = [] + dst_values = [] + if use_verts: + for bm_vert in bm.verts: + src_points.append(tuple(bm_vert.co)) + dst_values.append(0.0) + src_points.append(tuple(bm_vert.co + scale * bm_vert.normal)) + dst_values.append(1.0) + + if use_edges: + for bm_edge in bm.edges: + pt1 = 0.5*(bm_edge.verts[0].co + bm_edge.verts[1].co) + normal = (bm_edge.verts[0].normal + bm_edge.verts[1].normal).normalized() + pt2 = pt1 + scale * normal + src_points.append(tuple(pt1)) + dst_values.append(0.0) + src_points.append(tuple(pt2)) + dst_values.append(1.0) + + if use_faces: + for bm_face in bm.faces: + pt1 = bm_face.calc_center_median() + pt2 = pt1 + scale * bm_face.normal + src_points.append(tuple(pt1)) + dst_values.append(0.0) + src_points.append(tuple(pt2)) + dst_values.append(1.0) + + src_points = np.array(src_points) + dst_values = np.array(dst_values) + + xs_from = src_points[:,0] + ys_from = src_points[:,1] + zs_from = src_points[:,2] + + rbf = Rbf(xs_from, ys_from, zs_from, dst_values, + function = function, + smooth = smooth, + epsilon = epsilon, + mode = '1-D') + + return SvRbfScalarField(rbf) + def register(): pass -- GitLab From 5c132f52c3f233f47989399f5ef78910885bcd26 Mon Sep 17 00:00:00 2001 From: Ilya Portnov Date: Mon, 4 Jan 2021 22:51:12 +0500 Subject: [PATCH 2/3] Node documentation. --- docs/nodes/field/field_index.rst | 1 + docs/nodes/field/mesh_surface_field.rst | 78 +++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 docs/nodes/field/mesh_surface_field.rst diff --git a/docs/nodes/field/field_index.rst b/docs/nodes/field/field_index.rst index 9dac970b7..2e00bc1b8 100644 --- a/docs/nodes/field/field_index.rst +++ b/docs/nodes/field/field_index.rst @@ -12,6 +12,7 @@ Field noise_vfield voronoi_field image_field + mesh_surface_field mesh_normal_field minimal_sfield minimal_vfield diff --git a/docs/nodes/field/mesh_surface_field.rst b/docs/nodes/field/mesh_surface_field.rst new file mode 100644 index 000000000..66b3d70f5 --- /dev/null +++ b/docs/nodes/field/mesh_surface_field.rst @@ -0,0 +1,78 @@ +Mesh Smoothed Surface Field +=========================== + +Dependencies +------------ + +This node requires SciPy_ library to work. + +.. _SciPy: https://scipy.org/ + +Functionality +------------- + +This node generates a Scalar Field, which has value of zero at some "key" +points of a given mesh, and value of 1 at the ends of normals of the mesh at +these points. Points to be considered as "key" can be mesh's vertices, middle +points of edges, and centers of faces. Between the described points, the field +is interpolated by use of RBF_ method. Depending on "smooth" parameter, the +generated field can be approximating rather than interpolating. + +The field generated by this rule has it's iso-surface at zero level in a +general shape of the source mesh, but smoothed. + +.. _RBF: http://www.scholarpedia.org/article/Radial_basis_function + +Inputs +------ + +This node has the following inputs: + +* **Vertices**. The vertices of the source mesh. This input is mandatory. +* **Edges**. The edges of the source mesh. +* **Faces**. The faces of the source mesh. This input is mandatory. +* **Epsilon**. Epsilon parameter of used RBF function; it affects the shape of + generated field. The default value is 1.0. +* **Smooth**. Smoothness parameter of used RBF function. If this is zero, then + the field will have exactly the specified values in all provided points; + otherwise, it will be only an approximating field. The default value is 0.0. +* **Scale**. This defines the distance along the normals of the mesh, at which + the field should have the value of 1. The default value is 1.0. + +Parameters +---------- + +This node has the following parameters: + +* **Function**. The specific function used by the node. The available values are: + + * Multi Quadric + * Inverse + * Gaussian + * Cubic + * Quintic + * Thin Plate + + The default function is Multi Quadric. + +* **Use Vertices**. If checked, then the generated scalar field will have value + of zero at locations of mesh's vertices. Checked by default. +* **Use Edges**. If checked, then the generated scalar field will have value of + zero at middle points of mesh's edges. Unchecked by default. +* **Use Faces**. If checked, then the generated scalar field will have value of + zero at centers of mesh's faces. Unchecked by default. + +Outputs +------- + +This node has the following output: + +* **Field**. The generated scalar field. + +Example of Usage +---------------- + +In combination with "Marching Cubes" node this can be used to generate a smoothened version of the source mesh: + +.. image:: https://user-images.githubusercontent.com/284644/103563698-04322200-4edf-11eb-9dca-583aea877d80.png + -- GitLab From 92066be761127716762934a1d60c4ad48a2dc29b Mon Sep 17 00:00:00 2001 From: Ilya Portnov Date: Mon, 4 Jan 2021 22:55:59 +0500 Subject: [PATCH 3/3] Add an exception for tests. --- tests/ui_tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/ui_tests.py b/tests/ui_tests.py index f36177ba1..9c69f5b1c 100644 --- a/tests/ui_tests.py +++ b/tests/ui_tests.py @@ -29,6 +29,7 @@ class UiTests(SverchokTestCase): 'SvMonadInfoNode', 'SvExMinimalScalarFieldNode', 'SvExScalarFieldGraphNode', + 'SvMeshSurfaceFieldNode', 'SvExMeshNormalFieldNode', 'SvExMinimalVectorFieldNode', 'SvSolidCenterOfMassNode', -- GitLab