Created
December 28, 2019 20:26
-
-
Save kythyria/a61ec4a762da9273a0136967f8681e52 to your computer and use it in GitHub Desktop.
Blender merge by normals
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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