Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix 5103 Mesh Clustering, new Node #5104

Merged
merged 5 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,14 @@ def draw_message(box, package, dependencies=None):
except ImportError:
ezdxf = None

pyacvd_d = sv_dependencies["pyacvd"] = SvDependency("pyacvd","https://github.com/pyvista/pyacvd")
pyacvd_d.pip_installable = True
try:
import pyacvd
pyacvd_d.module = pyacvd
except ImportError:
pyacvd = None

settings.pip = pip
settings.sv_dependencies = sv_dependencies
settings.ensurepip = ensurepip
Expand Down
72 changes: 72 additions & 0 deletions docs/nodes/spatial/mesh_clustering.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
Mesh clustering
===============

.. image:: https://github.com/nortikin/sverchok/assets/14288520/d850920c-856c-4beb-a273-a779d86ef434
:target: https://github.com/nortikin/sverchok/assets/14288520/d850920c-856c-4beb-a273-a779d86ef434

Dependencies
------------

This node requires pyacvd_ library to work. One can install it in addon settings:

.. image:: https://github.com/nortikin/sverchok/assets/14288520/65467755-82b9-4e71-802d-0978eb2e849d
:target: https://github.com/nortikin/sverchok/assets/14288520/65467755-82b9-4e71-802d-0978eb2e849d

Functionality
-------------

This node takes a surface mesh and returns a uniformly meshed surface using voronoi clustering.

.. image:: https://github.com/nortikin/sverchok/assets/14288520/f50b985a-45b6-482c-8b7c-1f7551a50b8f
:target: https://github.com/nortikin/sverchok/assets/14288520/f50b985a-45b6-482c-8b7c-1f7551a50b8f

.. image:: https://github.com/nortikin/sverchok/assets/14288520/e761653b-a88f-42e1-b222-fa243b1642a5
:target: https://github.com/nortikin/sverchok/assets/14288520/e761653b-a88f-42e1-b222-fa243b1642a5

Inputs
------

This node has the following inputs:

- **Vertices**, **Edges**, **Faces** - Source mesh

Parameters
----------

- **Subdivide** - if source mesh is not dense enough for uniform remeshing then subdivide source mesh. A linear subdivision of the mesh. If model has high dense one can low Subdivide param to zero. For low dense mesh a value 3 is good.

.. image:: https://github.com/nortikin/sverchok/assets/14288520/2d2d6e3a-747e-4be2-88e2-50e9c9b39c1e
:target: https://github.com/nortikin/sverchok/assets/14288520/2d2d6e3a-747e-4be2-88e2-50e9c9b39c1e

- **Max itereation** - Max iteration of clusterization.
- **Clusters** - Cluster counts.

.. image:: https://github.com/nortikin/sverchok/assets/14288520/6d49a5cf-704d-4566-b56e-861a88b9d34f
:target: https://github.com/nortikin/sverchok/assets/14288520/6d49a5cf-704d-4566-b56e-861a88b9d34f

- **Triangulate mesh polygons**. If your mesh has faces with different count of vertices then this mash has to be triangulated.

Outputs
-------

Triangulated mesh:

- **Vertices**, **Edges**, **Faces**


Example of Usage
----------------

.. image:: https://github.com/nortikin/sverchok/assets/14288520/4140d9d7-c710-4ad7-a223-5ca62f4069f1
:target: https://github.com/nortikin/sverchok/assets/14288520/4140d9d7-c710-4ad7-a223-5ca62f4069f1

High poly clusterizing
----------------------

Subdivide is zero.

.. image:: https://github.com/nortikin/sverchok/assets/14288520/f4ca089c-7704-41aa-943f-b629f1dc10ab
:target: https://github.com/nortikin/sverchok/assets/14288520/f4ca089c-7704-41aa-943f-b629f1dc10ab


.. _pyacvd: https://github.com/pyvista/pyacvd
1 change: 1 addition & 0 deletions index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@
- ---
- SvConvexHullNodeMK2
- SvConcaveHullNode
- SvMeshClusteringNode

- Transforms:
- icon_name: ORIENTATION_LOCAL
Expand Down
1 change: 1 addition & 0 deletions menus/full_by_data_type.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,7 @@
- ---
- SvConvexHullNodeMK2
- SvConcaveHullNode
- SvMeshClusteringNode

- ---

Expand Down
1 change: 1 addition & 0 deletions menus/full_nortikin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@
- ---
- SvConvexHullNodeMK2
- SvConcaveHullNode
- SvMeshClusteringNode


################################################################################
Expand Down
191 changes: 191 additions & 0 deletions nodes/spatial/mesh_clustering.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
# ##### 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 #####

from itertools import product
import numpy as np
from sverchok.dependencies import pyacvd
if pyacvd is not None:
from pyvista import examples, PolyData
#import pyacvd

import bpy
import bmesh
from bpy.props import BoolVectorProperty, EnumProperty, BoolProperty, FloatProperty, IntProperty
from mathutils import Matrix

from sverchok.node_tree import SverchCustomTreeNode
from sverchok.utils.nodes_mixins.recursive_nodes import SvRecursiveNode
from sverchok.utils.sv_mesh_utils import mesh_join
from sverchok.utils.sv_bmesh_utils import bmesh_from_pydata, pydata_from_bmesh
from sverchok.utils.modules.matrix_utils import matrix_apply_np

from sverchok.data_structure import dataCorrect, updateNode, zip_long_repeat
from sverchok.utils.geom import bounding_box_aligned

class SvMeshClusteringNode(SverchCustomTreeNode, bpy.types.Node, SvRecursiveNode):
"""
Triggers: Bbox 2D or 3D
Tooltip: Get vertices bounding box (vertices, sizes, center)
"""
bl_idname = 'SvMeshClusteringNode'
bl_label = 'Mesh Clustering (Alpha)'
bl_icon = 'IMAGE_ALPHA'
sv_icon = 'SV_DELAUNAY'
sv_dependencies = {'pyacvd'}

output_as_numpy : BoolProperty(
name = "Output as numpy",
description = "If checked then output sockets as numpy",
default = False,
update = updateNode) # type: ignore


quad_modes = [
( "BEAUTY", "Beauty", "Split the quads in nice triangles, slower method", 1),
( "FIXED", "Fixed", "Split the quads on the 1st and 3rd vertices", 2),
( "ALTERNATE", "Fixed Alternate", "Split the quads on the 2nd and 4th vertices", 3),
("SHORT_EDGE", "Shortest Diagonal", "Split the quads based on the distance between the vertices", 4)
]

ngon_modes = [
( "BEAUTY", "Beauty", "Arrange the new triangles nicely, slower method", 1),
("EAR_CLIP", "Clip", "Split the ngons using a scanfill algorithm", 2)
]

quad_mode: EnumProperty(
name='Quads mode',
description="Quads processing mode",
items=quad_modes,
default="BEAUTY",
update=updateNode) # type: ignore

ngon_mode: EnumProperty(
name="Polygons mode",
description="Polygons processing mode",
items=ngon_modes,
default="BEAUTY",
update=updateNode) # type: ignore

cluster_subdivide : IntProperty(
min=0, default=0, name='Subdivide',
description="Cluster subdivide", update=updateNode) # type: ignore
max_iter : IntProperty(
min=1, default=100, name='Max iteration',
description="Max iteration of clusterization", update=updateNode) # type: ignore

cluster_counts : IntProperty(
min=0, default=1000, name='Clusters',
description="Cluster counts", update=updateNode) # type: ignore


def update_sockets(self, context):
updateNode(self, context)

def draw_buttons_ext(self, context, layout):
layout.row().prop(self, 'output_as_numpy')

def draw_buttons(self, context, layout):
layout.row().prop(self, 'cluster_subdivide')
layout.row().prop(self, 'max_iter')
layout.row().prop(self, 'cluster_counts')

col = layout.column()
col.row().label(text="Triangulate mesh polygons:")
row = col.row()
split = row.split(factor=0.4)
split.column().label(text="Quads mode:")
split.column().row(align=True).prop(self, "quad_mode", text='')

row = col.row()
split = row.split(factor=0.5)
split.column().label(text="Polygons mode:")
split.column().row(align=True).prop(self, "ngon_mode", text='')

pass

def sv_init(self, context):
son = self.outputs.new
self.inputs.new('SvVerticesSocket', 'Vertices')
self.inputs.new('SvStringsSocket', 'Edges')
self.inputs.new('SvStringsSocket', 'Faces')

son('SvVerticesSocket', 'Vertices')
son('SvStringsSocket', 'Edges')
son('SvStringsSocket', 'Faces')
self.width = 210


self.update_sockets(context)

def process(self):
inputs = self.inputs
Vertices = inputs["Vertices"].sv_get(default=None)
Edges = inputs["Edges"].sv_get(default=[[]])
Faces = inputs["Faces"].sv_get(default=None)

outputs = self.outputs
if not any( [o.is_linked for o in outputs]):
return

res_verts = []
res_edges = []
res_faces = []
for verts, edges, faces in zip_long_repeat(Vertices, Edges, Faces):
if( max(map(len, faces))!=min(map(len, faces))):
bm_I = bmesh_from_pydata(verts, edges, faces, markup_face_data=True, normal_update=True)
b_faces = []
for face in bm_I.faces:
b_faces.append(face)
res = bmesh.ops.triangulate( bm_I, faces=b_faces, quad_method=self.quad_mode, ngon_method=self.ngon_mode )
new_vertices_I, new_edges_I, new_faces_I = pydata_from_bmesh(bm_I)
bm_I.free()
pdmesh = PolyData.from_regular_faces( new_vertices_I, new_faces_I)
else:
pdmesh = PolyData.from_regular_faces( verts, faces)

clust = pyacvd.Clustering(pdmesh)
if(self.cluster_subdivide>0):
clust.subdivide(self.cluster_subdivide)
clust.cluster(self.cluster_counts, maxiter=self.max_iter)
remesh = clust.create_mesh()
# remesh is triangulated here
edges0 = remesh.regular_faces[:,[0,1]] # edges AB
edges1 = remesh.regular_faces[:,[1,2]] # edges BC
edges2 = remesh.regular_faces[:,[2,0]] # edges CA
# stack
remesh_edges = np.vstack((edges0, edges1, edges2))

if self.output_as_numpy:
res_verts.append(remesh.points)
res_edges.append(remesh_edges)
res_faces.append(remesh.regular_faces)
else:
res_verts.append(remesh.points.tolist())
res_edges.append(remesh_edges.tolist())
res_faces.append(remesh.regular_faces.tolist())

outputs['Vertices'].sv_set(res_verts)
outputs['Edges'].sv_set(res_edges)
outputs['Faces'].sv_set(res_faces)

def register():
bpy.utils.register_class(SvMeshClusteringNode)


def unregister():
bpy.utils.unregister_class(SvMeshClusteringNode)
1 change: 1 addition & 0 deletions settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,7 @@ def draw_freecad_ops():
draw_message(box, "pyOpenSubdiv")
draw_message(box, "numexpr")
draw_message(box, "ezdxf")
draw_message(box, "pyacvd")

draw_freecad_ops()

Expand Down