# How can I remove a point of junction and reconnect 2 meshes via python?

I have a set of problematic meshes that have junction vertices (those with 4 edges). And I have to remove these via python and connect those vertices marked in green. Is this possible?

Basically I would like to make sure the parts are all connected as "manifold" 2D mesh.

I can select these vertices linked to 4 edges like this

import bpy
import bmesh

obj = bpy.context.active_object
mesh = obj.data

bpy.ops.object.mode_set(mode='EDIT')

bm = bmesh.from_edit_mesh(mesh)
bm.edges.ensure_lookup_table()
bm.verts.ensure_lookup_table()

bpy.ops.mesh.select_all(action='DESELECT')

for vert in bm.verts:
vert.select = True

bmesh.update_edit_mesh(mesh)


But I don't know how I can "dissolve" this vertex and reconnect the mesh with those green lines.

Here's a sample mesh with several intersections:

• is this limited to 2d meshes? Commented Apr 28 at 17:07
• @jupiterbjy yes only 2d meshes Commented Apr 29 at 5:19

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()

• I didn't know one could add their own panel, cool! I'm learning new things everyday here. Commented Apr 28 at 23:43
• Hi XY thank you for your answer. You almost got it but not so quite. I need the other vertices to be connected like here in your result with the expected result if that can hopefully clarify the problem. I also updated the question with a screenshot to make it more clear and added a sample mesh blend file. Commented Apr 29 at 5:52
• hey it works now exactly how i want by changing using this line e1, e2, e3, e0 = vert.link_edges thank you so much! so what i will do is basically just select 1 intersection at a time and execute this script. Commented Apr 29 at 7:14

Assuming that you're doing 2D, verts with no intersections other than that junction, this might cut it.

import bpy
import bmesh
import itertools

obj = bpy.context.active_object
mesh = obj.data

bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type="VERT")

bm = bmesh.from_edit_mesh(mesh)
bm.edges.ensure_lookup_table()
bm.verts.ensure_lookup_table()

bpy.ops.mesh.select_all(action='DESELECT')

def get_middle_pos(*vs):
"""Get middle position List[float, float, float] from given verts"""
return [
sum(v.co[idx] for v in vs) / len(vs)
for idx in range(3)
]

def is_close(pos1, pos2, margin=0.05):
"""Returns true if Root Mean Squared error between position is < margin"""
return sum(
((pos1[idx] - pos2[idx]) ** 2) ** 0.5
for idx in range(3)
) < 0.05

# find junction vert
junction_vert = None

for vert in bm.verts:
junction_vert = vert
break

]

# connect combinations of each pair where pair's mean position isn't close to center
new_edges = [
bm.edges.new((vert_a, vert_b))
for vert_a, vert_b in itertools.combinations(adjacent_verts, 2)
if not is_close(
get_middle_pos(vert_a, vert_b),
junction_vert.co
)
]

# remove junction vert
bm.verts.remove(junction_vert)

# select one from adjacents, as we know at least those will be at edge loop

# select edge loop
bpy.ops.mesh.select_mode(type="EDGE")

# create face from selection & switch to face mode
bpy.ops.mesh.select_mode(type="FACE")

# do limited resolve to remove inner edges and delete face.
bpy.ops.mesh.dissolve_limited()
bpy.ops.mesh.delete(type='ONLY_FACE')

# go back to object mode
bpy.ops.object.mode_set(mode='OBJECT')


Since it's 03:26AM right now expect long delay on feedback(if needed) until I return from workplace!

• Hello thank you for your answer. Although this solution may work for the simple mesh you have demonstrated with, it generally doesn't work for other meshes. you can try with the sample blend file i attached. also i updated the question with the expected result. Commented Apr 29 at 6:06
• @MeganLove now I see the whole picture, welp guess I'll delete mine later, now it no longer fit the question and might get downvoted eventually for those who doesn't know. btw It almost seems like you're using pixelated noise map and trying to create low poly continents! Mind if I ask what that actually is? Commented Apr 29 at 7:47