Select all verts that has 4 edges connected?

How can I select all verts that has 4 edges connected to it? I need to get the “intersecting” verts. I figured the best way is to check if it has 4 edges connected.

Something maybe like the following:

cube = bpy.context.active_object
bm = bmesh.from_edit_mesh(cube.data)

# Find all verts with exactly 4 edges coming into it
verts4 = [v for v in bm.verts if len(v.link_edges) == 4]

# i.e. using > 5 above will find you all of the nasty 6-poles in your topology etc.

How can I only select the ones visible? If I hide parts, it still selects verts that has 4 edges even if it’s hidden.

What gets selected

What I need

@AFWS:

Maybe this floats your boat?

import bpy
import bmesh

aob   = bpy.context.active_object

# 3D viewport should be in object mode
if not aob.mode == 'OBJECT' :
    bpy.ops.object.editmode_toggle()

fmesh = bmesh.new()
fmesh.from_mesh(aob.data)

# Ensure bmesh internal index tables are fresh
fmesh.verts.ensure_lookup_table()
fmesh.edges.ensure_lookup_table()
fmesh.faces.ensure_lookup_table()

# cand: verts member of exactly four edges and visible
cand = [v for v in fmesh.verts if v.hide == False and len(v.link_edges) == 4]

pick = list()
# Select candidates with all four neighboring vertices also visible
for v in cand :
    somehidden = False
    for e in v.link_edges :
        somehidden = somehidden or e.other_vert(v).hide
    if not somehidden :
        v.select_set(True)
        pick.append(v)
    else :
        v.select_set(False)
    
# Sync up bmesh and mesh
fmesh.to_mesh(aob.data)

@grosgood

OK, just got around to testing this. Doesn’t seem to do what I need. Any verts that has 4 edges visible ,but has more edges that’s hidden, doesn’t get selected. Might not be possible to do.

Why not use Select Similar - connected edges?

bpy.ops.mesh.select_similar(type=“EDGE”)

or if you dont need it via script, shift g -> connected edges

Ah! Well then. Perhaps this floats your boat…

import bpy
import bmesh

aob   = bpy.context.active_object

# 3D viewport should be in object mode
if not aob.mode == 'OBJECT' :
    bpy.ops.object.editmode_toggle()

fmesh = bmesh.new()
fmesh.from_mesh(aob.data)

# Ensure bmesh internal index tables are fresh
fmesh.verts.ensure_lookup_table()
fmesh.edges.ensure_lookup_table()
fmesh.faces.ensure_lookup_table()

# cand: verts with membership in four or more edges and are also visible
cand = [v for v in fmesh.verts if v.hide == False and len(v.link_edges) > 3]

# Container for vertices that meet our criteria
pick = list()
# Select candidates with exactly four neighboring visible vertices
for v in cand :
    visibleneighbors = []
    for e in v.link_edges :
        if e.other_vert(v).hide == False :
            visibleneighbors.append(e.other_vert(v))

    # Select vertices with exactly four visible neighbors
    if len(visibleneighbors) == 4 :
        v.select_set(True)
        pick.append(v)
    else :
        v.select_set(False)
    
# Sync up bmesh and mesh
fmesh.to_mesh(aob.data)

# Do what you will with the vertices inside pick...

This concludes the useful portion of this post. What follows is a little…
<rant>
“I aim for the stars… but sometimes I hit London.”

– “Major William Taggert” [played by actor James Daley], a trenchant reinterpretation of one of Wernher von Braun’s aspirations. Aim for the Stars, a 1960 Columbia Pictures film.

`Tis one of the more capricious facets of our human existence that the actual meaning of the words we dispatch off to readers can differ greatly from our intended meaning.

Why do we screw up what ought to be concomitant things?

Methinks that the focus we maintain in trying to express our intended ideas actually distorts our perception of the marks we entrain for our readers. We observe the marks, but read into them our intents instead.

A disconnect: we read what we think we have written, but not what we wrote.

It is a bit of a trick, but when it comes to proofing, we really need to induce a complete, but temporary, amnesia about our intents so that we may read what we’ve written without the crosstalk of our intents getting in the way.

Then, when we have fully grasped what we have written – and only j ust what we’ve written – we can release ourselves from our self-induced amnesia and determine if our intents and our writings coincide.

Self-induced amnesia happens to be very hard to achieve. But as servants to our readers, it is our manifest obligation to ensure that the marks we entrain on paper, be it virtual or physical, really do reflect our intended communications. Because it is only the marks that convey meaning. The intent between our ears is forever opaque to all the others outside of us.
</rant>

@AFWS
The difficulty I have is that your expression of needs is a moving target. I don’t think you have any malicious intents whatsoever, but you are unintentionally omitting ideas, are not asking what you intend to ask and are not aware that you are subjecting us to scope creep.

In light of my rant, I request that you actually read how you titled this post and what you actually asked in the initial go-around. Seeing only what you wrote, and unable to read your mind, @deadpin gave a very servicable reply, I think. Your reply gave rise to a second requirement - new to the game - that hidden vertices were also a material aspect of your needs. I took a whack at that. From that came another variant-of-needs, one which drifts from your initial question. It is not a pattern of actual connectivity that you need to detect, but a pattern of apparent connectivity of visible vertices in a mesh with randomly hidden ones.

That is what your first question should have conveyed in the initial go around. Closing the gap between expression and intent is a writer’s obligation to readers, even in something as simple as a post, a text message, anything.

I hope the code helps. Take care.

1 Like

Hello,

for example, I need select: <= 1, == 3, == 5, >= 6
how to implement this in code to not duplicate whole code with changing one parameter?
Thanks.

@APEC You need it to do all of those things at once or you need a way to user-specify one (or more) of those things at a time?

If your need is “select any that isn’t 2 or 4 at the same time” then change this
if len(visibleneighbors) == 4:
into this
if len(visibleneighbors) != 2 and len(visibleneighbors) != 4):

Hello,

I made a buttons with select wrong/bad topology
topol_sel
I used code above to make a class

# Verteces with X edges connected - select 
class VERTX_OT_sel_vertx(Operator):
    bl_idname = "vertx.sec_vertx"
    bl_label = "Single Verteces"
    bl_description = "Select vertexes with X connected edges"

    def execute(self, context):
        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.mesh.select_mode(type="FACE")
        bpy.ops.mesh.reveal()
        bpy.ops.mesh.select_all(action='DESELECT')

        aob = bpy.context.active_object

        # 3D viewport should be in object mode
        if not aob.mode == 'OBJECT' :
            bpy.ops.object.editmode_toggle()

        fmesh = bmesh.new()
        fmesh.from_mesh(aob.data)

        # Ensure bmesh internal index tables are fresh
        fmesh.verts.ensure_lookup_table()
        fmesh.edges.ensure_lookup_table()
        fmesh.faces.ensure_lookup_table()

        # cand: verts member of X edges and visible
        cand = [v for v in fmesh.verts if v.hide == False and len(v.link_edges) <= 1]

        pick = list()
        # Select candidates with all X neighboring vertices also visible
        for v in cand :
            somehidden = False
            for e in v.link_edges :
                somehidden = somehidden or e.other_vert(v).hide
            if not somehidden :
                v.select_set(True)
                pick.append(v)
            else :
                v.select_set(False)
            
        # Sync up bmesh and mesh
        fmesh.to_mesh(aob.data)

        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.mesh.select_mode(type="VERT")
        bpy.ops.mesh.select_similar(type='EDGE', threshold=0.01)
        
        return {'FINISHED'}

than I write UI

# Selecting topology statistic Panel
class SELTOPOLOGY_PT_main(Panel):          
    bl_label = "Select Topology"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = 'AC'
    
    def draw (self, context):
        layout = self.layout

        layout.label(text="Select Verts by Edges:")
        row = layout.row(align=True)
        row.operator("vertx.sec_vertx", text = "0 or 1", icon = 'CON_TRACKTO')
        row.operator("vertx.sec_vertx", text = "3", icon = 'PARTICLES')
        row = layout.row(align=True)
        row.operator("vertx.sec_vertx", text = "5", icon = 'OUTLINER_OB_ARMATURE')
        row.operator("vertx.sec_vertx", text = "6+", icon = 'SORTBYEXT')

And to not write 4 additional classes to call operator I think maybe I can have one operator with an index int property.
row.operator(“vertx.sec_vertx”, text = “0 or 1”, icon = ‘CON_TRACKTO’).id = 1
row.operator(“vertx.sec_vertx”, text = “3”, icon = ‘PARTICLES’).id = 2
and so on.
I can’t figure out how to change
cand = [v for v in fmesh.verts if v.hide == False and len(v.link_edges) <= 1]
to call
cand = [v for v in fmesh.verts if v.hide == False and len(v.link_edges) <= 1]
cand = [v for v in fmesh.verts if v.hide == False and len(v.link_edges) == 3]
cand = [v for v in fmesh.verts if v.hide == False and len(v.link_edges) == 5]
cand = [v for v in fmesh.verts if v.hide == False and len(v.link_edges) >= 6]

Neat tool.

What you want is to add a property to the operator and then pass a value to it from the button. Then the value from the button determines what the operator will do.
https://docs.blender.org/api/current/bpy.props.html

Here I just used a simple Integer Property with the name of your_property_name and then added that to the end of each button. You could also do it with a String Property and strings instead of numbers. The actual value doesn’t matter, just that you have 4 things that are different; it could be “banana” “potato” “watermelon” “taco” if you wanted.

Inside the operator itself it’s just a simple list of if/elif checks to find a match to the value passed from the button. There might be a way to inject the comparison on a single line with string evals or something instead of if/elif checks with their own discreet code but I’m not that good of a programmer yet.

# Verteces with X edges connected - select 
class VERTX_OT_sel_vertx(bpy.types.Operator):
    bl_idname = "vertx.sec_vertx"
    bl_label = "Single Verteces"
    bl_description = "Select vertexes with X connected edges"

    your_property_name: bpy.props.IntProperty(
        name="Your Name", 
        description="Choose number of connected edges",
        default=0)

    def execute(self, context):
        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.mesh.select_mode(type="FACE")
        bpy.ops.mesh.reveal()
        bpy.ops.mesh.select_all(action='DESELECT')

        aob = bpy.context.active_object

        # 3D viewport should be in object mode
        if not aob.mode == 'OBJECT' :
            bpy.ops.object.editmode_toggle()

        fmesh = bmesh.new()
        fmesh.from_mesh(aob.data)

        # Ensure bmesh internal index tables are fresh
        fmesh.verts.ensure_lookup_table()
        fmesh.edges.ensure_lookup_table()
        fmesh.faces.ensure_lookup_table()

        # cand: verts member of X edges and visible
        if self.your_property_name == 0:
            cand = [v for v in fmesh.verts if v.hide == False and len(v.link_edges) <= 1]
        elif self.your_property_name == 1:
            cand = [v for v in fmesh.verts if v.hide == False and len(v.link_edges) == 3]
        elif self.your_property_name == 2:
            cand = [v for v in fmesh.verts if v.hide == False and len(v.link_edges) == 5]
        elif self.your_property_name == 3:
            cand = [v for v in fmesh.verts if v.hide == False and len(v.link_edges) >= 6]

        pick = list()
        # Select candidates with all X neighboring vertices also visible
        for v in cand :
            somehidden = False
            for e in v.link_edges :
                somehidden = somehidden or e.other_vert(v).hide
            if not somehidden :
                v.select_set(True)
                pick.append(v)
            else :
                v.select_set(False)
            
        # Sync up bmesh and mesh
        fmesh.to_mesh(aob.data)

        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.mesh.select_mode(type="VERT")
        bpy.ops.mesh.select_similar(type='EDGE', threshold=0.01)
        
        return {'FINISHED'}
# Selecting topology statistic Panel
class SELTOPOLOGY_PT_main(Panel):          
    bl_label = "Select Topology"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = 'AC'
    
    def draw (self, context):
        layout = self.layout

        layout.label(text="Select Verts by Edges:")
        row = layout.row(align=True)
        row.operator("vertx.sec_vertx", text = "0 or 1", icon = 'CON_TRACKTO').your_property_name = 0
        row.operator("vertx.sec_vertx", text = "3", icon = 'PARTICLES').your_property_name = 1
        row = layout.row(align=True)
        row.operator("vertx.sec_vertx", text = "5", icon = 'OUTLINER_OB_ARMATURE').your_property_name = 2
        row.operator("vertx.sec_vertx", text = "6+", icon = 'SORTBYEXT').your_property_name = 3
1 Like

You my savior!
Thanks!

I guess all I had to do was let it simmer in my subconscious for an hour and a half and I became a better programmer because it just spontaneously popped in. :stuck_out_tongue: Here’s how you could construct it with a 1-line string eval (and String Property) instead of an if/elif list for each possible state:
https://www.w3schools.com/python/ref_func_eval.asp

# Verteces with X edges connected - select 
class VERTX_OT_sel_vertx(bpy.types.Operator):
    bl_idname = "vertx.sec_vertx"
    bl_label = "Single Verteces"
    bl_description = "Select vertexes with X connected edges"

    your_property_name: bpy.props.StringProperty(
        name="Your Name", 
        description="Choose number of connected edges",
        default="<= 1")

    def execute(self, context):
        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.mesh.select_mode(type="FACE")
        bpy.ops.mesh.reveal()
        bpy.ops.mesh.select_all(action='DESELECT')

        aob = bpy.context.active_object

        # 3D viewport should be in object mode
        if not aob.mode == 'OBJECT' :
            bpy.ops.object.editmode_toggle()

        fmesh = bmesh.new()
        fmesh.from_mesh(aob.data)

        # Ensure bmesh internal index tables are fresh
        fmesh.verts.ensure_lookup_table()
        fmesh.edges.ensure_lookup_table()
        fmesh.faces.ensure_lookup_table()

        # cand: verts member of X edges and visible
        cand = eval("[v for v in fmesh.verts if v.hide == False and len(v.link_edges) " + self.your_property_name + "]")

        pick = list()
        # Select candidates with all X neighboring vertices also visible
        for v in cand :
            somehidden = False
            for e in v.link_edges :
                somehidden = somehidden or e.other_vert(v).hide
            if not somehidden :
                v.select_set(True)
                pick.append(v)
            else :
                v.select_set(False)
            
        # Sync up bmesh and mesh
        fmesh.to_mesh(aob.data)

        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.mesh.select_mode(type="VERT")
        bpy.ops.mesh.select_similar(type='EDGE', threshold=0.01)
        
        return {'FINISHED'}
# Selecting topology statistic Panel
class SELTOPOLOGY_PT_main(Panel):          
    bl_label = "Select Topology"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = 'AC'
    
    def draw (self, context):
        layout = self.layout

        layout.label(text="Select Verts by Edges:")
        row = layout.row(align=True)
        row.operator("vertx.sec_vertx", text = "0 or 1", icon = 'CON_TRACKTO').your_property_name = "<= 1"
        row.operator("vertx.sec_vertx", text = "3", icon = 'PARTICLES').your_property_name = "== 3"
        row = layout.row(align=True)
        row.operator("vertx.sec_vertx", text = "5", icon = 'OUTLINER_OB_ARMATURE').your_property_name = "== 5"
        row.operator("vertx.sec_vertx", text = "6+", icon = 'SORTBYEXT').your_property_name = ">= 6"
1 Like

Wow. So many ways to solve a task.
Many thanks!

it seems my last command

bpy.ops.mesh.select_similar(type='EDGE', threshold=0.01)

er2
gives an error if nothing is selected, I think it can be solve if nothing selected after execute, it show message something like “All good!”
Is it possible?

for now I troubleshooted only error message with code

        me = bpy.context.object.data
        bm = bmesh.from_edit_mesh(me)
        for vert in bm.verts:
            if vert.select:
                bpy.ops.mesh.select_similar(type='EDGE', threshold=0.01)
                break

Else :
self.report({‘INFO’}, “All good!”)
? I’m so lame…)
but anyway, I can live without that message :slight_smile: