A Python wrapper (implemented using ctypes) for the OpenSubdiv C++ Library far topology refiner.
-
Added keyword arguments (verbose (
-v) and level (-l <N>)) toctypes_subdivider.cpp(these are handled bysubdivider::settings). -
ctypes_subdivider.cpphandles level 0 subdivision better now.- Within the
refine_topologyfunction, an assertion is made that the number of vertices per face of the refined mesh is 4, i.e. it consists entirely of quads.
assert(fverts.size() == 4);
This assertion is only strictly true if the mesh is actually subdivided (
maxlevel > 0). Otherwise, it is possible to feed a mesh containing ngons, which if not subdivided (evaluated formaxlevel = 0), will fail the all quads assertion, crashing the code.It would seem reasonable to simply ignore the all-quads assertion, but the block that constructs the edges (
for (int i = 0; i < nn_faces; i++) { ... }) for the refined topology relies on the assumption. Furthermore, ignoring the all-quads assertion would involve returning (to python) a set of faces where each could contain an arbitrary number of vertices, which is very difficult (to the extent that I could not figure out a solution). Conceivably, it would be possible to use thevertsPerFacedata to reconstruct a flattened (1D)faceVertslist; but ultimately, any solution tends to involve reconstructing information that was passed into the refiner in the first place (as opposed to simply passing it through, which is easier said than done), which is not optimal.My solution to this problem was to create a method (
subdivider::edges_only) that only constructs edges from the incoming mesh data, and can handle faces containing an arbitrary number of vertices. This method only runs ifmaxlevel <= 0.In terms of Sverchok/Blender, the intention here is to have the node generate valid topology when it is not muted and the subdivision level is 0. When the node is muted, the incoming mesh data should be passed directly through (with no edges created), but this can be implemented on the python side.
Note that I would use the
edges_onlyfunction formaxlevel > 0, but the refined face vertices (fverts) are extracted one-by-one (fromreflastlevel), so this would be slightly tricky to implement, or at least require a for-loop each for extracting the refined faces and constructing the edges from them, where the current solution does both operations in the same loop. For these reasons, I have stuck with the current approach, even though it lacks elegance. - Within the
-
Testing is handled a little better now, with the test function integrated directly into the module now. The proper way is still unit tests and CI/CD, but I think this is a step in the right direction. Testing example in this readme have been updated accordingly.
- pyOpenSubdiv @ PyPi
pip install pyOpenSubdiv
- Discuss
I followed this guide. Pretty simple, at any rate:
python3 setup.py sdist bdist_wheel
twine upload dist/*
Note that when updating, it looks like it is necessary to either increment the version number in setup.py, or delete the existing version on PyPi and reupload.
-
Install General Requirements.
-
Install GLFW (Optional, but makes building
OpenSubdivsmoother)sudo apt-get install libglfw3 libglwf3-devThis should create/install the files
libglfw3.aat/usr/local/lib/andgflw3.hat/usr/local/include/GLFW/. -
Install
doxygenandxorg-dev(possibly optional)sudo apt-get install doxygen xorg-dev -
Clone OpenSubdiv
git clone https://github.com/PixarAnimationStudios/OpenSubdiv.git -
$ cd OpenSubdiv $ mkdir build $ cd build $ cmake -D NO_PTEX=1 -D NO_DOC=1 -D NO_OMP=1 -D NO_TBB=1 -D NO_CUDA=1 -D NO_OPENCL=1 -D NO_CLEW=1 -D GLFW_LOCATION="/usr/" .. $ cmake --build . --config Release --target install
This should create an
opensubdiv/directory at/usr/local/include/. └── opensubdiv/ ├── far/ ├── hbr/ ├── osd/ ├── sdc/ ├── version.h └── vtr/And
libosdCPUandlibosdGPUlibrary files at/usr/local/lib/. ├── libosdCPU.a ├── libosdCPU.so -> libosdCPU.so.3.4.4 ├── libosdCPU.so.3.4.4 ├── libosdGPU.a ├── libosdGPU.so -> libosdGPU.so.3.4.4 └── libosdGPU.so.3.4.4 -
Compile
This does not work (does not compile the
osdlibraries statically? Throws the errorOSError: libosdCPU.so.3.4.4: cannot open shared object file: No such file or directorywhen trying to importctypes_OpenSubdiv.soin e.g.load_library.py)g++ ctypes_subdivider.cpp -losdGPU -losdCPU -o ctypes_OpenSubdiv.so -fPIC -sharedThis seems to work (compiles the static
osd.alibraries intoctypes_OpenSubdiv.so?) (compile syntax courtesy this SE answer)g++ ctypes_subdivider.cpp -L/usr/local/lib/ -l:libosdGPU.a -l:libosdCPU.a -o ctypes_OpenSubdiv.so -fPIC -sharedNote that
-L/usr/local/lib/is unnecessary, but included for reference purposes.Some additional convenience commands:
Compile directly into pyOpenSubdiv/clib:
g++ ctypes_subdivider.cpp -L/usr/local/lib/ -l:libosdGPU.a -l:libosdCPU.a -o package/pyOpenSubdiv/clib/ctypes_OpenSubdiv.so -fPIC -sharedCompile to executable:
g++ ctypes_subdivider.cpp -L/usr/local/lib/ -l:libosdGPU.a -l:libosdCPU.a -o ctypes_OpenSubdiv_executable -
Test:
Testing
pyOpenSubdivon Linux now usesdocker, which I installed following this guide.With
dockerinstalled, build the test image by runningdocker build -t ubuntu_docker .from within thedockersdirectory. Then, start the container by runningstart_docker.shfrom the root of this repo.$ ./start_docker.sh
The
start_docker.shshell script automatically installs thepyOpenSubdivpackage to the container, and on exit deletes miscellaneous directories created from installing and running the module, and the container (note that deleting the module directories requiressudo).Start
IPython(run$ ipythonat the container terminal) (note that usingIPythonis technically optional, and it is only my preference to use it), import thepyOpenSubdivmodule, loadpyOpenSubdiv.pysubdivision, and runpysubdivision.test_pysubdivide()In [2]: import pyOpenSubdiv In [3]: from pyOpenSubdiv import pysubdivision In [4]: pysubdivision.test_pysubdivide()pysubdivision.test_pysubdivide()should produce an output like the following (theRuntimetests will more than likely be different between instances and hardware)In [4]: pysubdivision.test_pysubdivide() Subdivision Tests cube @ 0 suzanne @ 0 triangles @ 0 ngons @ 0 ngons2 @ 0 cube @ 1 suzanne @ 1 triangles @ 1 ngons @ 1 ngons2 @ 1 cube @ 2 suzanne @ 2 triangles @ 2 ngons @ 2 ngons2 @ 2 Verbose Test maxlevel 1 New Vertices 26 v -0.277778 -0.277778 0.277778 v 0.277778 -0.277778 0.277778 v -0.277778 0.277778 0.277778 v 0.277778 0.277778 0.277778 v -0.277778 0.277778 -0.277778 v 0.277778 0.277778 -0.277778 v -0.277778 -0.277778 -0.277778 v 0.277778 -0.277778 -0.277778 v 0.000000 0.000000 0.500000 v 0.000000 0.500000 0.000000 v 0.000000 0.000000 -0.500000 v 0.000000 -0.500000 0.000000 v 0.500000 0.000000 0.000000 v -0.500000 0.000000 0.000000 v 0.000000 -0.375000 0.375000 v 0.375000 0.000000 0.375000 v 0.000000 0.375000 0.375000 v -0.375000 0.000000 0.375000 v 0.375000 0.375000 0.000000 v 0.000000 0.375000 -0.375000 v -0.375000 0.375000 0.000000 v 0.375000 0.000000 -0.375000 v 0.000000 -0.375000 -0.375000 v -0.375000 0.000000 -0.375000 v 0.375000 -0.375000 0.000000 v -0.375000 -0.375000 0.000000 e 0 14 e 0 25 e 1 15 e 2 17 e 3 16 e 3 18 e 4 20 e 5 19 e 5 21 e 6 23 e 7 22 e 7 24 e 8 17 e 9 20 e 10 23 e 11 25 e 12 15 e 13 23 e 14 8 e 14 1 e 14 11 e 15 8 e 15 3 e 16 8 e 16 2 e 16 9 e 17 0 e 17 13 e 18 9 e 18 5 e 18 12 e 19 9 e 19 4 e 19 10 e 20 2 e 20 13 e 21 10 e 21 7 e 21 12 e 22 10 e 22 6 e 22 11 e 23 4 e 24 11 e 24 1 e 24 12 e 25 6 e 25 13 f 1 15 9 18 f 15 2 16 9 f 9 16 4 17 f 18 9 17 3 f 3 17 10 21 f 17 4 19 10 f 10 19 6 20 f 21 10 20 5 f 5 20 11 24 f 20 6 22 11 f 11 22 8 23 f 24 11 23 7 f 7 23 12 26 f 23 8 25 12 f 12 25 2 15 f 26 12 15 1 f 2 25 13 16 f 25 8 22 13 f 13 22 6 19 f 16 13 19 4 f 7 26 14 24 f 26 1 18 14 f 14 18 3 21 f 24 14 21 5 Runtime Cube: 0.478s @ 0 x 10000 Cube: 1.941s @ 1 x 10000 Cube: 5.102s @ 2 x 10000 Output v [-0.2777777910232544, -0.2777777910232544, 0.2777777910232544] v [0.2777777910232544, -0.2777777910232544, 0.2777777910232544] v [-0.2777777910232544, 0.2777777910232544, 0.2777777910232544] v [0.2777777910232544, 0.2777777910232544, 0.2777777910232544] v [-0.2777777910232544, 0.2777777910232544, -0.2777777910232544] v [0.2777777910232544, 0.2777777910232544, -0.2777777910232544] v [-0.2777777910232544, -0.2777777910232544, -0.2777777910232544] v [0.2777777910232544, -0.2777777910232544, -0.2777777910232544] v [0.0, 0.0, 0.5] v [0.0, 0.5, 0.0] ... e [0, 14] e [0, 25] e [1, 15] e [2, 17] e [3, 16] e [3, 18] e [4, 20] e [5, 19] e [5, 21] e [6, 23] ... f [0, 14, 8, 17] f [14, 1, 15, 8] f [8, 15, 3, 16] f [17, 8, 16, 2] f [2, 16, 9, 20] f [16, 3, 18, 9] f [9, 18, 5, 19] f [20, 9, 19, 4] f [4, 19, 10, 23] f [19, 5, 21, 10] ... static float g_verts[8][3] = { {-0.50f, -0.50f, 0.50f}, {0.50f, -0.50f, 0.50f}, {-0.50f, 0.50f, 0.50f}, {0.50f, 0.50f, 0.50f}, {-0.50f, 0.50f, -0.50f}, {0.50f, 0.50f, -0.50f}, {-0.50f, -0.50f, -0.50f}, {0.50f, -0.50f, -0.50f} }; static int g_nverts = 8, g_nfaces = 6; static int g_vertsperface[6] = {4, 4, 4, 4, 4, 4}; static int g_vertIndices[24] = { 0, 1, 3, 2, 2, 3, 5, 4, 4, 5, 7, 6, 6, 7, 1, 0, 1, 7, 5, 3, 6, 0, 2, 4 };The
pyOpenSubdivmodule may be uninstalled by running the commandpython -m pip uninstall pyOpenSubdiv.
Building on Windows (Visual Studio)
-
Install General Requirements.
-
Install GLFW (Optional, but makes building
OpenSubdivsmoother)- Download the GLFW Windows pre-compiled binaries (64-bit), and unzip file.
-
Clone OpenSubdiv
git clone https://github.com/PixarAnimationStudios/OpenSubdiv.git -
Build and Install OpenSubdiv
From an administrator powershell (vary appropriately for Visual Studio version andGLFWpath):$ cd OpenSubdiv $ mkdir build $ cd build $ cmake ^ -DCMAKE_GENERATOR_PLATFORM=x64 -G "<Visual Studio Version>" ^ -D NO_PTEX=1 -D NO_DOC=1 ^ -D NO_OMP=1 -D NO_TBB=1 -D NO_CUDA=1 -D NO_OPENCL=1 -D NO_CLEW=1 ^ -D "GLFW_LOCATION=<path to GLFW>" ^ .. $ cmake --build . --config Release --target install
For example
# Example build command $ cmake ^ -DCMAKE_GENERATOR_PLATFORM=x64 -G "Visual Studio 17 2022" ^ -D NO_PTEX=1 -D NO_DOC=1 ^ -D NO_OMP=1 -D NO_TBB=1 -D NO_CUDA=1 -D NO_OPENCL=1 -D NO_CLEW=1 ^ -D "GLFW_LOCATION=C:/Users/<username>/Desktop/cpp/glfw-3.3.7.bin.WIN64/glfw-3.3.7.bin.WIN64" ^ ..
A successful build and install should create an
opensubdiv\directory atC:\Program Files\OpenSubdiv\include\with the following structureC:. ├───far ├───hbr ├───osd ├───sdc └───vtrAnd
libosdCPU.libandlibosdGPU.liblibrary files atC:\Program Files\OpenSubdiv\lib\ -
Install Visual Studio Community, include
Desktop Development with C++. -
Create Visual Studio
C++Project- Create a new, blank,
C++project in Visual Studio. - Configure to target
Releaseandx64. - Add
ctypes_subdivider.cppto theSource Filesby right-clickingSource Filesin theSolution Explorer, selectingAdd->Existing Item..., and navigating toctypes_subdivider.cppin the browser window. - Note: It is also possible to make a C++ project in this directory (the repository) with a
Source.cppfile containing just the lineThe following setup steps remain the same.#include "../ctypes_subdivider.cpp"
- Create a new, blank,
-
Configure solution properties (for All builds, All platforms)
-
Set the solution Configuration Type to
Dynamic Library (.dll)Right-click Solution -> Properties -> Configuration Properties -> Configuration Type -> Dynamic Library (.dll) -
Add
OpenSubdiv\includeto Additional Include DirectoriesRight-click Solution -> C/C++ -> General -> Additional Include Directories -> C:\Program Files\OpenSubdiv\include -
Add
Opensubdiv\libsto Additional Library DirectoriesRight-click Solution -> Properties -> Linker -> General -> Additional Library Directories -> C:\Program Files\OpenSubdiv\lib -
Add
OpenSubdivlibrary binaries to linker's Additional DependenciesRight-click Solution -> Properties -> Linker -> Input -> Additional Dependencies -> osdCPU.lib;oscGPU.lib
-
-
Build the solution (
Build -> Build Solution), which should create actypes_OpenSubdiv.dllfile atx64\Release\. -
Test:
Testing on Windows does not use a docker container, even though maybe it should, but instead involves directly installing the module from the
setup.pyfile in thepackagedirectory. A validpythoninstallation needs to be present on your machine for this test to work.Navigate to
<this repo>/package, and installpyOpenSubdivby running the commandpython -m pip install .
Then import
pyOpenSubdiv, loadpyOpenSubdiv.pysubdivision, and runpysubdivision.test_pysubdivide()(again, I am usingIPythonhere, which is optional)In [1]: import pyOpenSubdiv In [2]: from pyOpenSubdiv import pysubdivision In [3]: pysubdivision.test_pysubdivide()pysubdivision.test_pysubdivide()should produce an output likeIn [3]: pysubdivision.test_pysubdivide() Subdivision Tests cube @ 0 suzanne @ 0 triangles @ 0 ngons @ 0 ngons2 @ 0 cube @ 1 suzanne @ 1 triangles @ 1 ngons @ 1 ngons2 @ 1 cube @ 2 suzanne @ 2 triangles @ 2 ngons @ 2 ngons2 @ 2 Verbose Test maxlevel 1 New Vertices 26 v -0.277778 -0.277778 0.277778 v 0.277778 -0.277778 0.277778 v -0.277778 0.277778 0.277778 v 0.277778 0.277778 0.277778 v -0.277778 0.277778 -0.277778 v 0.277778 0.277778 -0.277778 v -0.277778 -0.277778 -0.277778 v 0.277778 -0.277778 -0.277778 v 0.000000 0.000000 0.500000 v 0.000000 0.500000 0.000000 v 0.000000 0.000000 -0.500000 v 0.000000 -0.500000 0.000000 v 0.500000 0.000000 0.000000 v -0.500000 0.000000 0.000000 v 0.000000 -0.375000 0.375000 v 0.375000 0.000000 0.375000 v 0.000000 0.375000 0.375000 v -0.375000 0.000000 0.375000 v 0.375000 0.375000 0.000000 v 0.000000 0.375000 -0.375000 v -0.375000 0.375000 0.000000 v 0.375000 0.000000 -0.375000 v 0.000000 -0.375000 -0.375000 v -0.375000 0.000000 -0.375000 v 0.375000 -0.375000 0.000000 v -0.375000 -0.375000 0.000000 e 0 14 e 0 25 e 1 15 e 2 17 e 3 16 e 3 18 e 4 20 e 5 19 e 5 21 e 6 23 e 7 22 e 7 24 e 8 17 e 9 20 e 10 23 e 11 25 e 12 15 e 13 23 e 14 8 e 14 1 e 14 11 e 15 8 e 15 3 e 16 8 e 16 2 e 16 9 e 17 0 e 17 13 e 18 9 e 18 5 e 18 12 e 19 9 e 19 4 e 19 10 e 20 2 e 20 13 e 21 10 e 21 7 e 21 12 e 22 10 e 22 6 e 22 11 e 23 4 e 24 11 e 24 1 e 24 12 e 25 6 e 25 13 f 1 15 9 18 f 15 2 16 9 f 9 16 4 17 f 18 9 17 3 f 3 17 10 21 f 17 4 19 10 f 10 19 6 20 f 21 10 20 5 f 5 20 11 24 f 20 6 22 11 f 11 22 8 23 f 24 11 23 7 f 7 23 12 26 f 23 8 25 12 f 12 25 2 15 f 26 12 15 1 f 2 25 13 16 f 25 8 22 13 f 13 22 6 19 f 16 13 19 4 f 7 26 14 24 f 26 1 18 14 f 14 18 3 21 f 24 14 21 5 Runtime Cube: 0.104s @ 0 x 10000 Cube: 0.272s @ 1 x 10000 Cube: 0.620s @ 2 x 10000 Output v [-0.2777777910232544, -0.2777777910232544, 0.2777777910232544] v [0.2777777910232544, -0.2777777910232544, 0.2777777910232544] v [-0.2777777910232544, 0.2777777910232544, 0.2777777910232544] v [0.2777777910232544, 0.2777777910232544, 0.2777777910232544] v [-0.2777777910232544, 0.2777777910232544, -0.2777777910232544] v [0.2777777910232544, 0.2777777910232544, -0.2777777910232544] v [-0.2777777910232544, -0.2777777910232544, -0.2777777910232544] v [0.2777777910232544, -0.2777777910232544, -0.2777777910232544] v [0.0, 0.0, 0.5] v [0.0, 0.5, 0.0] ... e [0, 14] e [0, 25] e [1, 15] e [2, 17] e [3, 16] e [3, 18] e [4, 20] e [5, 19] e [5, 21] e [6, 23] ... f [0, 14, 8, 17] f [14, 1, 15, 8] f [8, 15, 3, 16] f [17, 8, 16, 2] f [2, 16, 9, 20] f [16, 3, 18, 9] f [9, 18, 5, 19] f [20, 9, 19, 4] f [4, 19, 10, 23] f [19, 5, 21, 10] ... static float g_verts[8][3] = { {-0.50f, -0.50f, 0.50f}, {0.50f, -0.50f, 0.50f}, {-0.50f, 0.50f, 0.50f}, {0.50f, 0.50f, 0.50f}, {-0.50f, 0.50f, -0.50f}, {0.50f, 0.50f, -0.50f}, {-0.50f, -0.50f, -0.50f}, {0.50f, -0.50f, -0.50f} }; static int g_nverts = 8, g_nfaces = 6; static int g_vertsperface[6] = {4, 4, 4, 4, 4, 4}; static int g_vertIndices[24] = { 0, 1, 3, 2, 2, 3, 5, 4, 4, 5, 7, 6, 6, 7, 1, 0, 1, 7, 5, 3, 6, 0, 2, 4 };
Sverchok Integration
- These links are useful for dealing with syncing forks up with master repos.
- How do I update or sync a forked repository on GitHub?
- Git Merge Master into Branch
- Also the
.git/configfile needs to be configured properly. It's all very confusing and annoying and I wish it made more sense. - I think the sync process is like this
git fetch upstream
git checkout master
git rebase upstream/master
git checkout <branch> (e.g. sverchok_OpenSubdiv_2)
git merge <branch to merge> (e.g. master)
git push
-
Make sure that
pyOpenSubdivis not installed for Blender's python- Blender's python is at
C:\Program Files (x86)\Steam\steamapps\common\Blender\3.4\python\bin>(or similar) - Installed packages are at
C:\Program Files (x86)\Steam\steamapps\common\Blender\3.4\python\lib\site-packages pyOpenSubdivmay be uninstalled by running e.g../python.exe -m pip uninstall pyOpenSubdivfrom within theC:\Program Files (x86)\Steam\steamapps\common\Blender\3.4\python\bin>(or similar) folder.
- Blender's python is at
-
Rename:
Catmull-Clark Subdivision -
Change
bl_idnameback to "SvOpenSubdivisionNode" -
nodes/modifier_change/opensubdivision.py- Changing the name to
Catmull-Clark Subdivision, but I'm leaving the python script asopensubdivision.py. - The
Catmull-Clark Subdivisionnode implementation. - Imports
pyOpenSubdiv. SvCatmullClarkSubdivisionNode->SvOpenSubdivisionNodeopensubdivision.py
- Changing the name to
-
(No Change)
dependencies.py- Add
pyOpenSubdivas Sverchok optional dependency.# dependencies.py ... pyOpenSubdiv_d = sv_dependencies["pyOpenSubdiv"] = SvDependency("pyOpenSubdiv","https://github.com/GeneralPancakeMSTR/pyOpenSubdivision") pyOpenSubdiv_d.pip_installable = True try: import pyOpenSubdiv pyOpenSubdiv_d.message = "pyOpenSubdiv package is available" pyOpenSubdiv_d.module = pyOpenSubdiv except ImportError: pyOpenSubdiv_d.message = "sv: pyOpenSubdiv package is not available, the Catmull-Clark Subdivision node will not be available" info(pyOpenSubdiv_d.message) pyOpenSubdiv = None ...
- Add
-
(No Change)
settings.py- Draw the
pyOpenSubdivdependency installation box in the Sverchok Preferences "Extra Nodes" tab.# settings.py ... draw_message(box, "scipy") draw_message(box, "geomdl") draw_message(box, "skimage") draw_message(box, "mcubes") draw_message(box, "circlify") draw_message(box, "cython") draw_message(box, "numba") draw_message(box, "pyOpenSubdiv") # Add option to install pyOpenSubdiv ...
- Draw the
-
index.md- Add the
Catmull-Clark Subdivisionnode to the Sverchok node menu.## Modifier Make LineConnectNodeMK2 --- SvCatmullClarkSubdivisionNode -> SvOpenSubdivisionNode SvSubdivideNodeMK2 SvSubdivideToQuadsNode SvOffsetLineNode SvContourNode --- SvDualMeshNode SvDiamondMeshNode SvClipVertsNode --- SvBevelCurveNode SvAdaptiveEdgeNode SvAdaptivePolygonsNodeMk3 SvDuplicateAlongEdgeNode SvFractalCurveNode SvFrameworkNode SvSolidifyNodeMk2 SvWireframeNode SvPipeNode SvMatrixTubeNode
- Add the
-
docs/nodes/modifier_change/modifier_change_index.rst- Added
opensubdivisionnode.
- Added
-
docs/nodes/modifier_change/opensubdivision.rstCatmull-Clark Subdivision(opensubdivision.py) node documentation.- Install successfully (old node)
- Test Node (old node)
- New tests, with renamed ("Catmull-Clark Subdivision") node, demonstrating better case handling.
- Implement
- Subdivision <= 0 should be handled better now.
- Identify and resolve new bugs.
- Handle case where user accidentally inputs a Face list that refers to vertices that do not exist in the vertices list (e.g. Faces refer to vert 7, where there are only 5 vertices).
- Implement
- Implement










