diff --git a/docs/nodes/pulga_physics/pulga_springs_force.rst b/docs/nodes/pulga_physics/pulga_springs_force.rst index 9d44f07980..1ab754ec78 100644 --- a/docs/nodes/pulga_physics/pulga_springs_force.rst +++ b/docs/nodes/pulga_physics/pulga_springs_force.rst @@ -42,7 +42,7 @@ Example in description: * Generator-> :doc:`Cylinder ` * Spatial-> :doc:`Populate Mesh ` -* Spatial-> :doc:`Voronoi on Mesh ` +* Spatial-> :doc:`Voronoi on Mesh ` * Modifiers->Modifier Change-> :doc:`Mesh Join ` * Modifiers->Modifier Change-> :doc:`Merge by Distance ` * Analyzers->Component Analyzer **Vertices->Sharpness**: :ref:`Vertices Sharpness` diff --git a/docs/nodes/spatial/voronoi_on_mesh.rst b/docs/nodes/spatial/voronoi_on_mesh_mk2.rst similarity index 100% rename from docs/nodes/spatial/voronoi_on_mesh.rst rename to docs/nodes/spatial/voronoi_on_mesh_mk2.rst diff --git a/index.yaml b/index.yaml index 2ce92be37d..dd92bc025e 100644 --- a/index.yaml +++ b/index.yaml @@ -315,7 +315,7 @@ - SvExVoronoi3DNode - SvExVoronoiSphereNode - SvVoronoiOnSurfaceNode - - SvVoronoiOnMeshNode + - SvVoronoiOnMeshNodeMK2 - SvVoronoiOnSolidNodeMK2 - --- - SvLloyd2dNode diff --git a/menus/full_by_data_type.yaml b/menus/full_by_data_type.yaml index 19c377d2dc..03861ed9f8 100644 --- a/menus/full_by_data_type.yaml +++ b/menus/full_by_data_type.yaml @@ -506,7 +506,7 @@ - SvExVoronoi3DNode - SvExVoronoiSphereNode - SvVoronoiOnSurfaceNode - - SvVoronoiOnMeshNode + - SvVoronoiOnMeshNodeMK2 - SvVoronoiOnSolidNodeMK2 - --- - SvLloyd2dNode diff --git a/menus/full_nortikin.yaml b/menus/full_nortikin.yaml index 0cc31e2cd0..402f4400ac 100644 --- a/menus/full_nortikin.yaml +++ b/menus/full_nortikin.yaml @@ -383,7 +383,7 @@ - SvExVoronoi3DNode - SvExVoronoiSphereNode - SvVoronoiOnSurfaceNode - - SvVoronoiOnMeshNode + - SvVoronoiOnMeshNodeMK2 - SvVoronoiOnSolidNodeMK2 - --- - SvLloyd2dNode diff --git a/nodes/spatial/voronoi_on_mesh_mk2.py b/nodes/spatial/voronoi_on_mesh_mk2.py new file mode 100644 index 0000000000..384d8f29db --- /dev/null +++ b/nodes/spatial/voronoi_on_mesh_mk2.py @@ -0,0 +1,193 @@ +# ##### 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, EnumProperty, BoolProperty, IntProperty + +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level, get_data_nesting_level,\ + ensure_min_nesting +from sverchok.utils.sv_bmesh_utils import recalc_normals +from sverchok.utils.sv_mesh_utils import mesh_join +from sverchok.utils.voronoi3d import voronoi_on_mesh + + +class SvVoronoiOnMeshNodeMK2(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Voronoi Mesh + Tooltip: Generate Voronoi diagram on the surface of a mesh object + """ + bl_idname = 'SvVoronoiOnMeshNodeMK2' + bl_label = 'Voronoi on Mesh' + bl_icon = 'OUTLINER_OB_EMPTY' + sv_icon = 'SV_VORONOI' + sv_dependencies = {'scipy'} + + modes = [ + ('VOLUME', "Split Volume", "Split volume of the mesh into regions of Voronoi diagram", 0), + ('SURFACE', "Split Surface", "Split the surface of the mesh into regions of Vornoi diagram", 1), + #('RIDGES', "Ridges near Surface", "Generate ridges of 3D Voronoi diagram near the surface of the mesh", 2), + #('REGIONS', "Regions near Surface", "Generate regions of 3D Voronoi diagram near the surface of the mesh", 3) + ] + + spacing : FloatProperty( + name = "Spacing", + default = 0.0, + min = 0.0, + description="Percent of space to leave between generated fragment meshes", + update=updateNode) + + normals : BoolProperty( + name = "Correct normals", + default = True, + description="Make sure that all normals of generated meshes point outside", + update = updateNode) + + def update_sockets(self, context): + self.inputs['Spacing'].hide_safe = self.mode not in {'VOLUME', 'SURFACE'} + updateNode(self, context) + + mode : EnumProperty( + name = "Mode", + items = modes, + default = 'VOLUME', + update = update_sockets) + + join_modes = [ + ('FLAT', "Flat list", "Output a single flat list of mesh objects (Voronoi diagram ridges / regions) for all input meshes", 0), + ('SEPARATE', "Separate lists", "Output a separate list of mesh objects (Voronoi diagram ridges / regions) for each input mesh", 1), + ('JOIN', "Join meshes", "Output one mesh, joined from ridges / edges of Voronoi diagram, for each input mesh", 2) + ] + + join_mode : EnumProperty( + name = "Output mode", + items = join_modes, + default = 'FLAT', + update = updateNode) + + accuracy : IntProperty( + name = "Accuracy", + description = "Accuracy for mesh bisecting procedure", + default = 6, + min = 1, + update = updateNode) + + def sv_init(self, context): + self.inputs.new('SvVerticesSocket', 'Vertices') + self.inputs.new('SvStringsSocket', 'Faces') + self.inputs.new('SvVerticesSocket', "Sites") +# self.inputs.new('SvStringsSocket', 'Thickness').prop_name = 'thickness' + self.inputs.new('SvStringsSocket', 'Spacing').prop_name = 'spacing' + self.outputs.new('SvVerticesSocket', "Vertices") + self.outputs.new('SvStringsSocket', "Edges") + self.outputs.new('SvStringsSocket', "Faces") + self.outputs.new('SvStringsSocket', "Sites_idx") + self.update_sockets(context) + + def draw_buttons(self, context, layout): + layout.label(text="Mode:") + layout.prop(self, "mode", text='') + if self.mode == 'VOLUME': + layout.prop(self, 'normals') + layout.label(text='Output nesting:') + layout.prop(self, 'join_mode', text='') + + def draw_buttons_ext(self, context, layout): + self.draw_buttons(context, layout) + layout.prop(self, 'accuracy') + + def process(self): + + if not any(socket.is_linked for socket in self.outputs): + return + + verts_in = self.inputs['Vertices'].sv_get(deepcopy=False) + faces_in = self.inputs['Faces'].sv_get(deepcopy=False) + sites_in = self.inputs['Sites'].sv_get(deepcopy=False) + #thickness_in = self.inputs['Thickness'].sv_get() + spacing_in = self.inputs['Spacing'].sv_get(deepcopy=False) + + verts_in = ensure_nesting_level(verts_in, 4) + input_level = get_data_nesting_level(sites_in) + sites_in = ensure_nesting_level(sites_in, 4) + faces_in = ensure_nesting_level(faces_in, 4) + #thickness_in = ensure_nesting_level(thickness_in, 2) + spacing_in = ensure_min_nesting(spacing_in, 2) + + nested_output = input_level > 3 + + precision = 10 ** (-self.accuracy) + + verts_out = [] + edges_out = [] + faces_out = [] + sites_idx_out = [] + for params in zip_long_repeat(verts_in, faces_in, sites_in, spacing_in): + new_verts = [] + new_edges = [] + new_faces = [] + new_sites = [] + for verts, faces, sites, spacing in zip_long_repeat(*params): + verts, edges, faces, used_sites_idx = voronoi_on_mesh(verts, faces, sites, thickness=0, + spacing = spacing, + #clip_inner = self.clip_inner, clip_outer = self.clip_outer, + do_clip=True, clipping=None, + mode = self.mode, + normal_update = self.normals, + precision = precision) + + if self.join_mode == 'FLAT': + new_verts.extend(verts) + new_edges.extend(edges) + new_faces.extend(faces) + new_sites.extend([[idx] for idx in used_sites_idx]) + elif self.join_mode == 'SEPARATE': + new_verts.append(verts) + new_edges.append(edges) + new_faces.append(faces) + new_sites.append(used_sites_idx) + else: # JOIN + verts, edges, faces = mesh_join(verts, edges, faces) + new_verts.append(verts) + new_edges.append(edges) + new_faces.append(faces) + new_sites.append(used_sites_idx) + + if nested_output: + verts_out.append(new_verts) + edges_out.append(new_edges) + faces_out.append(new_faces) + sites_idx_out.append(new_sites) + else: + verts_out.extend(new_verts) + edges_out.extend(new_edges) + faces_out.extend(new_faces) + sites_idx_out.extend(new_sites) + + self.outputs['Vertices'].sv_set(verts_out) + self.outputs['Edges'].sv_set(edges_out) + self.outputs['Faces'].sv_set(faces_out) + self.outputs['Sites_idx'].sv_set(sites_idx_out) + + +def register(): + bpy.utils.register_class(SvVoronoiOnMeshNodeMK2) + + +def unregister(): + bpy.utils.unregister_class(SvVoronoiOnMeshNodeMK2) diff --git a/nodes/spatial/voronoi_on_solid_mk2.py b/nodes/spatial/voronoi_on_solid_mk2.py index 822b1ef95d..a355150c08 100644 --- a/nodes/spatial/voronoi_on_solid_mk2.py +++ b/nodes/spatial/voronoi_on_solid_mk2.py @@ -121,7 +121,7 @@ def process(self): z_min, z_max = box.ZMin - clipping, box.ZMax + clipping bounds = list(itertools.product([x_min,x_max], [y_min, y_max], [z_min, z_max])) bounds_box_faces = [ [0,1,3,2], [2,3,7,6], [6,7,5,4], [4,5,1,0], [2,6,4,0], [7,3,1,5] ] # cube's faces - verts, edges, faces = voronoi_on_mesh_bmesh(bounds, bounds_box_faces, len(sites), sites, spacing=inset, mode='VOLUME' ) + verts, edges, faces, used_sites_idx = voronoi_on_mesh_bmesh(bounds, bounds_box_faces, len(sites), sites, spacing=inset, mode='VOLUME' ) if isinstance(inset, list): inset = repeat_last_for_length(inset, len(sites)) diff --git a/nodes/spatial/voronoi_on_surface.py b/nodes/spatial/voronoi_on_surface.py index 22054b3fcf..50523702b0 100644 --- a/nodes/spatial/voronoi_on_surface.py +++ b/nodes/spatial/voronoi_on_surface.py @@ -217,7 +217,7 @@ def process(self): uvverts, verts, edges, faces = self.voronoi_uv(surface, uvpoints, maxsides) new_uvverts.append(uvverts) else: - verts, edges, faces = voronoi_on_surface(surface, uvpoints, thickness, self.do_clip, clipping, self.mode == 'REGIONS') + verts, edges, faces, used_sites = voronoi_on_surface(surface, uvpoints, thickness, self.do_clip, clipping, self.mode == 'REGIONS') if (self.mode in {'RIDGES', 'REGIONS'} or self.make_faces) and self.normals: verts, edges, faces = recalc_normals(verts, edges, faces, loop = (self.mode in {'REGIONS', 'RIDGES'})) diff --git a/nodes/spatial/voronoi_on_mesh.py b/old_nodes/voronoi_on_mesh.py similarity index 99% rename from nodes/spatial/voronoi_on_mesh.py rename to old_nodes/voronoi_on_mesh.py index 2ebd328d91..ce45091b2c 100644 --- a/nodes/spatial/voronoi_on_mesh.py +++ b/old_nodes/voronoi_on_mesh.py @@ -190,4 +190,4 @@ def register(): def unregister(): - bpy.utils.unregister_class(SvVoronoiOnMeshNode) + bpy.utils.unregister_class(SvVoronoiOnMeshNode) \ No newline at end of file diff --git a/utils/voronoi3d.py b/utils/voronoi3d.py index a5cb2c69de..2dbea0df89 100644 --- a/utils/voronoi3d.py +++ b/utils/voronoi3d.py @@ -186,7 +186,7 @@ def voronoi3d_layer(n_src_sites, all_sites, make_regions, do_clip, clipping, ski faces_n.append(faces_i) verts, edges, faces = verts_n, edges_n, faces_n - return verts, edges, faces + return verts, edges, faces, all_sites def voronoi_on_surface(surface, uvpoints, thickness, do_clip, clipping, make_regions): u_min, u_max, v_min, v_max = surface.get_domain() @@ -418,6 +418,7 @@ def cut_cell(start_mesh, sites_delaunay_params, site_idx, spacing, center_of_mas bbox_aligned = bounding_box_aligned(verts)[0] start_mesh = bmesh_from_pydata(verts, [], faces, normal_update=False) + used_sites_idx = [] for site_idx in range(len(sites)): cell = cut_cell(start_mesh, sites_delaunay_params, site_idx, spacing[site_idx], center_of_mass, bbox_aligned) if cell is not None: @@ -426,6 +427,7 @@ def cut_cell(start_mesh, sites_delaunay_params, site_idx, spacing, center_of_mas verts_out.append(new_verts) edges_out.append(new_edges) faces_out.append(new_faces) + used_sites_idx.append( site_idx ) start_mesh.clear() # remember to clear empty geometry start_mesh.free() @@ -435,7 +437,7 @@ def cut_cell(start_mesh, sites_delaunay_params, site_idx, spacing, center_of_mas # unb - unpredicted erased mesh (bbox_aligned cannot make predicted results) # sites - count of sites in process # print( f"bisects: {num_bisect: 4d}, unb={num_unpredicted_erased: 4d}, sites={len(sites)}") - return verts_out, edges_out, faces_out + return verts_out, edges_out, faces_out, used_sites_idx def voronoi_on_mesh(verts, faces, sites, thickness, spacing = 0.0, @@ -469,10 +471,10 @@ def voronoi_on_mesh(verts, faces, sites, thickness, else: # VOLUME, SURFACE all_points = sites[:] - verts, edges, faces = voronoi_on_mesh_bmesh(verts, faces, len(sites), all_points, + verts, edges, faces, used_sites_idx = voronoi_on_mesh_bmesh(verts, faces, len(sites), all_points, spacing = spacing, mode = mode, normal_update = normal_update, precision = precision) - return verts, edges, faces, all_points + return verts, edges, faces, used_sites_idx def project_solid_normals(shell, pts, thickness, add_plus=True, add_minus=True, predicate_plus=None, predicate_minus=None): k = 0.5*thickness