[Python] How should I go about splitting faces in blender [python script]?

Discussão em 'Python' iniciado por Stack, Março 4, 2025.

  1. Stack

    Stack Membro Participativo

    I am currently trying to connect two opposite loops of faces by generating a connector between them. I managed to get it working, if the opposing loops have the same number of faces, however I am struggling to figure out what to do if the face counts are different. I figured out, that I should iterate over the faces of the smaller loop and subdivide the largest faces in half until the number of faces inside the loop is equal to the other loop. Unfortunately, I cannot figure out how to do it in code and this is what I have got so far:

    """
    Creates connectors between two face loops by finding the optimal face ordering
    for loop2_faces relative to loop1_faces to minimize connector length.

    Args:
    loop1_faces (list of BMFace): List of faces in the first loop.
    loop2_faces (list of BMFace): List of faces in the second loop.
    obj1 (Object): Blender object containing loop1.
    obj2 (Object): Blender object containing loop2.

    Returns:
    list of Object: List of connector objects created between the loops.
    """
    if obj1.mode != 'EDIT' or obj2.mode != 'EDIT':
    raise ValueError("Both objects must be in edit mode to perform this operation.")

    while len(loop1_faces) != len(loop2_faces):
    print("len(loop1_faces) != len(loop2_faces)")
    print(f"len of loop1_faces: {len(loop1_faces)}, len of loop2_faces: {len(loop2_faces)}")

    bm = bmesh.from_edit_mesh(obj2.data)
    for face in bm.faces:
    face.select = False
    for face in loop2_faces:
    if face.is_valid:
    face.select = True

    bmesh.update_edit_mesh(obj2.data)

    largest_face = find_largest_face(loop2_faces)
    print("Largest face:", largest_face)

    divided_faces = divide_face_in_half(obj2, largest_face)

    bm.faces.ensure_lookup_table()

    loop2_faces.remove(largest_face)
    new_faces = [f for f in bm.faces if f.select and f.is_valid]
    loop2_faces.extend(new_faces)

    bmesh.update_edit_mesh(obj2.data)

    print(f"Updated loop2_faces: {[f.index for f in loop2_faces]}")
    print(f"len of loop1_faces: {len(loop1_faces)}, len of loop2_faces: {len(loop2_faces)}")
    ordered_loop1_faces = order_faces_by_adjacency(loop1_faces[0], loop1_faces, clockwise=True)


    """
    Safely divide a quadrilateral face into two smaller quads using bpy.ops.mesh.subdivide
    with adjusted parameters to prevent grid-like results.

    Args:
    obj (bpy.types.Object): The Blender object containing the face.
    face (BMFace): The quadrilateral face to be divided.

    Returns:
    list: Updated list of faces after division, or an empty list if the operation fails.
    """

    # Ensure the object is in edit mode
    bpy.context.view_layer.objects.active = obj
    if obj.mode != 'EDIT':
    bpy.ops.object.mode_set(mode='EDIT')

    # Get the BMesh for the object's edit mesh
    bm = bmesh.from_edit_mesh(obj.data)

    # Ensure face lookups are refreshed
    bm.faces.ensure_lookup_table()
    bm.edges.ensure_lookup_table()

    # Match the target face
    matching_face = next((bm_face for bm_face in bm.faces if bm_face.index == face.index), None)

    if not matching_face:
    print("Target face not found in BMesh.")
    return []

    num_edges = len(matching_face.edges)

    if num_edges == 4:
    # Deselect all geometry
    for bm_face in bm.faces:
    bm_face.select = False
    for bm_edge in bm.edges:
    bm_edge.select = False

    # Select only two opposite edges
    edges = list(matching_face.edges)
    edges[0].select = True # Select one edge
    edges[2].select = True # Select the opposite edge

    # Update the mesh selection for Blender
    print("yesyes")
    bmesh.update_edit_mesh(obj.data, loop_triangles=False)

    # Subdivide the selected edges
    try:
    bpy.ops.mesh.subdivide(
    number_cuts=1,
    smoothness=0,
    ngon=False, # Ensure quads remain
    quadcorner='STRAIGHT_CUT', # Enforce straight cuts
    fractal=0,
    fractal_along_normal=0,
    seed=0,
    )

    # Refresh the BMesh
    bm = bmesh.from_edit_mesh(obj.data)
    bm.faces.ensure_lookup_table()

    # Collect new faces created by subdivision
    new_faces = [f for f in bm.faces if f.select and f != matching_face]

    print(f"Successfully divided face {face.index} into {len(new_faces)} new faces.")
    return new_faces
    except Exception as e:
    print(f"Subdivision failed: {e}")
    return []

    else:
    print(f"Face with {num_edges} edges cannot be divided into two quads.")
    return []


    """
    Orders faces in the loop based on physical adjacency starting from start_face.

    Args:
    start_face (BMFace): The starting face in the loop.
    face_loop (list of BMFace]): List of faces in the second loop.
    clockwise (bool): If True, traverse the loop in a clockwise direction.

    Returns:
    list of BMFace: Faces ordered by adjacency.
    """
    ordered_faces = [start_face]
    face_set = set(face_loop)
    face_set.remove(start_face)

    current_face = start_face

    while face_set:
    next_face = None

    # Find the next face in the loop that shares an edge with the current face
    for edge in current_face.edges:
    for linked_face in edge.link_faces:
    if linked_face in face_set:
    next_face = linked_face
    break
    if next_face:
    break

    if next_face is None:
    print("WARNING: Stopping early, some faces are disconnected.")
    break # Prevent infinite loops

    # Add face and remove it from the remaining set
    ordered_faces.append(next_face)
    face_set.remove(next_face)
    current_face = next_face

    # If we didn't order all faces, print debugging info
    if len(ordered_faces) != len(face_loop):
    print(f"ERROR: Only {len(ordered_faces)} / {len(face_loop)} faces ordered!")
    print(f"Remaining unprocessed faces: {[f.index for f in face_set]}")

    return ordered_faces if clockwise else list(reversed(ordered_faces))


    Currently, it seems to work splitting the faces in half, but it fails here: WARNING: Stopping early, some faces are disconnected. It seems to me, that the new faces (resulting from the subdivisions) do not share the same edges with the other faces of the loop and that is why that fails, but in the viewport they seem to be in the correct place. ERROR: Only 7 / 40 faces ordered! Remaining unprocessed faces: [46009, 45973, 46010, 45974, 76013, 46013, 45977, 46014, 45978, 46017, 45981, 46018, 45982, 46021, 45985, 45986, 45989, 45990, 45993, 45994, 75998, 45997, 45998, 46001, 45965, 46002, 45966, 76005, 46005, 45969, 46006, 45970, 76010]

    I apologize for the post sloppiness, it is my first stackoverflow question.

    example of a face loop

    I figured out, that I should iterate over the faces of the smaller loop and subdivide the largest faces in half until the number of faces inside the loop is equal to the other loop. Unfortunately, I cannot figure out how to do it in code and this is what I have got so far. I also tried splitting the faces (in the smaller loop) manually, but then the script didn't work due to some index out of range error.

    Continue reading...

Compartilhe esta Página