Module sverchok.utils.catmull_clark
Implementation of Catmull-Clark subdivision algorithm in pure Python, with use of NumPy and Blender's bmesh library.
Expand source code
# This file is part of project Sverchok. It's copyrighted by the contributors
# recorded in the version control history of the file, available from
# its original location https://github.com/nortikin/sverchok/commit/master
#
# SPDX-License-Identifier: GPL3
# License-Filename: LICENSE
"""
Implementation of Catmull-Clark subdivision algorithm in pure Python, with use
of NumPy and Blender's bmesh library.
"""
import numpy as np
import bmesh
from sverchok.utils.sv_bmesh_utils import pydata_from_bmesh
def calc_new_verts(bm):
"""
Calculate coordinates of new vertices by Catmull-Clark algorithm.
Args:
bm: input mesh, as Blender's bmesh object.
Returns:
Lists of new vertices, all as np.ndarray of shape (x, 3):
* new vertices to replace vertices of original mesh
* new vertices to be placed in the middles of edges
* new vertices to be placed in the middles of faces
"""
verts, edges, faces = pydata_from_bmesh(bm)
verts = np.asarray(verts)
face_centers = np.array([tuple(f.calc_center_median()) for f in bm.faces])
edge_verts = np.array([verts[e] for e in edges])
points_by_edge = np.array([np.vstack(([face_centers[f.index] for f in e.link_faces], edge_verts[e.index])) for e in bm.edges])
edge_points = np.array([np.mean(pts, axis=0) if len(pts) == 4 else np.mean(pts[1:], axis=0) for pts in points_by_edge])
edge_centers = np.mean(edge_verts, axis=1)
new_verts_from_faces = face_centers
new_verts_from_edges = edge_points
new_verts_from_verts = np.zeros((len(bm.verts), 3))
for vert in bm.verts:
n = len(vert.link_faces)
linked_face_idxs = [f.index for f in vert.link_faces]
linked_edge_idxs = [e.index for e in vert.link_edges]
P = np.array(vert.co)
F = np.mean(face_centers[linked_face_idxs], axis=0)
R = np.mean(edge_centers[linked_edge_idxs], axis=0)
if n >= 3:
new_verts_from_verts[vert.index] = (F + 2*R + (n-3)*P) / n
elif n == 2:
new_verts_from_verts[vert.index] = P
else:
new_verts_from_verts[vert.index] = (R + P) / 2
return new_verts_from_verts, new_verts_from_edges, new_verts_from_faces
def subdivide_once(bm, normal_update = False):
"""
Subdivide mesh by use of Catmull-Clark algorithm, one time.
Args:
bm: input mesh - as bmesh object.
normal_update: if True, recalculate mesh normals in the end.
Returns:
new bmesh object.
"""
points_from_verts, points_from_edges, points_from_faces = calc_new_verts(bm)
new_bm = bmesh.new()
new_vert_verts = [new_bm.verts.new(v) for v in points_from_verts]
new_face_verts = [new_bm.verts.new(v) for v in points_from_faces]
new_edge_verts = [new_bm.verts.new(v) for v in points_from_edges]
new_bm.verts.index_update()
new_bm.verts.ensure_lookup_table()
for orig_face in bm.faces:
n_edges = len(orig_face.edges)
face_vert = new_face_verts[orig_face.index]
loop = orig_face.loops[0]
for i in range(n_edges):
edge = loop.edge
prev_edge = loop.link_loop_prev.edge
edge_vert = new_edge_verts[edge.index]
prev_edge_vert = new_edge_verts[prev_edge.index]
vert_vert = new_vert_verts[loop.vert.index]
new_bm.faces.new([vert_vert, edge_vert, face_vert, prev_edge_vert])
loop = loop.link_loop_next
new_bm.edges.index_update()
new_bm.edges.ensure_lookup_table()
new_bm.faces.index_update()
new_bm.faces.ensure_lookup_table()
if normal_update:
new_bm.normal_update()
return new_bm
def subdivide(bm, iterations=1):
"""
Subdivide mesh by use of Catmull-Clark algorithm, one or several times.
Args:
bm: input mesh - as bmesh object.
iterations: number of times the subdivision is to be applied.
Returns:
new bmesh object.
"""
for i in range(iterations):
bm = subdivide_once(bm)
bm.normal_update()
return bm
Functions
def calc_new_verts(bm)
-
Calculate coordinates of new vertices by Catmull-Clark algorithm.
Args
bm
- input mesh, as Blender's bmesh object.
Returns
Lists of new vertices, all as np.ndarray of shape (x, 3):
- new vertices to replace vertices of original mesh
- new vertices to be placed in the middles of edges
- new vertices to be placed in the middles of faces
Expand source code
def calc_new_verts(bm): """ Calculate coordinates of new vertices by Catmull-Clark algorithm. Args: bm: input mesh, as Blender's bmesh object. Returns: Lists of new vertices, all as np.ndarray of shape (x, 3): * new vertices to replace vertices of original mesh * new vertices to be placed in the middles of edges * new vertices to be placed in the middles of faces """ verts, edges, faces = pydata_from_bmesh(bm) verts = np.asarray(verts) face_centers = np.array([tuple(f.calc_center_median()) for f in bm.faces]) edge_verts = np.array([verts[e] for e in edges]) points_by_edge = np.array([np.vstack(([face_centers[f.index] for f in e.link_faces], edge_verts[e.index])) for e in bm.edges]) edge_points = np.array([np.mean(pts, axis=0) if len(pts) == 4 else np.mean(pts[1:], axis=0) for pts in points_by_edge]) edge_centers = np.mean(edge_verts, axis=1) new_verts_from_faces = face_centers new_verts_from_edges = edge_points new_verts_from_verts = np.zeros((len(bm.verts), 3)) for vert in bm.verts: n = len(vert.link_faces) linked_face_idxs = [f.index for f in vert.link_faces] linked_edge_idxs = [e.index for e in vert.link_edges] P = np.array(vert.co) F = np.mean(face_centers[linked_face_idxs], axis=0) R = np.mean(edge_centers[linked_edge_idxs], axis=0) if n >= 3: new_verts_from_verts[vert.index] = (F + 2*R + (n-3)*P) / n elif n == 2: new_verts_from_verts[vert.index] = P else: new_verts_from_verts[vert.index] = (R + P) / 2 return new_verts_from_verts, new_verts_from_edges, new_verts_from_faces
def subdivide(bm, iterations=1)
-
Subdivide mesh by use of Catmull-Clark algorithm, one or several times.
Args
bm
- input mesh - as bmesh object.
iterations
- number of times the subdivision is to be applied.
Returns
new bmesh object.
Expand source code
def subdivide(bm, iterations=1): """ Subdivide mesh by use of Catmull-Clark algorithm, one or several times. Args: bm: input mesh - as bmesh object. iterations: number of times the subdivision is to be applied. Returns: new bmesh object. """ for i in range(iterations): bm = subdivide_once(bm) bm.normal_update() return bm
def subdivide_once(bm, normal_update=False)
-
Subdivide mesh by use of Catmull-Clark algorithm, one time.
Args
bm
- input mesh - as bmesh object.
normal_update
- if True, recalculate mesh normals in the end.
Returns
new bmesh object.
Expand source code
def subdivide_once(bm, normal_update = False): """ Subdivide mesh by use of Catmull-Clark algorithm, one time. Args: bm: input mesh - as bmesh object. normal_update: if True, recalculate mesh normals in the end. Returns: new bmesh object. """ points_from_verts, points_from_edges, points_from_faces = calc_new_verts(bm) new_bm = bmesh.new() new_vert_verts = [new_bm.verts.new(v) for v in points_from_verts] new_face_verts = [new_bm.verts.new(v) for v in points_from_faces] new_edge_verts = [new_bm.verts.new(v) for v in points_from_edges] new_bm.verts.index_update() new_bm.verts.ensure_lookup_table() for orig_face in bm.faces: n_edges = len(orig_face.edges) face_vert = new_face_verts[orig_face.index] loop = orig_face.loops[0] for i in range(n_edges): edge = loop.edge prev_edge = loop.link_loop_prev.edge edge_vert = new_edge_verts[edge.index] prev_edge_vert = new_edge_verts[prev_edge.index] vert_vert = new_vert_verts[loop.vert.index] new_bm.faces.new([vert_vert, edge_vert, face_vert, prev_edge_vert]) loop = loop.link_loop_next new_bm.edges.index_update() new_bm.edges.ensure_lookup_table() new_bm.faces.index_update() new_bm.faces.ensure_lookup_table() if normal_update: new_bm.normal_update() return new_bm