Skip to content

Instantly share code, notes, and snippets.

@kythyria
Created December 28, 2019 20:26
Show Gist options
  • Save kythyria/a61ec4a762da9273a0136967f8681e52 to your computer and use it in GitHub Desktop.
Save kythyria/a61ec4a762da9273a0136967f8681e52 to your computer and use it in GitHub Desktop.
Blender merge by normals
bl_info = {
"name": "Merge by Normals",
"author": "Kyth \"Grey Heron\" Tieran",
"version": (0,1,0),
"blender": (2,80,0),
"category": "Mesh",
"location": "View3D Editmode > Vertex > Merge by Normals",
"description": "Merge vertices that have the same position and same custom split normals together"
}
import bpy
import bmesh
import itertools
def create_loop_info(mesh):
infos = []
for loop in mesh.loops:
info = {
"loop": loop,
"vert": mesh.vertices[loop.vertex_index],
"edge": mesh.edges[loop.edge_index]
}
infos.append(info)
return infos
def group_concident_loops(mesh, loopinfos, epsilon):
def similar_enough(left, right):
close = (left["vert"].co - right["vert"].co).length <= epsilon
parallel = left["loop"].normal.angle(right["loop"].normal) <= epsilon
return close and parallel
groups = []
for loop in loopinfos:
group = None
for g in groups:
if similar_enough(g[0], loop):
group = g
break
if group is None:
group = []
groups.append(group)
group.append(loop)
return groups
def is_edge(v1,v2):
print("search (%d,%d)" % (v1,v2))
def is_edge_predicate(edge):
fwd = (edge.vertices[0] == v1) and (edge.vertices[1] == v2)
bck = (edge.vertices[0] == v2) and (edge.vertices[1] == v1)
print("Search (%d,%d), Candidate (%d,%d), match (%s,%s)" % (v1,v2,edge.vertices[0], edge.vertices[1],fwd,bck))
return ((edge.vertices[0] == v1) and (edge.vertices[1] == v2)) or ((edge.vertices[0] == v2) and (edge.vertices[1] == v1))
return is_edge_predicate
class WeldByNormalOperator(bpy.types.Operator):
"""Merges vertices which have the same custom normals together, preserving UV seams."""
bl_idname = "mesh.normal_mergeby"
bl_label = "Merge By Normals"
epsilon = 0.0001
@classmethod
def poll(cls, context):
obj = context.active_object
return obj and obj.type == 'MESH' and obj.mode == 'EDIT'
def execute(self, context):
obj = context.active_object
me = obj.data
me.calc_normals_split()
#bm = bmesh.from_edit_mesh(me)
# find loops whose vertices and normals are within epsilon of each other.
loopinfos = create_loop_info(me)
closeloops = group_concident_loops(me, loopinfos, 0.0001)
bpy.ops.object.mode_set(mode = 'OBJECT')
for group in closeloops:
for loop in group:
loop["vert"].select = True
bpy.ops.object.mode_set(mode = 'EDIT')
bpy.ops.mesh.remove_doubles(threshold=WeldByNormalOperator.epsilon, use_unselected=False)
bpy.ops.mesh.normals_tools(mode='RESET')
bpy.ops.mesh.select_all(action='DESELECT')
#bmesh.update_edit_mesh(me)
return {'FINISHED'}
def menu_func(self, context):
self.layout.operator("mesh.normal_mergeby")
def register():
bpy.utils.register_class(WeldByNormalOperator)
bpy.types.VIEW3D_MT_edit_mesh_merge.append(menu_func)
def unregister():
bpy.types.VIEW3D_MT_edit_mesh_merge.remove(menu_func)
bpy.utils.unregister_class(WeldByNormalOperator)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment