2
$\begingroup$

I want to build a blender python loop around all my vertices of one plane which is subdivided and so has many vertices. It should select one vertice (do something else with it which is not the question here) and select the next vertice after each iteration.

It should start with the top left, then go through the first row to the right and then step over to the second row and again go from left to right.:

enter image description here

$\endgroup$
2
  • $\begingroup$ Is it always the same plane? Otherwise the script, in order to determine which vertex is fist (which is top-left) would have to read viewport camera properties. $\endgroup$ Apr 4 '21 at 19:07
  • $\begingroup$ It will be used for one plane only. But this plane could be different when I create a new blender file. But it would be okay to have some manual changes in the script to make it work. It's not necessary to cover every possibility. $\endgroup$
    – karlo922
    Apr 4 '21 at 19:20
3
$\begingroup$

Operator solution

Since you're asking specifically to select a vertex:

import bpy

data = bpy.context.view_layer.objects.active.data

bpy.ops.object.mode_set(mode = 'OBJECT')
verts = sorted([(v.co.z, v.co.x, v.index) for v in data.vertices], reverse=True)
bpy.ops.object.mode_set(mode = 'EDIT') 

for z, x, v_index in verts:
    bpy.ops.mesh.select_mode(type="VERT")
    bpy.ops.mesh.select_all(action = 'DESELECT')
    bpy.ops.object.mode_set(mode = 'OBJECT')
    data.vertices[v_index].select = True
    # modify manually here, e.g.:
    # data.vertices[v_index].co.y += x
    bpy.ops.object.mode_set(mode = 'EDIT') 
    # use bpy.ops here, e.g.:
    # if x > 0: bpy.ops.mesh.hide()

Bmesh solution

import bpy, bmesh

mesh = bpy.context.edit_object.data
bm = bmesh.from_edit_mesh(mesh)
verts = sorted([(v.co.z, v.co.x, v) for v in bm.verts], reverse=True)
for z, x, v in verts:
    v.co.y = z+x # example
    
bmesh.update_edit_mesh(mesh)

Changing for different planes (views)

I assumed the Back Orthographic view and XZ plane: . For other planes you need to sort differently. Perhaps remove reverse argument and use minus signs to swap the sign of the coordinate inside the tuple used for sorting. So the following line:

verts = sorted([(v.co.z, v.co.x, v) for v in bm.verts], reverse=True)

would become: [for Back Orthographic]

verts = sorted([(-v.co.z, -v.co.x, v) for v in bm.verts])

and for Front Orthographic you would change the sign of x for sorting:

verts = sorted([(-v.co.z, v.co.x, v) for v in bm.verts])

Likewise you may want to turn (v.co.z, v.co.x, v) to (v.co.x, v.co.z, v) if you rotate the camera by 90 degrees or want to iterate by columns instead of rows.

Interactive Testing

Here's a script allowing to test if the order is correct, by pressing Ctrl+Shift+F12

bl_info = {
    "name": "Vertex iterator",
    "category": "Mesh",
}

import bpy

addon_keymaps = []
current_vertex = 0


class WorkMacro(bpy.types.Operator):
    """Work Macro"""
    bl_idname = "object.work_macro"
    bl_label = "Work Macro"
    bl_options = {"REGISTER"}

    def execute(self, context):    
        global current_vertex    
        data = bpy.context.view_layer.objects.active.data
        bpy.ops.object.mode_set(mode = 'OBJECT')
        verts = sorted([(-v.co.z, -v.co.x, v.index) for v in data.vertices])
        bpy.ops.object.mode_set(mode = 'EDIT')
        z, x, v_index = verts[current_vertex]
        bpy.ops.mesh.select_mode(type="VERT")
        bpy.ops.mesh.select_all(action = 'DESELECT')
        bpy.ops.object.mode_set(mode = 'OBJECT')
        data.vertices[v_index].select = True
        bpy.ops.object.mode_set(mode = 'EDIT')
        current_vertex = (current_vertex + 1) % len(verts) 
        return {'FINISHED'}


def register():
    bpy.utils.register_class(WorkMacro)
    wm = bpy.context.window_manager
    km = wm.keyconfigs.addon.keymaps.new(name='3D View Generic', space_type='VIEW_3D')
    kmi = km.keymap_items.new(WorkMacro.bl_idname, 'F12', 'PRESS', ctrl=True, shift=True)
    addon_keymaps.append(km)
    

def unregister():
    bpy.utils.unregister_class(WorkMacro)
    wm = bpy.context.window_manager
    for km in addon_keymaps:
        wm.keyconfigs.addon.keymaps.remove(km)
    del addon_keymaps[:]


if __name__ == "__main__":
    register()

$\endgroup$
7
  • $\begingroup$ Thank you very much but unfortunately it does not fully work as expected. It somehow gets disrupted somewhere in the middle of the plane. I can reproduce this everytime. I create a plane, go to Edit mode, rotate by x axis with 90°, make it bigger and to very many subdivisions. I have wirtten a script which tehn assigns a vertex group so teach single vertex with a weight of "1". In weight paint mode if you step through all vertices its then easy to see where it did not count correctly (as the weigth paint directly shows this). Its somewhere in the middle of the plane $\endgroup$
    – karlo922
    Apr 5 '21 at 6:26
  • $\begingroup$ It seems that it only correctly works using bmesh. With that I now got it working $\endgroup$
    – karlo922
    Apr 5 '21 at 8:13
  • $\begingroup$ @karlo922 I tested both in Back Orthographic view (the same as in your question), without any transformations on the object containing the plane mesh. I added a script so you can more easily test your case and see if indeed the selection is a problem, or maybe something else. $\endgroup$ Apr 5 '21 at 8:28
  • $\begingroup$ Hi, the selection is the issue and stays the issue even with bmesh. It works for many vertices but somewhere in the plane it jumps. It jumps after finishing a row not to X near 0 but somewhere in the middle, and then jumps to the x = 0 later. I do not understand what is it doing $\endgroup$
    – karlo922
    Apr 5 '21 at 14:21
  • $\begingroup$ <Vector (0.5935, 0.0000, 3.8381)> <Vector (0.6755, 0.0000, 3.8381)> <Vector (0.7576, 0.0000, 3.8381)> <Vector (0.0190, 0.0000, 3.8381)>' This is the sorted list, you see that the last element is in wrong order $\endgroup$
    – karlo922
    Apr 5 '21 at 14:29
2
$\begingroup$

Default order of things.

If we add a primitive grid, the vertices are ordered left to right ($X$), bottom to top ($Y$).

enter image description here

With all geometry selected, Scaling by -1 in local $Y$ axis, SYY -1 will change index order to left to right, top to bottom, but will flip the normals.

Use AltN to flip them back.

Sorting and Saving

The answer here contains links and shows how to re-sort a meshes indices using bmesh.

So similarly to method of @MarkusvonBroady can sort by geo (in this case $XY$ plane) and set the verts in this order.

import bpy
import bmesh
context = bpy.context
me = context.edit_object.data

bm = bmesh.from_edit_mesh(me)
for i, v in enumerate(
        sorted(
            bm.verts, 
            key=lambda v: (-v.co.y, v.co.x)
            )
        ):
    v.index = i
    
bm.verts.sort()
bmesh.update_edit_mesh(me)

Iterate over indices ==> the order we want.

Now simply iterating thru the verts will give the desired order, eg for 4x4 grid as shown in gif,

for v in mesh.vertices:
    row = v.index // 4
    col = v.index % 4 

Note, counting from zero, not one.

$\endgroup$
2
  • $\begingroup$ Nice, I didn't know you can change the vertex index, especially inside a loop. And the links don't break, because each vertex object holds a reference to a link object and only in a file links use indices? $\endgroup$ Apr 5 '21 at 15:19
  • $\begingroup$ bit confused by your terminology. Imagine this is much like ensure lookup table, get all the valid verts and re-index them. In this case set the order with index property and call bm.verts.sort() to re index faces / edges / loops etc. Would make for an interesting effect in conjunction with vertex parenting. $\endgroup$
    – batFINGER
    Apr 5 '21 at 15:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.