Vértices sin bucles

Sep 23 2020

Estoy trabajando en un script de Python para exportar una malla. Necesito exportar normales, entonces uso este código:

def get_custom_vertex_normals(mesh_obj):
    mesh_obj.data.calc_normals_split()
    nrm_avg_list = [None] * len(mesh_obj.data.vertices)
    for loop in mesh_obj.data.loops:
        nrm_avg_list[loop.vertex_index] = (loop.normal.x, loop.normal.y, loop.normal.z)
    return nrm_avg_list

(Como lo señaló @batFinger, este método tomará la información del último ciclo que comparte el mismo vértice. Esto está bien para mi propio flujo de trabajo, ya que estoy fusionando normales antes de exportar, pero los lectores de esta pregunta deberían usar su método si quieren normales promedio para un vértice).

El problema es que nrm_avg_listtermina teniendo elementos None. Hice un poco de depuración y, efectivamente, estos vértices no tenían bucles.

Intenté limpiar mi malla (eliminando geometría suelta) pero estos vértices todavía están presentes (y no puedo encontrarlos en la ventana gráfica).

¿Cómo debo limpiar estos vértices deshonestos? ¿Hay alguna manera, usando la API de Python (evitando bpy.opssi es posible) para simplemente eliminar estos vértices?

Respuestas

2 batFINGER Sep 23 2020 at 11:35

Script de Bmesh para eliminar verts no vinculados a ninguna cara

import bpy
import bmesh
from bpy import context

ob = context.object 
me = ob.data
# clean all verts not connected to faces
bm = bmesh.new()
bm.from_mesh(me)
bmesh.ops.delete(
    bm,
    geom=[v for v in bm.verts if not v.link_faces],
    context='VERTS', # default
    )
bm.to_mesh(me)

Re tu método de pregunta

El problema con el método de la pregunta es que el mismo vert puede ser miembro de muchos bucles. Solo el del vértice del último bucle se asignará a su lista.

Encuentre con una a muchas relaciones como esta en las que la defaultdictclase desde collectionsque se creó con a listle da a cualquier clave un valor predeterminado list()o al []que se puede agregar.

import bpy
from collections import defaultdict

def get_custom_vertex_normals(me):
    me.calc_normals_split()
    loop_norms = defaultdict(list)

    for loop in me.loops:
        loop_norms[loop.vertex_index].append(loop.normal)
    return loop_norms

ob = bpy.context.object
loop_norms = get_custom_vertex_normals(ob.data)
for i, normals in loop_norms.items():
        print(i, [tuple(round(x, 4) for x in v)  for v in normals])

cuando se ejecuta en el cubo predeterminado, genera:

0 [(0.0, 0.0, -1.0), (1.0, -0.0, 0.0), (0.0, 1.0, 0.0)]
1 [(0.0, 0.0, -1.0), (1.0, -0.0, 0.0), (-0.0, -1.0, -0.0)]
2 [(0.0, 0.0, -1.0), (-0.0, -1.0, -0.0), (-1.0, 0.0, -0.0)]
3 [(0.0, 0.0, -1.0), (-1.0, 0.0, -0.0), (0.0, 1.0, 0.0)]
4 [(0.0, 0.0, 1.0), (1.0, -0.0, 0.0), (0.0, 1.0, 0.0)]
7 [(0.0, 0.0, 1.0), (-1.0, 0.0, -0.0), (0.0, 1.0, 0.0)]
6 [(0.0, 0.0, 1.0), (-0.0, -1.0, -0.0), (-1.0, 0.0, -0.0)]
5 [(0.0, 0.0, 1.0), (1.0, -0.0, 0.0), (-0.0, -1.0, -0.0)]

es decir, cada vert tiene 3 bucles para cada cara a la que pertenece.

El promedio normal se puede calcular mediante

avge_normal = sum(normals, Vector()) / len(normals)

o

avge_normal = sum(normals, Vector()).normalized()

para obtener un vector suma normalizado. que cuando se ejecuta en el cubo predeterminado editado arriba

for i, normals in loop_norms.items():
    print(i, sum(normals, Vector()).normalized())

salidas

0 <Vector (0.5774, 0.5774, -0.5774)>
1 <Vector (0.5774, -0.5774, -0.5774)>
2 <Vector (-0.5774, -0.5774, -0.5774)>
3 <Vector (-0.5774, 0.5774, -0.5774)>
4 <Vector (0.5774, 0.5774, 0.5774)>
7 <Vector (-0.5774, 0.5774, 0.5774)>
6 <Vector (-0.5774, -0.5774, 0.5774)>
5 <Vector (0.5774, -0.5774, 0.5774)>

que podría ser más parecido al resultado que busca.

Y-King Sep 23 2020 at 09:43

para seleccionar vértices con API estándar, debe anular la selección de todo y salir del modo de edición. Establecer .select en False no funciona, solo se admite la configuración en True con este enfoque.

import bpy 
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.object.mode_set(mode='OBJECT') 
for v in bpy.context.object.data.vertices:
    if v.co[1] > 0:
        v.select = True 
bpy.ops.object.mode_set(mode='EDIT')   
bpy.ops.mesh.delete(type='VERT')