From f03eb72d46a8cbd1806239097655b59cc298d2ae Mon Sep 17 00:00:00 2001 From: satabol Date: Sun, 13 Aug 2023 14:43:39 +0300 Subject: [PATCH 1/3] Update node "Voronoi on Solid": - update using Voronoi. Get new cells for border cells. - update parameter inset (now it use distance. Before was percent.) --- docs/nodes/spatial/populate_solid.rst | 2 +- ..._on_solid.rst => voronoi_on_solid_mk2.rst} | 0 index.yaml | 2 +- menus/full_by_data_type.yaml | 2 +- menus/full_nortikin.yaml | 2 +- nodes/spatial/voronoi_on_solid_mk2.py | 202 ++++++++++++++++++ .../spatial => old_nodes}/voronoi_on_solid.py | 0 7 files changed, 206 insertions(+), 4 deletions(-) rename docs/nodes/spatial/{voronoi_on_solid.rst => voronoi_on_solid_mk2.rst} (100%) create mode 100644 nodes/spatial/voronoi_on_solid_mk2.py rename {nodes/spatial => old_nodes}/voronoi_on_solid.py (100%) diff --git a/docs/nodes/spatial/populate_solid.rst b/docs/nodes/spatial/populate_solid.rst index 785ab2e939..a1a930a874 100644 --- a/docs/nodes/spatial/populate_solid.rst +++ b/docs/nodes/spatial/populate_solid.rst @@ -231,5 +231,5 @@ Example of "Radius Field" mode usage: * Generator-> :doc:`IcoSphere ` * Fields-> :doc:`Scalar Field Formula ` * Solids-> :doc:`Cylinder (Solid) ` -* Solids-> :doc:`Voronoi on Solid ` +* Solids-> :doc:`Voronoi on Solid ` * Viz-> :doc:`Viewer Draw ` \ No newline at end of file diff --git a/docs/nodes/spatial/voronoi_on_solid.rst b/docs/nodes/spatial/voronoi_on_solid_mk2.rst similarity index 100% rename from docs/nodes/spatial/voronoi_on_solid.rst rename to docs/nodes/spatial/voronoi_on_solid_mk2.rst diff --git a/index.yaml b/index.yaml index 8b2e86e866..2ce92be37d 100644 --- a/index.yaml +++ b/index.yaml @@ -316,7 +316,7 @@ - SvExVoronoiSphereNode - SvVoronoiOnSurfaceNode - SvVoronoiOnMeshNode - - SvVoronoiOnSolidNode + - SvVoronoiOnSolidNodeMK2 - --- - SvLloyd2dNode - SvLloyd3dNode diff --git a/menus/full_by_data_type.yaml b/menus/full_by_data_type.yaml index 4bf13ff228..19c377d2dc 100644 --- a/menus/full_by_data_type.yaml +++ b/menus/full_by_data_type.yaml @@ -507,7 +507,7 @@ - SvExVoronoiSphereNode - SvVoronoiOnSurfaceNode - SvVoronoiOnMeshNode - - SvVoronoiOnSolidNode + - SvVoronoiOnSolidNodeMK2 - --- - SvLloyd2dNode - SvLloyd3dNode diff --git a/menus/full_nortikin.yaml b/menus/full_nortikin.yaml index 3f86edbf1c..0cc31e2cd0 100644 --- a/menus/full_nortikin.yaml +++ b/menus/full_nortikin.yaml @@ -384,7 +384,7 @@ - SvExVoronoiSphereNode - SvVoronoiOnSurfaceNode - SvVoronoiOnMeshNode - - SvVoronoiOnSolidNode + - SvVoronoiOnSolidNodeMK2 - --- - SvLloyd2dNode - SvLloyd3dNode diff --git a/nodes/spatial/voronoi_on_solid_mk2.py b/nodes/spatial/voronoi_on_solid_mk2.py new file mode 100644 index 0000000000..37e07a6b1a --- /dev/null +++ b/nodes/spatial/voronoi_on_solid_mk2.py @@ -0,0 +1,202 @@ +# ##### 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 itertools +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, repeat_last_for_length +from sverchok.utils.voronoi3d import voronoi_on_mesh_bmesh +from sverchok.utils.geom import scale_relative, center, diameter +from sverchok.utils.solid import BMESH, svmesh_to_solid +from sverchok.dependencies import FreeCAD + +if FreeCAD is not None: + import Part + + +class SvVoronoiOnSolidNodeMK2(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Voronoi Solid + Tooltip: Generate Voronoi diagram on the Solid object + """ + bl_idname = 'SvVoronoiOnSolidNodeMK2' + bl_label = 'Voronoi on Solid' + bl_icon = 'OUTLINER_OB_EMPTY' + sv_icon = 'SV_VORONOI' + sv_dependencies = {'scipy', 'FreeCAD'} + + modes = [ + ('SURFACE', "Surface", "Generate regions of Voronoi diagram on the surface of the solid", 0), + ('VOLUME', "Volume", "Split volume of the solid body into regions of Voronoi diagram", 2) + ] + + mode : EnumProperty( + name = "Mode", + items = modes, + update = updateNode) + + accuracy : IntProperty( + name = "Accuracy", + description = "Accuracy for mesh to solid transformation", + default = 6, + min = 1, + update = updateNode) + + inset : FloatProperty( + name = "Inset", + min = 0.0, #max = 1.0, + default = 0.1, + description="Distance to leave between generated Voronoi regions", + update = updateNode) + + scale_types = [ + ('SITE', "Site", "Scale each region relative to corresponding site location", 0), + ('MEAN', "Barycenter", "Scale each region relative to it's barycenter, i.e. average location of it's vertices", 1) + ] + + scale_center : EnumProperty( + name = "Scale around", + description = "Defines the center, along which the regions of Voronoi diagram are to be scaled in order to make inset", + items = scale_types, + default = 'SITE', + update = updateNode) + + flat_output : BoolProperty( + name = "Flat output", + description = "If checked, output single flat list of fragments for all input solids; otherwise, output a separate list of fragments for each solid.", + default = True, + update = updateNode) + + def sv_init(self, context): + self.inputs.new('SvSolidSocket', 'Solid') + self.inputs.new('SvVerticesSocket', "Sites") + self.inputs.new('SvStringsSocket', "Inset").prop_name = 'inset' + self.outputs.new('SvSolidSocket', "InnerSolid") + self.outputs.new('SvSolidSocket', "OuterSolid") + + def draw_buttons(self, context, layout): + layout.prop(self, "mode") + layout.prop(self, "flat_output") + + def draw_buttons_ext(self, context, layout): + self.draw_buttons(context, layout) + layout.prop(self, 'scale_center') + layout.prop(self, 'accuracy') + + def scale_cells(self, verts, sites, insets, precision): + if all(i == 0.0 for i in insets): + return verts + verts_out = [] + for vs, site, inset in zip(verts, sites, insets): + if inset >= 1.0: + continue + if self.scale_center == 'SITE': + c = site + else: + c = center(vs) + vs1 = scale_relative(vs, c, 1.0 - inset) + if diameter(vs1, axis=None) <= precision: + continue + verts_out.append(vs1) + return verts_out + + def process(self): + + if not any(socket.is_linked for socket in self.outputs): + return + + solid_in = self.inputs['Solid'].sv_get() + sites_in = self.inputs['Sites'].sv_get() + inset_in = self.inputs['Inset'].sv_get() + + solid_in = ensure_nesting_level(solid_in, 2, data_types=(Part.Shape,)) + input_level = get_data_nesting_level(sites_in) + sites_in = ensure_nesting_level(sites_in, 4) + inset_in = ensure_min_nesting(inset_in, 2) + + nested_output = input_level > 3 + need_inner = self.outputs['InnerSolid'].is_linked + need_outer = self.outputs['OuterSolid'].is_linked + + precision = 10 ** (-self.accuracy) + + inner_fragments_out = [] + outer_fragments_out = [] + for params in zip_long_repeat(solid_in, sites_in, inset_in): + new_inner_fragments = [] + new_outer_fragments = [] + for solid, sites, inset in zip_long_repeat(*params): + #verts, edges, faces = voronoi_on_solid(solid, sites, do_clip=True, clipping=None) + box = solid.BoundBox + clipping = 1 + x_min, x_max = box.XMin - clipping, box.XMax + clipping + y_min, y_max = box.YMin - clipping, box.YMax + clipping + 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])) + verts, edges, faces = voronoi_on_mesh_bmesh(bounds, [ [0,1,3,2], [2,3,7,6], [6,7,5,4], [4,5,1,0], [2,6,4,0], [7,3,1,5] ], len(sites), sites, spacing=inset, mode='VOLUME' ) + + if isinstance(inset, list): + inset = repeat_last_for_length(inset, len(sites)) + else: + inset = [inset for i in range(len(sites))] + #verts = self.scale_cells(verts, sites, inset, precision) + fragments = [svmesh_to_solid(vs, fs, precision, method=BMESH, remove_splitter=False) for vs, fs in zip(verts, faces)] + + if self.mode == 'SURFACE': + if solid.Shells: + shell = solid.Shells[0] + else: + shell = Part.Shell(solid.Faces) + src = shell + else: # VOLUME + src = solid + + if need_inner: + inner = [src.common(fragments)] + if self.flat_output: + new_inner_fragments.extend(inner) + else: + new_inner_fragments.append(inner) + + if need_outer: + outer = [src.cut(fragments)] + if self.flat_output: + new_outer_fragments.extend(outer) + else: + new_outer_fragments.append(outer) + + if nested_output: + inner_fragments_out.append(new_inner_fragments) + outer_fragments_out.append(new_outer_fragments) + else: + inner_fragments_out.extend(new_inner_fragments) + outer_fragments_out.extend(new_outer_fragments) + + self.outputs['InnerSolid'].sv_set(inner_fragments_out) + self.outputs['OuterSolid'].sv_set(outer_fragments_out) + + +def register(): + bpy.utils.register_class(SvVoronoiOnSolidNodeMK2) + + +def unregister(): + bpy.utils.unregister_class(SvVoronoiOnSolidNodeMK2) diff --git a/nodes/spatial/voronoi_on_solid.py b/old_nodes/voronoi_on_solid.py similarity index 100% rename from nodes/spatial/voronoi_on_solid.py rename to old_nodes/voronoi_on_solid.py From 8cb4642b2596c9f96c6174af589f7cd7dae4906d Mon Sep 17 00:00:00 2001 From: satabol Date: Sun, 13 Aug 2023 16:23:35 +0300 Subject: [PATCH 2/3] append comment --- nodes/spatial/voronoi_on_solid_mk2.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nodes/spatial/voronoi_on_solid_mk2.py b/nodes/spatial/voronoi_on_solid_mk2.py index 37e07a6b1a..ca8bf7d24d 100644 --- a/nodes/spatial/voronoi_on_solid_mk2.py +++ b/nodes/spatial/voronoi_on_solid_mk2.py @@ -145,6 +145,7 @@ def process(self): new_outer_fragments = [] for solid, sites, inset in zip_long_repeat(*params): #verts, edges, faces = voronoi_on_solid(solid, sites, do_clip=True, clipping=None) + # see more info: https://github.com/nortikin/sverchok/pull/4977 box = solid.BoundBox clipping = 1 x_min, x_max = box.XMin - clipping, box.XMax + clipping From d17d3738fe886e13f0ecc56c85f761785e449803 Mon Sep 17 00:00:00 2001 From: satabol Date: Sun, 13 Aug 2023 17:44:36 +0300 Subject: [PATCH 3/3] fix. 1. remove unused elements 2. fix exception in a case with no fragments (cites) --- nodes/spatial/voronoi_on_solid_mk2.py | 44 +++++---------------------- 1 file changed, 8 insertions(+), 36 deletions(-) diff --git a/nodes/spatial/voronoi_on_solid_mk2.py b/nodes/spatial/voronoi_on_solid_mk2.py index ca8bf7d24d..822b1ef95d 100644 --- a/nodes/spatial/voronoi_on_solid_mk2.py +++ b/nodes/spatial/voronoi_on_solid_mk2.py @@ -24,7 +24,6 @@ from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level, get_data_nesting_level,\ ensure_min_nesting, repeat_last_for_length from sverchok.utils.voronoi3d import voronoi_on_mesh_bmesh -from sverchok.utils.geom import scale_relative, center, diameter from sverchok.utils.solid import BMESH, svmesh_to_solid from sverchok.dependencies import FreeCAD @@ -67,18 +66,6 @@ class SvVoronoiOnSolidNodeMK2(SverchCustomTreeNode, bpy.types.Node): description="Distance to leave between generated Voronoi regions", update = updateNode) - scale_types = [ - ('SITE', "Site", "Scale each region relative to corresponding site location", 0), - ('MEAN', "Barycenter", "Scale each region relative to it's barycenter, i.e. average location of it's vertices", 1) - ] - - scale_center : EnumProperty( - name = "Scale around", - description = "Defines the center, along which the regions of Voronoi diagram are to be scaled in order to make inset", - items = scale_types, - default = 'SITE', - update = updateNode) - flat_output : BoolProperty( name = "Flat output", description = "If checked, output single flat list of fragments for all input solids; otherwise, output a separate list of fragments for each solid.", @@ -98,26 +85,8 @@ def draw_buttons(self, context, layout): def draw_buttons_ext(self, context, layout): self.draw_buttons(context, layout) - layout.prop(self, 'scale_center') layout.prop(self, 'accuracy') - def scale_cells(self, verts, sites, insets, precision): - if all(i == 0.0 for i in insets): - return verts - verts_out = [] - for vs, site, inset in zip(verts, sites, insets): - if inset >= 1.0: - continue - if self.scale_center == 'SITE': - c = site - else: - c = center(vs) - vs1 = scale_relative(vs, c, 1.0 - inset) - if diameter(vs1, axis=None) <= precision: - continue - verts_out.append(vs1) - return verts_out - def process(self): if not any(socket.is_linked for socket in self.outputs): @@ -144,7 +113,6 @@ def process(self): new_inner_fragments = [] new_outer_fragments = [] for solid, sites, inset in zip_long_repeat(*params): - #verts, edges, faces = voronoi_on_solid(solid, sites, do_clip=True, clipping=None) # see more info: https://github.com/nortikin/sverchok/pull/4977 box = solid.BoundBox clipping = 1 @@ -152,13 +120,13 @@ def process(self): y_min, y_max = box.YMin - clipping, box.YMax + clipping 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])) - verts, edges, faces = voronoi_on_mesh_bmesh(bounds, [ [0,1,3,2], [2,3,7,6], [6,7,5,4], [4,5,1,0], [2,6,4,0], [7,3,1,5] ], len(sites), sites, spacing=inset, mode='VOLUME' ) + 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' ) if isinstance(inset, list): inset = repeat_last_for_length(inset, len(sites)) else: inset = [inset for i in range(len(sites))] - #verts = self.scale_cells(verts, sites, inset, precision) fragments = [svmesh_to_solid(vs, fs, precision, method=BMESH, remove_splitter=False) for vs, fs in zip(verts, faces)] if self.mode == 'SURFACE': @@ -171,14 +139,18 @@ def process(self): src = solid if need_inner: - inner = [src.common(fragments)] + inner = [src] + if fragments: + inner = [src.common(fragments)] if self.flat_output: new_inner_fragments.extend(inner) else: new_inner_fragments.append(inner) if need_outer: - outer = [src.cut(fragments)] + outer = [src] + if fragments: + outer = [src.cut(fragments)] if self.flat_output: new_outer_fragments.extend(outer) else: