**Note:** I switched to highlight.js in combination with showdown.js to create new articles on this blog so expect some minor weirdness here and there! When manipulating **large numbers of vertices** in Blender via Python, a **naive approach** — relying on standard Python iteration (loops) and individual property assignment — is computationally slow. Blender's Python layer introduces significant overhead when repeatedly crossing the boundary between the Python interpreter and the core C/C++ data structures for each vertex. The performance bottleneck can be overcome by using **NumPy** to process the data in **bulk** using **vectorized operations**. Because [NumPy](https://numpy.org/) is bundled with Blender, there is no need to install anything extra; your add-on can simple import it. The numpy based approach involves three main steps: data export, array processing, and data import. ----- ### 1\. Bulk Data Transfer: `foreach_get()` and `foreach_set()` Instead of looping through `mesh.vertices[i].co` one at a time, Blender's Python API provides high-speed methods for transferring entire blocks of data: **`foreach_get`** and **`foreach_set`**. These methods directly copy data between Blender's internal data structures (like vertex coordinates, normals, or custom properties) and a flat, contiguous **NumPy `ndarray`**. This minimizes the number of expensive function calls across the Python/C boundary, turning thousands of slow operations into one or two very fast memory copies. The `foreach_get()` and `foreach_set()` methods are available for any [property collection](https://docs.blender.org/api/latest/bpy.types.bpy_prop_collection.html#bpy.types.bpy_prop_collection.foreach_get), and allow access to any property that has a 'basic'type (i.e. a bool, int or float). ```python # Assuming 'mesh' is a Blender mesh data block import numpy as np # bundled with Blender, so no need to install it # 1. Export: Get all vertex coordinates into a flat NumPy array # 'co' are the vertex coordinates (x, y, z), so the array size is N_verts * 3 verts_co = np.empty(len(mesh.vertices) * 3, dtype=np.float32) mesh.vertices.foreach_get("co", verts_co) # The array is now a flat 1D array; reshape for 3D processing verts_co = verts_co.reshape((-1, 3)) ``` ----- Note that the NumPy ndarray needs to be allocated before we can use a call to `foreach_get()`. There is no need to zero out the contents of this new array, so we can us `np.empty()` to allocate space saving some time by omitting unnecessary initialization. ### 2\. Vectorized Processing with NumPy Once the vertex coordinates are in a NumPy **`ndarray`**, all processing should be performed using NumPy's optimized C-backed functions. #### NumPy Operations NumPy executes operations on the entire array at once, often using highly optimized C implementations, which is vastly faster than interpreted Python loops. **Example: Translating all vertices** ```python # Define a translation vector translation_vector = np.array([1.0, 0.5, 0.0]) # Perform the addition on all vertices simultaneously # This is where the speed comes from verts_co += translation_vector ``` #### The Power of Broadcasting [Broadcasting](https://numpy.org/doc/stable/user/absolute_beginners.html#broadcasting) is a fundamental NumPy mechanism that automatically handles operations between arrays of different but compatible shapes. It allows the smaller array (the translation vector `[1.0, 0.5, 0.0]`) to be conceptually "stretched" or "broadcast" across the larger array (`verts_co`), eliminating the need to explicitly write a loop to apply the translation to every single vertex row. It is no exageration to state that when using NumPy, any explicit loop left in your Python code means your are probably not doing things right. NumPy performs the operation by aligning the shapes from the trailing dimensions, effectively applying the 1x3 translation vector shown in the example to every row in the Nx3 array of vertex coordinates. ----- ### 3\. Importing Data Back After all modifications are complete, the resulting NumPy array is copied back into the Blender mesh data using **`foreach_set()`**. ```python # 3. Import: Flatten the processed array back to 1D verts_co = verts_co.reshape(-1) # Set the data back into the mesh mesh.vertices.foreach_set("co", verts_co) # Update the mesh data for Blender to display the changes mesh.update() ``` By utilizing **`foreach_get()`/`foreach_set()`**, and NumPy's **vectorized operations**, operations involving hundreds of thousands or even millions of vertices become practical. In a future article I will explore this some more with a real world example and some performance data.
Efficient Vertex Manipulation in Blender with NumPy
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment