diff --git a/dependencies.py b/dependencies.py index 40a82672a1..31cdfe1d8c 100644 --- a/dependencies.py +++ b/dependencies.py @@ -201,6 +201,14 @@ def draw_message(box, package, dependencies=None): except ImportError: pyacvd = None +pyQuadriFlow_d = sv_dependencies["pyQuadriFlow"] = SvDependency("pyQuadriFlow","https://github.com/satabol/pyQuadriFlow") +pyQuadriFlow_d.pip_installable = True +try: + import pyQuadriFlow + pyQuadriFlow_d.module = pyQuadriFlow +except ImportError: + pyQuadriFlow = None + settings.pip = pip settings.sv_dependencies = sv_dependencies settings.ensurepip = ensurepip diff --git a/docs/nodes/modifier_change/quadriflow.rst b/docs/nodes/modifier_change/quadriflow.rst new file mode 100644 index 0000000000..1208e2049f --- /dev/null +++ b/docs/nodes/modifier_change/quadriflow.rst @@ -0,0 +1,31 @@ +Quadriflow +========== + +.. image:: https://github.com/nortikin/sverchok/assets/14288520/094fb95a-e199-438e-b1e8-dcca9132ebe5 + :target: https://github.com/nortikin/sverchok/assets/14288520/094fb95a-e199-438e-b1e8-dcca9132ebe5 + + +Description +----------- + +This node is a fork of the operator in Blender: + +.. image:: https://github.com/nortikin/sverchok/assets/14288520/181ab570-c13d-496a-b5b8-c8418fe31bf9 + :target: https://github.com/nortikin/sverchok/assets/14288520/181ab570-c13d-496a-b5b8-c8418fe31bf9 + +Functionality +------------- + +.. image:: https://github.com/nortikin/sverchok/assets/14288520/642dab70-49e3-4142-8de9-32381502e5d2 + :target: https://github.com/nortikin/sverchok/assets/14288520/642dab70-49e3-4142-8de9-32381502e5d2 + +Warning +------- + +This is a time consumer node so be carefully with settings. + +Examples +-------- + +.. image:: https://github.com/nortikin/sverchok/assets/14288520/33de8b65-962f-4b05-8bcf-f537690cefd6 + :target: https://github.com/nortikin/sverchok/assets/14288520/33de8b65-962f-4b05-8bcf-f537690cefd6 \ No newline at end of file diff --git a/index.yaml b/index.yaml index 5d3089db81..a2716d64c2 100644 --- a/index.yaml +++ b/index.yaml @@ -451,6 +451,8 @@ - SvOffsetLineNode - SvContourNode - --- + - SvQuadriFlowNode + - --- - SvDualMeshNode - SvDiamondMeshNode - SvClipVertsNode diff --git a/menus/full_by_data_type.yaml b/menus/full_by_data_type.yaml index 2381024b41..6509f14d66 100644 --- a/menus/full_by_data_type.yaml +++ b/menus/full_by_data_type.yaml @@ -137,6 +137,8 @@ - SvOffsetLineNode - SvContourNode - --- + - SvQuadriFlowNode + - --- - SvDualMeshNode - SvDiamondMeshNode - SvClipVertsNode diff --git a/menus/full_nortikin.yaml b/menus/full_nortikin.yaml index abd0d7edfa..cad6d4023c 100644 --- a/menus/full_nortikin.yaml +++ b/menus/full_nortikin.yaml @@ -264,6 +264,8 @@ - SvOffsetLineNode - SvContourNode - --- + - SvQuadriFlowNode + - --- - SvDualMeshNode - SvDiamondMeshNode - SvClipVertsNode diff --git a/nodes/modifier_change/quadriflow.py b/nodes/modifier_change/quadriflow.py new file mode 100644 index 0000000000..d332cc1cfe --- /dev/null +++ b/nodes/modifier_change/quadriflow.py @@ -0,0 +1,217 @@ +# ##### 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 ##### + +# Implements the QuadriFlow Node +# sverchok/nodes/modifier_change/quadriflow.py + +import bpy +import bmesh +from bpy.props import IntProperty, BoolProperty, EnumProperty +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode, match_long_repeat, flatten_data +from sverchok.utils.sv_bmesh_utils import bmesh_from_pydata, pydata_from_bmesh + +enable_module = False +try: + import pyQuadriFlow + from pyQuadriFlow.pyQuadriFlow import pyquadriflow + enable_module = True +except ModuleNotFoundError: + enable_module = False + +from itertools import chain +import traceback +class SvQuadriFlowNode(bpy.types.Node,SverchCustomTreeNode): + bl_idname = "SvQuadriFlowNode" # Use this for index.md reference + bl_label = "Quadriflow" + bl_icon = 'OUTLINER_OB_EMPTY' + sv_icon = None + + sv_dependencies = ['pyQuadriFlow'] + + #maxSubdivision = 6 # creates a self.maxSubdivision attribute + flag_preserve_sharp : BoolProperty( + name = "Preserve Sharp", + description = "Try to preserve sharp features on the mesh", + default = False, + update = updateNode) # type: ignore + + flag_preserve_boundary : BoolProperty( + name = "Preserve Mesh Boundary", + description = "Try to preserve mesh boundary on the mesh", + default = False, + update = updateNode) # type: ignore + + flag_adaptive_scale : BoolProperty( + name = "Preserve Adaptive Scale", + description = "???", + default = False, + update = updateNode) # type: ignore + + flag_aggresive_sat : BoolProperty( + name = "Preserve Aggresive Sat", + description = "???", + default = False, + update = updateNode) # type: ignore + + flag_minimum_cost_flow : BoolProperty( + name = "Minimum Cost Flow", + description = "???", + 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 + + + # Mute Node Implementation + @property + def sv_internal_links(self): + mapping = [ + (self.inputs['Vertices'],self.outputs['Vertices']), + (self.inputs['Faces'],self.outputs['Faces']) + ] + return mapping + + def sv_init(self,context): + self.width = 200 + self.inputs.new('SvVerticesSocket', "vertices") + #self.inputs.new('SvVerticesSocket', "Edges") + self.inputs.new('SvStringsSocket', "faces") + self.inputs["vertices"].label = "vertices" + self.inputs["faces"].label = "Faces" + + socket = self.inputs.new('SvStringsSocket', "number_faces") + socket.use_prop=True + socket.default_property_type = 'int' + socket.default_int_property = 1000 + socket.label = "Number of Faces" + + socket = self.inputs.new('SvStringsSocket', "seed") + socket.use_prop=True + socket.default_property_type = 'int' + socket.default_int_property = 0 + socket.label = "Seed" + + self.outputs.new('SvVerticesSocket', "vertices") + self.outputs.new('SvStringsSocket', "faces") + self.outputs["vertices"].label = "vertices" + self.outputs["faces"].label = "Faces" + + def draw_buttons(self, context, layout): + layout.prop(self, "flag_preserve_sharp") + layout.prop(self, "flag_preserve_boundary") + layout.prop(self, "flag_adaptive_scale") + layout.prop(self, "flag_aggresive_sat") + layout.prop(self, "flag_minimum_cost_flow") + + 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='') + + + + + def process(self): + if not enable_module: + raise Exception("The dependent library is not installed (pyQuadriFlow).") + + outputs = self.outputs + if not any( [o.is_linked for o in outputs]): + return + + verts_sets = self.inputs['vertices'].sv_get(default=[],deepcopy=False) + faces_sets = self.inputs['faces'].sv_get(default=[],deepcopy=False) + _number_faces_sets = self.inputs['number_faces'].sv_get(default=[[4000]],deepcopy=False) + number_faces_sets = flatten_data(_number_faces_sets) + _seed_sets = self.inputs['seed'].sv_get(default=[[0]],deepcopy=False) + seed_sets = flatten_data(_seed_sets) + + new_meshes = { + 'vertices':[], + 'edges':[], + 'faces':[] + } + + if(verts_sets != [] and faces_sets != []): + for verts_set, faces_set, number_faces_set, seed_set in zip(*match_long_repeat([verts_sets,faces_sets, number_faces_sets, seed_sets])): + bm_I = bmesh_from_pydata(verts_set, [], faces_set, 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, ret_edges=False) + bm_I.free() + + #new_mesh = {'vertices': new_vertices_I, 'faces': new_faces_I} + + new_mesh = pyquadriflow(number_faces_set, + seed_set, + new_vertices_I, + new_faces_I, + self.flag_preserve_sharp, + self.flag_preserve_boundary, + self.flag_adaptive_scale, + self.flag_aggresive_sat, + self.flag_minimum_cost_flow + ) + + new_meshes['vertices'].append(new_mesh['vertices']) + #new_meshes['edges'].append(new_mesh['edges']) + new_meshes['faces'].append(new_mesh['faces']) + + self.outputs['vertices'].sv_set(new_meshes['vertices']) + #self.outputs['Edges'].sv_set(new_meshes['edges']) + self.outputs['faces'].sv_set(new_meshes['faces']) + +def register(): + bpy.utils.register_class(SvQuadriFlowNode) + +def unregister(): + bpy.utils.unregister_class(SvQuadriFlowNode) diff --git a/settings.py b/settings.py index deb4ef94b5..6f8e5339b6 100644 --- a/settings.py +++ b/settings.py @@ -622,6 +622,7 @@ def draw_freecad_ops(): draw_message(box, "numexpr") draw_message(box, "ezdxf") draw_message(box, "pyacvd") + draw_message(box, "pyQuadriFlow") draw_freecad_ops()