2
$\begingroup$

I'm trying to write a blender script which duplicate and flip faces if X material is found. Right now the script only can duplicate the faces but I need to flip their normals. (Only duplicated faces not the original faces)

The code I have so far:

for ob in bpy.context.scene.objects:
    if ob.type == 'MESH':
        tbh_slots = [id for id, mat in enumerate(ob.data.materials) if mat == mat_tbh]

        faces_mat_tbh = []
        bm = bmesh.new()
        bm.from_mesh(ob.data)

        for face in bm.faces:
            if face.material_index in tbh_slots:

                faces_mat_tbh.append(face)

        bmesh.ops.duplicate(bm, geom=faces_mat_tbh)
        
        bmesh.ops.reverse_faces(bm, faces=faces_mat_tbh, flip_multires=False) #Trying to flip normals, but it does it to every face that applies to the condition, for obvious reasons.
        bm.to_mesh(ob.data)
        bm.free()

    else: 
        ob.select_set(state=True)
$\endgroup$
2
  • 1
    $\begingroup$ You're almost there; after you perform the duplicate operation create a new list faces_added = [f for f in bm.faces if f.material_index in tbh_slots and f not in faces_mat_tbh] then when performing the reverse_faces operation reference the new list. should get you what you need. $\endgroup$
    – Ratt
    Mar 10, 2021 at 18:45
  • $\begingroup$ @Ratt thanks, I can't test it yet, now I'm having an error that says mat_tbh is not definited despite being working some hours ago without any change. $\endgroup$
    – Ancapo
    Mar 10, 2021 at 19:00

1 Answer 1

3
$\begingroup$

Bmesh operators return resulting geometry.

  • Most likely only want to process a mesh once, a hundred selected objects could all share one mesh. Hence have made a set.

  • Materials can be OBJECT linked. Have stuck with mesh linked (Mesh.materials) but in general often a better option to iterate over the object's material slots.

  • Make one bmesh, populate it, do stuff, write back, then clear and repeat, then free.

  • The duplicate operator returns a number of items in a dictionary regarding the result, consult the docs bmesh.ops.duplicate or check it out in the console.

    >>> bmesh.ops.duplicate(
    duplicate()
    BMeshOpFunc bmesh.ops.duplicate(bmesh, geom=[], dest=None, use_select_history=False, use_edge_flip_from_face=False)
    -> dict(geom_orig=[], geom=[], vert_map={}, edge_map={}, face_map={}, boundary_map={}, isovert_map={})
    

    we can get the new faces by checking for instances of bmesh.types.BMFace in the "geom" key or instead I have made a list of the values of the "face_map" (which is a handy dictionary with originals as keys, and newbies as values)

    It is also more often than not the case that the newly added faces are

     bm.faces[-len(faces_mat_tbh):]
    

    the last n faces where n is the length of the to dupe list.

Changes.

import bpy
import bmesh
from bpy import context
# no need to do same on linked meshes.

mat_tbh = bpy.data.materials.get("Material")

meshes = set(
        ob.data for ob in context.selected_objects
        if ob.type == 'MESH'
        )
        
bm = bmesh.new()    
for me in meshes:
    tbh_slots = [id for id, mat 
            in enumerate(me.materials) 
            if mat
            and mat is mat_tbh]
    if not tbh_slots:
        continue
    bm.from_mesh(me)
    new_faces = bmesh.ops.duplicate(
            bm, 
            geom=[
                f for f in bm.faces
                if f.material_index in tbh_slots        
                ]
            )["face_map"]
    
    bmesh.ops.reverse_faces(
            bm, 
            faces=list(new_faces.values()), 
            flip_multires=False,
            )
    bm.to_mesh(me)
    bm.clear()
        
bm.free()
$\endgroup$
1
  • $\begingroup$ I forget to add that I need to do this by texture/bitmap name instead of Material name, and in a loop around a list of texture names. $\endgroup$
    – Ancapo
    Mar 26, 2021 at 3:44

You must log in to answer this question.

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