From 0abee4f09bbfdb5691af4062551ce99591dd5086 Mon Sep 17 00:00:00 2001 From: satabol Date: Wed, 20 Mar 2024 22:22:59 +0300 Subject: [PATCH 1/7] - fix performance voronoi on mesh. If count of point more or equals 4 then select 4 point randomly and test are they not in one plane. If true then use delaunay without 4D tricks. - append mask input and source sites output --- nodes/spatial/voronoi_on_mesh_mk2.py | 40 +++++++++---- utils/voronoi3d.py | 87 +++++++++++++++++++--------- 2 files changed, 91 insertions(+), 36 deletions(-) diff --git a/nodes/spatial/voronoi_on_mesh_mk2.py b/nodes/spatial/voronoi_on_mesh_mk2.py index 384d8f29db..8d5a5e8c88 100644 --- a/nodes/spatial/voronoi_on_mesh_mk2.py +++ b/nodes/spatial/voronoi_on_mesh_mk2.py @@ -92,11 +92,13 @@ def sv_init(self, context): self.inputs.new('SvStringsSocket', 'Faces') self.inputs.new('SvVerticesSocket', "Sites") # self.inputs.new('SvStringsSocket', 'Thickness').prop_name = 'thickness' + self.inputs.new('SvStringsSocket', "Mask") 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.outputs.new('SvStringsSocket', "Sites_verts") self.update_sockets(context) def draw_buttons(self, context, layout): @@ -119,6 +121,13 @@ def process(self): 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) + + mask_in = self.inputs['Mask'] #.sv_get(deepcopy=False) + if mask_in.is_linked==False: + mask_in = [[[]]] + else: + mask_in = mask_in.sv_get(deepcopy=False) + #thickness_in = self.inputs['Thickness'].sv_get() spacing_in = self.inputs['Spacing'].sv_get(deepcopy=False) @@ -128,6 +137,7 @@ def process(self): faces_in = ensure_nesting_level(faces_in, 4) #thickness_in = ensure_nesting_level(thickness_in, 2) spacing_in = ensure_min_nesting(spacing_in, 2) + mask_in = ensure_min_nesting(mask_in, 3) nested_output = input_level > 3 @@ -137,52 +147,62 @@ def process(self): edges_out = [] faces_out = [] sites_idx_out = [] - for params in zip_long_repeat(verts_in, faces_in, sites_in, spacing_in): + sites_verts_out = [] + for params in zip_long_repeat(verts_in, faces_in, sites_in, spacing_in, mask_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, + new_sites_idx = [] + new_sites_verts = [] + for verts, faces, sites, spacing, mask in zip_long_repeat(*params): + verts, edges, faces, used_sites_idx, used_sites_verts = 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) + precision = precision, + mask = mask + ) 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]) + new_sites_idx.extend([[idx] for idx in used_sites_idx]) + new_sites_verts.extend([[idx] for idx in used_sites_verts]) elif self.join_mode == 'SEPARATE': new_verts.append(verts) new_edges.append(edges) new_faces.append(faces) - new_sites.append(used_sites_idx) + new_sites_idx.append(used_sites_idx) + new_sites_verts.append(used_sites_verts) 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) + new_sites_idx.append(used_sites_idx) + new_sites_verts.append(used_sites_verts) if nested_output: verts_out.append(new_verts) edges_out.append(new_edges) faces_out.append(new_faces) - sites_idx_out.append(new_sites) + sites_idx_out.append(new_sites_idx) + sites_verts_out.append(new_sites_verts) else: verts_out.extend(new_verts) edges_out.extend(new_edges) faces_out.extend(new_faces) - sites_idx_out.extend(new_sites) + sites_idx_out.extend(new_sites_idx) + sites_verts_out.extend(new_sites_verts) 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) + self.outputs['Sites_verts'].sv_set(sites_verts_out) def register(): diff --git a/utils/voronoi3d.py b/utils/voronoi3d.py index 5e1c8cf18e..ba5d512839 100644 --- a/utils/voronoi3d.py +++ b/utils/voronoi3d.py @@ -23,6 +23,7 @@ import bpy import bmesh +import random from mathutils import Vector from mathutils.bvhtree import BVHTree @@ -236,7 +237,7 @@ def calc_bvh_projections(bvh, sites): return np.array(projections) # see additional info https://github.com/nortikin/sverchok/pull/4948 -def voronoi_on_mesh_bmesh(verts, faces, n_orig_sites, sites, spacing=0.0, mode='VOLUME', normal_update = False, precision=1e-8): +def voronoi_on_mesh_bmesh(verts, faces, n_orig_sites, sites, spacing=0.0, mode='VOLUME', normal_update = False, precision=1e-8, mask=[]): def get_sites_delaunay_params(delaunay, n_orig_sites): result = defaultdict(list) @@ -391,18 +392,37 @@ def cut_cell(start_mesh, sites_delaunay_params, site_idx, spacing, center_of_mas edges_out = [] faces_out = [] - # https://github.com/nortikin/sverchok/pull/4952 - # http://www.qhull.org/html/qdelaun.htm - # http://www.qhull.org/html/qh-optc.htm - # https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.Delaunay.html - # Convert sites to 4D - np_sites = np.array([(s[0], s[1], s[2], 0) for s in sites], dtype=np.float32) - # Add 3D tetraedre to the 4D with W=1 - np_sites = np.append(np_sites, [[0.0, 0.0, 0.0, 1], - [1.0, 0.0, 0.0, 1], - [0.0, 1.0, 0.0, 1], - [0.0, 0.0, 1.0, 1], - ], axis=0) + are_sites_plane = True # plane or line + if len(sites)>=4: + # select random sites to test are they are tethraeder or 3D? + # If this thethod get wrong answer then not optimal method will be used. + list_sites_for_test_plane = random.sample( range(0, len(sites)), 4) + v0 = sites[list_sites_for_test_plane[0]] + res = v0 + for I in range(1,4): + v_I = list_sites_for_test_plane[I] + res = np.cross( res, sites[v_I] ) + res_norm = np.linalg.norm( res/np.linalg.norm(res) ) + if res_norm>0.1: + are_sites_plane = False + else: + are_sites_plane = True + + if are_sites_plane: + # https://github.com/nortikin/sverchok/pull/4952 + # http://www.qhull.org/html/qdelaun.htm + # http://www.qhull.org/html/qh-optc.htm + # https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.Delaunay.html + # Convert sites to 4D + np_sites = np.array([(s[0], s[1], s[2], 0) for s in sites], dtype=np.float32) + # Add 3D tetraedre to the 4D with W=1 + np_sites = np.append(np_sites, [[0.0, 0.0, 0.0, 1], + [1.0, 0.0, 0.0, 1], + [0.0, 1.0, 0.0, 1], + [0.0, 0.0, 1.0, 1], + ], axis=0) + else: + np_sites = np.array([(s[0], s[1], s[2]) for s in sites], dtype=np.float32) delaunay = Delaunay(np.array(np_sites, dtype=np.float32)) sites_delaunay_params = get_sites_delaunay_params(delaunay, n_orig_sites) @@ -417,17 +437,28 @@ def cut_cell(start_mesh, sites_delaunay_params, site_idx, spacing, center_of_mas # using for precalc unneeded bisects bbox_aligned, *_ = bounding_box_aligned(verts) + # Extend mask if it is less len of sites + if len(mask)==0: + # if len of mask is 0 then use all sites + mask = [True] * ( len(sites)-len(mask) ) + else: + # else extend mask by false and do not use sites that are not in the mask + mask = mask[:]+[False]*(len(sites)-len(mask) if len(mask)<=len(sites) else 0) + start_mesh = bmesh_from_pydata(verts, [], faces, normal_update=False) used_sites_idx = [] + used_sites_verts = [] 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: - new_verts, new_edges, new_faces = cell - if new_verts: - verts_out.append(new_verts) - edges_out.append(new_edges) - faces_out.append(new_faces) - used_sites_idx.append( site_idx ) + if(mask[site_idx]): + cell = cut_cell(start_mesh, sites_delaunay_params, site_idx, spacing[site_idx], center_of_mass, bbox_aligned) + if cell is not None: + new_verts, new_edges, new_faces = cell + if new_verts: + verts_out.append(new_verts) + edges_out.append(new_edges) + faces_out.append(new_faces) + used_sites_idx.append( site_idx ) + used_sites_verts.append( sites[site_idx] ) start_mesh.clear() # remember to clear empty geometry start_mesh.free() @@ -436,14 +467,18 @@ def cut_cell(start_mesh, sites_delaunay_params, site_idx, spacing, center_of_mas # bisects - count of bisects in cut_cell # unb - unpredicted erased mesh (bbox_aligned cannot make predicted results) # sites - count of sites in process + # mask - mask of sites that uset to the result. Empty list all sites uset to result. # print( f"bisects: {num_bisect: 4d}, unb={num_unpredicted_erased: 4d}, sites={len(sites)}") - return verts_out, edges_out, faces_out, used_sites_idx + return verts_out, edges_out, faces_out, used_sites_idx, used_sites_verts def voronoi_on_mesh(verts, faces, sites, thickness, spacing = 0.0, clip_inner=True, clip_outer=True, do_clip=True, clipping=1.0, mode = 'REGIONS', normal_update=False, - precision = 1e-8): + precision = 1e-8, + mask = [] + ): + bvh = BVHTree.FromPolygons(verts, faces) npoints = len(sites) @@ -471,10 +506,10 @@ def voronoi_on_mesh(verts, faces, sites, thickness, else: # VOLUME, SURFACE all_points = sites[:] - verts, edges, faces, used_sites_idx = voronoi_on_mesh_bmesh(verts, faces, len(sites), all_points, + verts, edges, faces, used_sites_idx, used_sites_verts = voronoi_on_mesh_bmesh(verts, faces, len(sites), all_points, spacing = spacing, mode = mode, normal_update = normal_update, - precision = precision) - return verts, edges, faces, used_sites_idx + precision = precision, mask=mask) + return verts, edges, faces, used_sites_idx, used_sites_verts def project_solid_normals(shell, pts, thickness, add_plus=True, add_minus=True, predicate_plus=None, predicate_minus=None): k = 0.5*thickness From 93b1ab7813ad06ac9be0e23b783ddc9df212ef79 Mon Sep 17 00:00:00 2001 From: satabol Date: Sat, 30 Mar 2024 17:30:08 +0300 Subject: [PATCH 2/7] - Update docs - Update menus --- docs/nodes/analyzer/bbox_aligned.rst | 2 +- .../pulga_physics/pulga_springs_force.rst | 2 +- ...n_mesh_mk2.rst => voronoi_on_mesh_mk3.rst} | 10 +- index.yaml | 2 +- menus/full_by_data_type.yaml | 2 +- menus/full_nortikin.yaml | 2 +- nodes/spatial/voronoi_on_mesh_mk3.py | 208 ++++++++++++++++++ .../voronoi_on_mesh_mk2.py | 40 +--- 8 files changed, 231 insertions(+), 37 deletions(-) rename docs/nodes/spatial/{voronoi_on_mesh_mk2.rst => voronoi_on_mesh_mk3.rst} (96%) create mode 100644 nodes/spatial/voronoi_on_mesh_mk3.py rename {nodes/spatial => old_nodes}/voronoi_on_mesh_mk2.py (83%) diff --git a/docs/nodes/analyzer/bbox_aligned.rst b/docs/nodes/analyzer/bbox_aligned.rst index efd85b0da7..2a7fc8d255 100644 --- a/docs/nodes/analyzer/bbox_aligned.rst +++ b/docs/nodes/analyzer/bbox_aligned.rst @@ -52,7 +52,7 @@ Examples :target: https://github.com/nortikin/sverchok/assets/14288520/84588685-1360-4029-b8de-5346008b982b * Spatial-> :doc:`Populate Mesh ` -* Spatial-> :doc:`Voronoi on Mesh ` +* Spatial-> :doc:`Voronoi on Mesh ` * Viz-> :doc:`Viewer Draw ` * Scene-> :doc:`Get Objects Data ` diff --git a/docs/nodes/pulga_physics/pulga_springs_force.rst b/docs/nodes/pulga_physics/pulga_springs_force.rst index 1ab754ec78..23498fd41e 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_mk2.rst b/docs/nodes/spatial/voronoi_on_mesh_mk3.rst similarity index 96% rename from docs/nodes/spatial/voronoi_on_mesh_mk2.rst rename to docs/nodes/spatial/voronoi_on_mesh_mk3.rst index 4f605de504..2410adfa6b 100644 --- a/docs/nodes/spatial/voronoi_on_mesh_mk2.rst +++ b/docs/nodes/spatial/voronoi_on_mesh_mk3.rst @@ -1,8 +1,8 @@ Voronoi on Mesh =============== -.. image:: https://github.com/nortikin/sverchok/assets/14288520/3782710e-2a14-4bea-a835-37ac6ff715b4 - :target: https://github.com/nortikin/sverchok/assets/14288520/3782710e-2a14-4bea-a835-37ac6ff715b4 +.. image:: https://github.com/nortikin/sverchok/assets/14288520/c907cc2d-7493-4117-a20f-b5e760a47d28 + :target: https://github.com/nortikin/sverchok/assets/14288520/c907cc2d-7493-4117-a20f-b5e760a47d28 Dependencies ------------ @@ -29,6 +29,12 @@ This node has the following inputs: * **Vertices**. Vertices of the mesh to generate Voronoi diagram on. This input is mandatory. * **Faces**. Faces of the mesh to generate Voronoi diagram on. This input is mandatory. +* **Mask**. List of True/False. What Sites will visible. + +.. image:: https://github.com/nortikin/sverchok/assets/14288520/cc788894-acc7-4b1f-a1c0-fb5756e42eb2 + :target: https://github.com/nortikin/sverchok/assets/14288520/cc788894-acc7-4b1f-a1c0-fb5756e42eb2 + + * **Sites**. The points to generate Voronoi diagram for. Usually you want for this points to lie either inside the mesh or on it's surface, but this is not necessary. This input is mandatory. diff --git a/index.yaml b/index.yaml index e6edaf5752..c2430b59c0 100644 --- a/index.yaml +++ b/index.yaml @@ -315,7 +315,7 @@ - SvExVoronoi3DNode - SvExVoronoiSphereNode - SvVoronoiOnSurfaceNode - - SvVoronoiOnMeshNodeMK2 + - SvVoronoiOnMeshNodeMK3 - SvVoronoiOnSolidNodeMK2 - --- - SvLloyd2dNode diff --git a/menus/full_by_data_type.yaml b/menus/full_by_data_type.yaml index e905e2ae10..02bee714b5 100644 --- a/menus/full_by_data_type.yaml +++ b/menus/full_by_data_type.yaml @@ -507,7 +507,7 @@ - SvExVoronoi3DNode - SvExVoronoiSphereNode - SvVoronoiOnSurfaceNode - - SvVoronoiOnMeshNodeMK2 + - SvVoronoiOnMeshNodeMK3 - SvVoronoiOnSolidNodeMK2 - --- - SvLloyd2dNode diff --git a/menus/full_nortikin.yaml b/menus/full_nortikin.yaml index f83e340cec..ce89354f67 100644 --- a/menus/full_nortikin.yaml +++ b/menus/full_nortikin.yaml @@ -383,7 +383,7 @@ - SvExVoronoi3DNode - SvExVoronoiSphereNode - SvVoronoiOnSurfaceNode - - SvVoronoiOnMeshNodeMK2 + - SvVoronoiOnMeshNodeMK3 - SvVoronoiOnSolidNodeMK2 - --- - SvLloyd2dNode diff --git a/nodes/spatial/voronoi_on_mesh_mk3.py b/nodes/spatial/voronoi_on_mesh_mk3.py new file mode 100644 index 0000000000..a9a621c6f6 --- /dev/null +++ b/nodes/spatial/voronoi_on_mesh_mk3.py @@ -0,0 +1,208 @@ +# ##### 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 SvVoronoiOnMeshNodeMK3(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Voronoi Mesh + Tooltip: Generate Voronoi diagram on the surface of a mesh object + """ + bl_idname = 'SvVoronoiOnMeshNodeMK3' + 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', "Mask") + 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.outputs.new('SvStringsSocket', "Sites_verts") + 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) + + mask_in = self.inputs['Mask'] #.sv_get(deepcopy=False) + if mask_in.is_linked==False: + mask_in = [[[]]] + else: + mask_in = mask_in.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) + mask_in = ensure_min_nesting(mask_in, 3) + + nested_output = input_level > 3 + + precision = 10 ** (-self.accuracy) + + verts_out = [] + edges_out = [] + faces_out = [] + sites_idx_out = [] + sites_verts_out = [] + for params in zip_long_repeat(verts_in, faces_in, sites_in, spacing_in, mask_in): + new_verts = [] + new_edges = [] + new_faces = [] + new_sites_idx = [] + new_sites_verts = [] + for verts, faces, sites, spacing, mask in zip_long_repeat(*params): + verts, edges, faces, used_sites_idx, used_sites_verts = 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, + mask = mask + ) + + if self.join_mode == 'FLAT': + new_verts.extend(verts) + new_edges.extend(edges) + new_faces.extend(faces) + new_sites_idx.extend([[idx] for idx in used_sites_idx]) + new_sites_verts.extend([[idx] for idx in used_sites_verts]) + elif self.join_mode == 'SEPARATE': + new_verts.append(verts) + new_edges.append(edges) + new_faces.append(faces) + new_sites_idx.append(used_sites_idx) + new_sites_verts.append(used_sites_verts) + else: # JOIN + verts, edges, faces = mesh_join(verts, edges, faces) + new_verts.append(verts) + new_edges.append(edges) + new_faces.append(faces) + new_sites_idx.append(used_sites_idx) + new_sites_verts.append(used_sites_verts) + + if nested_output: + verts_out.append(new_verts) + edges_out.append(new_edges) + faces_out.append(new_faces) + sites_idx_out.append(new_sites_idx) + sites_verts_out.append(new_sites_verts) + else: + verts_out.extend(new_verts) + edges_out.extend(new_edges) + faces_out.extend(new_faces) + sites_idx_out.extend(new_sites_idx) + sites_verts_out.extend(new_sites_verts) + + 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) + self.outputs['Sites_verts'].sv_set(sites_verts_out) + +classes = [SvVoronoiOnMeshNodeMK3] +register, unregister = bpy.utils.register_classes_factory(classes) \ No newline at end of file diff --git a/nodes/spatial/voronoi_on_mesh_mk2.py b/old_nodes/voronoi_on_mesh_mk2.py similarity index 83% rename from nodes/spatial/voronoi_on_mesh_mk2.py rename to old_nodes/voronoi_on_mesh_mk2.py index 8d5a5e8c88..384d8f29db 100644 --- a/nodes/spatial/voronoi_on_mesh_mk2.py +++ b/old_nodes/voronoi_on_mesh_mk2.py @@ -92,13 +92,11 @@ def sv_init(self, context): self.inputs.new('SvStringsSocket', 'Faces') self.inputs.new('SvVerticesSocket', "Sites") # self.inputs.new('SvStringsSocket', 'Thickness').prop_name = 'thickness' - self.inputs.new('SvStringsSocket', "Mask") 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.outputs.new('SvStringsSocket', "Sites_verts") self.update_sockets(context) def draw_buttons(self, context, layout): @@ -121,13 +119,6 @@ def process(self): 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) - - mask_in = self.inputs['Mask'] #.sv_get(deepcopy=False) - if mask_in.is_linked==False: - mask_in = [[[]]] - else: - mask_in = mask_in.sv_get(deepcopy=False) - #thickness_in = self.inputs['Thickness'].sv_get() spacing_in = self.inputs['Spacing'].sv_get(deepcopy=False) @@ -137,7 +128,6 @@ def process(self): faces_in = ensure_nesting_level(faces_in, 4) #thickness_in = ensure_nesting_level(thickness_in, 2) spacing_in = ensure_min_nesting(spacing_in, 2) - mask_in = ensure_min_nesting(mask_in, 3) nested_output = input_level > 3 @@ -147,62 +137,52 @@ def process(self): edges_out = [] faces_out = [] sites_idx_out = [] - sites_verts_out = [] - for params in zip_long_repeat(verts_in, faces_in, sites_in, spacing_in, mask_in): + for params in zip_long_repeat(verts_in, faces_in, sites_in, spacing_in): new_verts = [] new_edges = [] new_faces = [] - new_sites_idx = [] - new_sites_verts = [] - for verts, faces, sites, spacing, mask in zip_long_repeat(*params): - verts, edges, faces, used_sites_idx, used_sites_verts = voronoi_on_mesh(verts, faces, sites, thickness=0, + 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, - mask = mask - ) + precision = precision) if self.join_mode == 'FLAT': new_verts.extend(verts) new_edges.extend(edges) new_faces.extend(faces) - new_sites_idx.extend([[idx] for idx in used_sites_idx]) - new_sites_verts.extend([[idx] for idx in used_sites_verts]) + 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_idx.append(used_sites_idx) - new_sites_verts.append(used_sites_verts) + 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_idx.append(used_sites_idx) - new_sites_verts.append(used_sites_verts) + 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_idx) - sites_verts_out.append(new_sites_verts) + 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_idx) - sites_verts_out.extend(new_sites_verts) + 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) - self.outputs['Sites_verts'].sv_set(sites_verts_out) def register(): From fe05a7b9ce90da70393dd37e6a45286599980069 Mon Sep 17 00:00:00 2001 From: satabol Date: Fri, 5 Apr 2024 22:07:17 +0300 Subject: [PATCH 3/7] refactor file of voronoy on mesh to see main history --- nodes/spatial/{voronoi_on_mesh_mk3.py => voronoi_on_mesh_mk2.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename nodes/spatial/{voronoi_on_mesh_mk3.py => voronoi_on_mesh_mk2.py} (100%) diff --git a/nodes/spatial/voronoi_on_mesh_mk3.py b/nodes/spatial/voronoi_on_mesh_mk2.py similarity index 100% rename from nodes/spatial/voronoi_on_mesh_mk3.py rename to nodes/spatial/voronoi_on_mesh_mk2.py From 6b2424fc9b33c793abef4cceb92b6cb609a4feb8 Mon Sep 17 00:00:00 2001 From: satabol Date: Fri, 5 Apr 2024 22:09:14 +0300 Subject: [PATCH 4/7] refactor file of voronoy on mesh to see main history --- .../spatial/{voronoi_on_mesh_mk3.rst => voronoi_on_mesh_mk2.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/nodes/spatial/{voronoi_on_mesh_mk3.rst => voronoi_on_mesh_mk2.rst} (100%) diff --git a/docs/nodes/spatial/voronoi_on_mesh_mk3.rst b/docs/nodes/spatial/voronoi_on_mesh_mk2.rst similarity index 100% rename from docs/nodes/spatial/voronoi_on_mesh_mk3.rst rename to docs/nodes/spatial/voronoi_on_mesh_mk2.rst From 47b402d0061a5d13ab58f0aff0817b4ee45af42e Mon Sep 17 00:00:00 2001 From: satabol Date: Fri, 5 Apr 2024 22:10:48 +0300 Subject: [PATCH 5/7] refactor file of voronoy on mesh to see main history --- docs/nodes/analyzer/bbox_aligned.rst | 2 +- docs/nodes/pulga_physics/pulga_springs_force.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/nodes/analyzer/bbox_aligned.rst b/docs/nodes/analyzer/bbox_aligned.rst index 2a7fc8d255..efd85b0da7 100644 --- a/docs/nodes/analyzer/bbox_aligned.rst +++ b/docs/nodes/analyzer/bbox_aligned.rst @@ -52,7 +52,7 @@ Examples :target: https://github.com/nortikin/sverchok/assets/14288520/84588685-1360-4029-b8de-5346008b982b * Spatial-> :doc:`Populate Mesh ` -* Spatial-> :doc:`Voronoi on Mesh ` +* Spatial-> :doc:`Voronoi on Mesh ` * Viz-> :doc:`Viewer Draw ` * Scene-> :doc:`Get Objects Data ` diff --git a/docs/nodes/pulga_physics/pulga_springs_force.rst b/docs/nodes/pulga_physics/pulga_springs_force.rst index 23498fd41e..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` From c340eab156b4f24928d6907a1f8cd2c956f6daba Mon Sep 17 00:00:00 2001 From: satabol Date: Fri, 5 Apr 2024 23:20:32 +0300 Subject: [PATCH 6/7] - Append mask modes (one can select mask node - by mask or by indices) - append inversion of mask (output index of sites can by inverted) --- nodes/spatial/voronoi_on_mesh_mk2.py | 58 +++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/nodes/spatial/voronoi_on_mesh_mk2.py b/nodes/spatial/voronoi_on_mesh_mk2.py index a9a621c6f6..90bfeef385 100644 --- a/nodes/spatial/voronoi_on_mesh_mk2.py +++ b/nodes/spatial/voronoi_on_mesh_mk2.py @@ -25,6 +25,7 @@ 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 +import numpy as np class SvVoronoiOnMeshNodeMK3(SverchCustomTreeNode, bpy.types.Node): @@ -80,6 +81,30 @@ def update_sockets(self, context): default = 'FLAT', update = updateNode) + def updateMaskMode(self, context): + if self.mask_mode=='MASK': + self.inputs["Mask"].label = "Mask of Sites" + elif self.mask_mode=='INDEXES': + self.inputs["Mask"].label = "Indexes of Sites" + updateNode(self, context) + + mask_modes = [ + ('MASK', "Mask of sites", "Boolean value (0/1) to mask of sites", 0), + ('INDEXES', "Index of sites", "Indexes of sites to mask", 1), + ] + mask_mode : EnumProperty( + name = "Mask mode", + items = mask_modes, + default = 'MASK', + update = updateMaskMode) + + mask_inversion : BoolProperty( + name = "Inversion", + default = False, + description="Invert mask of sites", + update = updateNode) + + accuracy : IntProperty( name = "Accuracy", description = "Accuracy for mesh bisecting procedure", @@ -92,7 +117,7 @@ def sv_init(self, context): self.inputs.new('SvStringsSocket', 'Faces') self.inputs.new('SvVerticesSocket', "Sites") # self.inputs.new('SvStringsSocket', 'Thickness').prop_name = 'thickness' - self.inputs.new('SvStringsSocket', "Mask") + self.inputs.new('SvStringsSocket', "Mask").label = "Mask of Sites" self.inputs.new('SvStringsSocket', 'Spacing').prop_name = 'spacing' self.outputs.new('SvVerticesSocket', "Vertices") self.outputs.new('SvStringsSocket', "Edges") @@ -104,6 +129,9 @@ def sv_init(self, context): def draw_buttons(self, context, layout): layout.label(text="Mode:") layout.prop(self, "mode", text='') + split = layout.column().split(factor=0.6) + split.column().prop(self, "mask_mode", text='') + split.column().prop(self, "mask_inversion", text='Invert') if self.mode == 'VOLUME': layout.prop(self, 'normals') layout.label(text='Output nesting:') @@ -155,6 +183,34 @@ def process(self): new_sites_idx = [] new_sites_verts = [] for verts, faces, sites, spacing, mask in zip_long_repeat(*params): + # if mask is zero or not connected then do not mask any. Except of inversion, + if not mask: + np_mask = np.ones(len(sites), dtype=bool) + if self.mask_inversion==True: + np_mask = np.invert(np_mask) + mask = np_mask.tolist() + else: + if self.mask_mode=='MASK': + if self.mask_inversion==True: + mask = list( map( lambda v: False if v==0 else True, mask) ) + mask = mask[:len(sites)] + np_mask = np.zeros(len(sites), dtype=bool) + np_mask[0:len(mask)]=mask + np_mask = np.invert(np_mask) + mask = np_mask.tolist() + pass + elif self.mask_mode=='INDEXES': + mask_len = len(sites) + mask_range = [] + for x in mask: + if -mask_len Date: Sat, 6 Apr 2024 00:59:59 +0300 Subject: [PATCH 7/7] - fix glitch of test if sites are planes or lines (if not then it can increase performance) --- utils/voronoi3d.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/utils/voronoi3d.py b/utils/voronoi3d.py index ba5d512839..8f6dbf1515 100644 --- a/utils/voronoi3d.py +++ b/utils/voronoi3d.py @@ -397,12 +397,12 @@ def cut_cell(start_mesh, sites_delaunay_params, site_idx, spacing, center_of_mas # select random sites to test are they are tethraeder or 3D? # If this thethod get wrong answer then not optimal method will be used. list_sites_for_test_plane = random.sample( range(0, len(sites)), 4) - v0 = sites[list_sites_for_test_plane[0]] - res = v0 - for I in range(1,4): + v0 = Vector(sites[list_sites_for_test_plane[0]]) + res = Vector(sites[list_sites_for_test_plane[1]])-v0 + for I in range(2,4): v_I = list_sites_for_test_plane[I] - res = np.cross( res, sites[v_I] ) - res_norm = np.linalg.norm( res/np.linalg.norm(res) ) + res = np.cross( res, Vector(sites[v_I])-v0 ) + res_norm = np.linalg.norm(res,ord=1) if res_norm>0.1: are_sites_plane = False else: