sverchok icon indicating copy to clipboard operation
sverchok copied to clipboard

Add Catmull-Clark subdivision to Sverchok

Open GeneralPancakeMSTR opened this issue 2 years ago • 5 comments

Problem statement

Catmull-Clark Subdivision (usually as OpenSubdiv) is an important tool standard to most (if not all) poly-based 3D modeling programs, and a core component of many users' modeling pipeline (including mine).

However, in Sverchok, there is no way to apply Catmull-Clark subdivision directly to mesh data, which is a significant drawback of an otherwise very powerful and complete tool.

Solution

A potential solution to this problem would be to somehow wrap the OpenSubdiv subdivision algorithm in Python, to make it compatible with Sverchok.

I am pleased to say that I have achieved this, specifically using ctypes for wrapping the C++ code from OpenSubdiv. I have also forked Sverchok, written a node that implements my subdivision wrapper, and tested its functionality directly in Blender:

vectorized_opensubdiv_final

Implementation

  • The code I wrote for wrapping OpenSubdiv's subdivision algorithm is available at my pyOpenSubdivision repository, which includes instructions on how to build and test it.

  • My fork of Sverchok that includes the OpenSubdiv node is available here. The modifications are as follows:

    • Added nodes/modifier_change/opensubdivide.py: The OpenSubdiv node.

    • Added utils/modules/ctypes_pyOpenSubdiv.py: Python code which communicates with the C++ code (using ctypes) that implements the OpenSubdiv Subdivision function and exposes it in a "friendly" way (i.e. as an easily useable function) to the OpenSubdiv node (opensubdivide.py).

    • Added utils/modules/ctypes_OpenSubdiv.dll: C++ code compiled into a Dynamic Linked Library (.dll) to expose the OpenSubdiv Subdivision function to ctypes (i.e. to ctypes_pyOpenSubdiv.py), on Windows x64 platforms.

    • Added utils/modules/ctypes_OpenSubdiv.so: C++ code compiled into a Dynamic Library to expose the OpenSubdiv Subdivision function to ctypes (i.e. to ctypes_pyOpenSubdiv.py), on Linux x64 platforms.

    • modified: .gitignore: +!/utils/modules/ctypes_OpenSubdiv.so, to include the Linux dynamic library file in version control.

    • modified: index.md: To make the OpenSubdiv node available from the Sverchok menu.

        ...
        ## Modifier Make
            LineConnectNodeMK2
            ---
            SvOpenSubdivideNode <- Add the OpenSubdiv node to Sverchok menu 
            SvSubdivideNodeMK2
            SvSubdivideToQuadsNode
            SvOffsetLineNode
            SvContourNode
            ---
        ...
        ```
    
    

Advantages

  • Speed: The OpenSubdiv library is extremely fast, capable of performing simultaneous subdivision on many meshes in roughly real time, the below example shows 3 levels of subdivision applied to 100 randomly variable cubes:

multi_opensubdiv

  • Low User Overhead: No extra or complicated steps must be taken to activate the OpenSubdiv node, and because ctypes is built into python, the linked libraries (.dll and .so files) should not be python version-sensitive.

Disadvantages

  • Platform Dependency: A dynamic library file containing the C++ code in binary format must be compiled separately for every platform Blender is available on, i.e. Windows, Linux, and OS X Intel and ARM. I have compiled libraries for Linux and Windows, but have yet to compile for OS X (this should not be too complicated, though).

GeneralPancakeMSTR avatar Jul 17 '22 17:07 GeneralPancakeMSTR

Quite impressive.

image

I think if we want to gain performance using compiled code is the only way. So I have no objections to add the changes into master. We already has nodes which use dependencies so it won't make any difference.

There are several additions which should be added to make it work smoothly:

  • Sverchok should be able to work without any dependencies. Current approach looks next:
import open_subdiv
class Node:
    def process(self):
        if open_subdiv is None:
            raise(Error("The dependent library is not installed"))

All next is optional:

  • Some installation script which would simplify the installation process (probably using setuptools)
  • Using github actions to automate creating builds. Similar to how it's done in Animation Nodes.

Durman avatar Jul 18 '22 06:07 Durman

Some installation script which would simplify the installation process (probably using setuptools)

I think this is a good idea, and I was planning on getting that working anyway.

Using github actions to automate creating builds. Similar to how it's done in Animation Nodes.

I have also thought about this and think it's a good idea, but it might be a little more complicated and may take me a little while to get working.

I'll work on getting the opensubdiv dependencies working by importing them from the Sverchok preferences menu, and let you know once I've got that going?

GeneralPancakeMSTR avatar Jul 18 '22 14:07 GeneralPancakeMSTR

You can create Pull request from your fork even if you are not finished yet, just mark that the work is in progress. With pull request it's easier to inspect code and ask for changes. After it will be finished any of developers will be able to merge PR into master.

Also you can split this into several PRs. In the first I would code the node and the library in the second code for Github action or another approach of simplifying the installation.

Durman avatar Jul 19 '22 04:07 Durman

Is it good idea to store dll / so in a git repo? Most probably you will have to recompile them for different linux / windows versions, for different blender versions, idk... IMHO, git repo should contain only sources. We could try to set up GH action pipeline to build dll / so there.

portnov avatar Jul 19 '22 10:07 portnov

You can create Pull request from your fork even if you are not finished yet, just mark that the work is in progress.

Great, will do.

Is it good idea to store dll / so in a git repo? ... We could try to set up GH action pipeline to build dll / so there.

I don't think it's too big a deal, but in the long run I agree that setting up a CI/CD pipeline is a good idea.

GeneralPancakeMSTR avatar Jul 19 '22 14:07 GeneralPancakeMSTR

It appears I have a working implementation of Catmull-Clark subdivision in python+bmesh+numpy, see utils.catmull_clark. It's obviously much slower than OpenSubdiv, but OTOH it does not require external dependencies. Do we want a separate node for it?

for now anyone can try it with scripted node:

"""
in verts_in v
in edges_in s
in faces_in s
in subdivisions_in s
out verts_out v
out edges_out s
out faces_out s
"""

from sverchok.data_structure import zip_long_repeat
from sverchok.utils.sv_bmesh_utils import bmesh_from_pydata, pydata_from_bmesh
from sverchok.utils.catmull_clark import subdivide

verts_out = []
edges_out = []
faces_out = []
for verts, edges, faces, subdivisions in zip_long_repeat(verts_in, edges_in, faces_in, subdivisions_in):
    if isinstance(subdivisions, (list, tuple)):
        subdivisions = subdivisions[0]

    bm = bmesh_from_pydata(verts, edges, faces, normal_update=True)
    new_bm = subdivide(bm, iterations=subdivisions)
    bm.free()

    new_verts, new_edges, new_faces = pydata_from_bmesh(new_bm)
    new_bm.free()

    verts_out.append(new_verts)
    edges_out.append(new_edges)
    faces_out.append(new_faces)

portnov avatar Sep 17 '22 18:09 portnov

Naturally I don't object to adding this as a subdivision node. As for my version, I know I have been a little absent lately, but I've improved my implementation a lot, and intend to push the update within the next week or two.

GeneralPancakeMSTR avatar Sep 18 '22 02:09 GeneralPancakeMSTR

Do we want a separate node for it?

Usually we add a mode to a node for adding different implementation of the same functionality.

Durman avatar Sep 19 '22 04:09 Durman

Yeah, I agree, but currently the node is called "OpenSubdivision", which is the name of specific implementation. What about:

  • rename node to "Catmull-Clark Subdivision"
  • add "Implementation" parameter with options: "Sverchok", "OpenSubdiv" (available only when dependency is installed) ?

portnov avatar Sep 19 '22 05:09 portnov

I, personally, don't see the point of hiding dependent nodes and, as in this case, dependent modes. The node can just raise an error if something is not installed. We already has cases when such hiding is impossible - when a node generates a curve and another node calls its method which is dependent, if I recall correctly.

I see the point of combining nodes in toolsets and hiding them, for example Pulga Physics or SVG categories which probably are not used everyday. And it has nothing with dependencies.

Durman avatar Sep 19 '22 07:09 Durman

It looks cool. I like it.

mifth avatar Sep 19 '22 15:09 mifth

But yeah probably we could move it into something global. Like, mesh things. Subdivide smooth, subdivide linear, bevel, remesh, triangulate etc. Something like mesh tools.

mifth avatar Sep 19 '22 16:09 mifth

@Durman about whether to display nodes which are working only when a dependency is installed — it's a separate question, I believe we have an issue open for that discussion. I'm not in general against your idea, just if we decide so it should be applied for all such nodes.

About having some kind of toolboxes, which could be hidden when not used — I thought about that as well. But at the moment it's not clear to me what exactly should it look like.

portnov avatar Sep 19 '22 17:09 portnov