5
$\begingroup$

I need to shift the order of referenced vertices in a face. I've been trying to do it in python after being unable to find a "normal" way of doing it, but I've hit a bit of a dead end and I hope someone can help me.

To be more concise: Let's say, for example, a triangle face uses the vertices [1, 2, 3]. What I need is to shift those indices to [2, 3, 1]. Obviously it's the same triangle, but I need those indices to be in a certain order.

The furthest I can get is the following python script that shows the vertex indices of the selected triangle. I'd love to rearrange the "verts" member, but it's read-only.

import bpy
import bmesh

me = bpy.context.object.data
bm = bmesh.new()   
bm.from_mesh(me)  

for face in bm.faces:
    if (face.select):        
        print("face index %u" % face.index)
            
        v0 = face.verts[0]        
        v1 = face.verts[1]
        v2 = face.verts[2]

        print("vertex 1: %u" % v0.index)
        print("vertex 2: %u" % v1.index)
        print("vertex 3: %u" % v2.index)

I understand that bmesh is some kind of "working copy" of a mesh which then needs to be saved back. Do I need to make a new bmesh and then copy the old one while applying my changes somehow?

EDIT: Since I can't write comments myself, I'll address thibsert's comment here: As far as I can tell that just rearranges the "global" list of vertices of the mest, which I need to be untouched. It's the face's own list of corner vertices that I need to shift. I know this is a very fringe use case :)

$\endgroup$
2
  • $\begingroup$ The "normal" way of doing it is : Mesh menu / Sort Elements / Reverse. Edit: not 100% sure this is correct, I'll leave it here just in case that might help you. $\endgroup$
    – thibsert
    Commented May 4, 2021 at 12:17
  • $\begingroup$ Re last edit. (Try commenting if replying to a comment on own question) The face verts are in a winding order. If two faces share an edge and you change order of one face's verts it may affect connected faces vert order. Can make a mesh using Mesh.from_pydata(verts, [], faces) where verts is coordinates and faces is tuples of vert indices. Consider testing to see if what you are attempting to do is possible. Also can you clarify, is the change in order always cycle one right? $\endgroup$
    – batFINGER
    Commented May 4, 2021 at 13:40

2 Answers 2

7
$\begingroup$

I don't know how to do it with a bmesh, but it's easy with a regular mesh since mesh.loops[i].vertex_index is writable.

import bpy

ob = bpy.context.object
assert ob.type == 'MESH'

# If in edit mode, switch to object mode and switch back at the end
was_in_edit_mode = False
if ob.mode == 'EDIT':
    bpy.ops.object.mode_set(mode='OBJECT')
    was_in_edit_mode = True

me = ob.data

for poly in me.polygons:
    if not poly.select:
        continue

    # Get loops for this poly
    # Loop = corner of a poly
    loop_start = poly.loop_start
    loop_end = loop_start + poly.loop_total
    loops = [me.loops[loopi] for loopi in range(loop_start, loop_end)]

    # Get vertex indices for each loop
    vidxs = [loop.vertex_index for loop in loops]

    # Shift
    vidxs = vidxs[1:] + vidxs[0:1]

    # Write back
    for i, loop in enumerate(loops):
        loop.vertex_index = vidxs[i]

# Not sure if you need this
me.update()

if was_in_edit_mode:
    bpy.ops.object.mode_set(mode='EDIT')
$\endgroup$
6
$\begingroup$

With one loop.

Was seeking clarification on this, if the idea is to rotate the vertices of the face loop. Similar to @scurest can use the foreach_get and foreach_set methods to provide quick access to values without looping in python. This will have a significantly increased performance for a high poly count.

The loop start is taken directly from the data. The loop end by accumulating the number of verts in each face.

Have used a deque to rotate the face vert list. A positive value for rotate rotates the list to n the right, negative to the left. eg to make the ith element first Deque.rotate(-i) For example could find which is face vert is closest to the origin and rotate each accordingly

import bpy
from collections import deque
from itertools import accumulate

rotate = 1 # rotate 1 to right

context = bpy.context

ob = context.object
me = ob.data
n = len(me.polygons)

loop_starts = [0] * n
me.polygons.foreach_get("loop_start", loop_starts)
loop_ends = [0] * n
me.polygons.foreach_get("loop_total", loop_ends)
loop_ends = accumulate(loop_ends)
face_select = [False] * n
me.polygons.foreach_get("select", face_select)
vert_indices = [0] * len(me.loops)
me.loops.foreach_get("vertex_index", vert_indices)
'''
# Note same as
me.polygons.foreach_get("vertices", vert_indices)
'''

for select, start, end in zip(
        face_select,
        loop_starts,
        loop_ends
        ):
    if select: 
        face_verts = deque(vert_indices[start : end])
        face_verts.rotate(rotate)
        vert_indices[start : end] = face_verts

me.loops.foreach_set("vertex_index", vert_indices)
#print(me.validate()) # while testing just in case.

Test on default cube.

Before

>>> me = C.object.data
>>> for f in me.polygons:
...     f.vertices[:]
...     
(0, 1, 3, 2)
(2, 3, 7, 6)
(6, 7, 5, 4)
(4, 5, 1, 0)
(2, 6, 4, 0)
(7, 3, 1, 5)

After

(2, 0, 1, 3)
(6, 2, 3, 7)
(4, 6, 7, 5)
(0, 4, 5, 1)
(0, 2, 6, 4)
(5, 7, 3, 1)

similarly with bmesh

>>> bm = bmesh.new()
>>> bm.from_mesh(me)
>>> for f in bm.faces:
...     [v.index for v in f.verts]
...     
[2, 0, 1, 3]
[6, 2, 3, 7]
[4, 6, 7, 5]
[0, 4, 5, 1]
[0, 2, 6, 4]
[5, 7, 3, 1]

Note.

Still need to make sure of the winding order, if for example sake were to simply randomly shuffle (random.shuffle) the face loop verts

        face_verts = vert_indices[start : end]
        shuffle(face_verts)
        vert_indices[start : end] = face_verts

will end up with incorrect winding, and a mess

enter image description here

$\endgroup$
5
  • $\begingroup$ loop_ends isn't necessarily accumulate(loop_totals). $\endgroup$
    – scurest
    Commented May 4, 2021 at 20:00
  • $\begingroup$ Can you provide an example where it isn't? $\endgroup$
    – batFINGER
    Commented May 4, 2021 at 20:08
  • $\begingroup$ If the loop_starts aren't in ascending order, it won't be true. You can make an example by picking any two quads and swapping their loop_start. I think it is true for anything that came from a bmesh, but it's just as easy to use start + total instead of end. $\endgroup$
    – scurest
    Commented May 4, 2021 at 21:01
  • $\begingroup$ validate() returns False if it was already valid, and it doesn't change the loop_starts for me. Going through a bmesh (toggling edit mode) does reorder back to ascending order. $\endgroup$
    – scurest
    Commented May 4, 2021 at 21:29
  • $\begingroup$ Yep know how validate works, even though totally naffed up the comment & .. forgot to edit. Simply toggling edit mode returns to ascending order eg range(0, 4), range(4, 8) etc for default cube. Very f-n unlikely that the user has made it such that the order isn't ascending. Happy to stick with accumulate. $\endgroup$
    – batFINGER
    Commented May 4, 2021 at 21:35

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .