Angle based method

There is a n-panel button you can test it.

import bpy, bmesh
from bpy.props import *
from collections import defaultdict
from math import asin, pi
# return a bm vert and shared amount that have largest shared amount
def get_common_vert(edges):
d = defaultdict(list)
for e in edges:
d[e.verts[0]].append(None)
d[e.verts[1]].append(None)
k = max(d, key=lambda x: len(d[x]))
return k, len(d[k])
def get_angle(v0, v_center, v1):
vec1 = (v0.co - v_center.co).normalized()
vec2 = (v1.co - v_center.co).normalized()
if vec1.dot(vec2) >= 0.0:
return 2.0 * asin((vec1 - vec2).length / 2.0)
return pi - 2.0 * asin((vec1 + vec2).length / 2.0)
class ModalOperator(bpy.types.Operator):
bl_idname = "wm.modal_operator"
bl_label = "Simple Modal Operator"
bl_options = {'REGISTER', 'UNDO'}
is_even : BoolProperty(name="Even", default=False)
def warn(self, s):
self.report({"WARNING"}, s)
return {'CANCELLED'}
def execute(self, context):
if context.area.type != "VIEW_3D": return self.warn("Requires 3D Viewport")
if not context.object: return self.warn("Active object not find")
if context.object.mode != "EDIT": return self.warn("Requires Edit Mode")
if not hasattr(context.object, "type") or context.object.type != "MESH": return self.warn("Mesh not found")
bm = bmesh.from_edit_mesh(context.object.data)
selected_edges = [e for e in bm.edges if e.select]
if not selected_edges: return self.warn("No edge selected")
vert, amount = get_common_vert(selected_edges)
if amount != 4: return self.warn("Largest shared vertex must equal to 4.")
# find 2 pair edge (Angle based)
e1, e2, e3, e0 = vert.link_edges
if self.is_even:
e1, e3 = e3, e1
e0_e1_angle = get_angle(*[e for e in e0.verts if e is not vert], vert, *[e for e in e1.verts if e is not vert])
e0_e2_angle = get_angle(*[e for e in e0.verts if e is not vert], vert, *[e for e in e2.verts if e is not vert])
if e0_e1_angle > e0_e2_angle:
v0, = [e for e in e0.verts if e is not vert]
v1, = [e for e in e2.verts if e is not vert]
v2, = [e for e in e1.verts if e is not vert]
v3, = [e for e in e3.verts if e is not vert]
else:
v0, = [e for e in e0.verts if e is not vert]
v1, = [e for e in e1.verts if e is not vert]
v2, = [e for e in e2.verts if e is not vert]
v3, = [e for e in e3.verts if e is not vert]
bm.verts.remove(vert)
bm.edges.new((v0, v1))
bm.edges.new((v2, v3))
bmesh.update_edit_mesh(context.object.data)
bm.free()
return {'FINISHED'}
class MY_PT_Panel(bpy.types.Panel):
bl_label = "My Tool"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "My Tools"
def draw(self, context):
self.layout.operator(ModalOperator.bl_idname, text=ModalOperator.bl_label)
def register():
bpy.utils.register_class(ModalOperator)
bpy.utils.register_class(MY_PT_Panel)
def unregister():
bpy.utils.unregister_class(ModalOperator)
bpy.utils.unregister_class(MY_PT_Panel)
if __name__ == "__main__":
register()