Skip to content

Instantly share code, notes, and snippets.

@stestagg
Last active January 31, 2024 01:03
Show Gist options
  • Save stestagg/b39a38e49ab2965f619998f1a6c3d5b6 to your computer and use it in GitHub Desktop.
Save stestagg/b39a38e49ab2965f619998f1a6c3d5b6 to your computer and use it in GitHub Desktop.
edge-distance based mesh cleanup
import bpy
import bmesh
import numpy as np
from math import radians
MERGE_THRESHOLD = 0.01
DISSOLVE_ANGLE = 0.5
MAX_ITER = 10
def distance_from_line(p, q, rs, oobval):
x = p - q
shifted_points = rs - q
projections = np.dot(shifted_points, x)
x_magnitude_squared = np.dot(x, x)
scaling_factors = projections / x_magnitude_squared
in_bounds = (scaling_factors >= 0) & (scaling_factors <= 1)
scaling_factors = scaling_factors[in_bounds]
projected_points = np.outer(scaling_factors, x) + q - rs[in_bounds]
distances = np.full((rs.shape[0],), oobval, dtype=np.float64)
distances[in_bounds] = np.linalg.norm(projected_points, axis=1)
return distances
def closest_point_parameter(a, b, x):
ab = b - a
ax = x - a
t = np.dot(ax, ab) / np.dot(ab, ab)
return t
obj = bpy.context.edit_object
me = obj.data
for i in range(MAX_ITER):
bm = bmesh.from_edit_mesh(me)
bm.verts.ensure_lookup_table()
print(f'Iter {i}. Verts: {len(bm.verts)}. Edges: {len(bm.edges)}')
edge_as = []
edge_bs = []
verts = np.array([v.co for v in bm.verts])
vert_idxs = np.arange(len(bm.verts))
edge_percents = {}
for edge in bm.edges:
e_a, e_b = edge.verts
a = np.array(e_a.co)
b = np.array(e_b.co)
vert_mask = np.all(verts == a, axis=1) | np.all(verts == b, axis=1)
other_verts = verts[~vert_mask]
dists = distance_from_line(a, b, other_verts, MERGE_THRESHOLD * 10)
min_dist_idx = np.argmin(dists)
if dists[min_dist_idx] < MERGE_THRESHOLD:
vert_idx = vert_idxs[~vert_mask][min_dist_idx]
vert = bm.verts[vert_idx]
percent = closest_point_parameter(a, b, other_verts[min_dist_idx])
edge_percents[edge] = percent
if not edge_percents:
break
bmesh.ops.subdivide_edges(bm, edges=list(edge_percents.keys()), cuts=1, edge_percents=edge_percents)
bmesh.update_edit_mesh(me, loop_triangles=True)
bpy.ops.mesh.remove_doubles(threshold=MERGE_THRESHOLD, use_unselected=True)
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.mesh.select_mode(type = 'FACE')
bpy.ops.mesh.select_interior_faces()
bpy.ops.mesh.delete(type='FACE')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.dissolve_limited(angle_limit=radians(DISSOLVE_ANGLE))
print('DONE')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment