Vertici senza loop

Sep 23 2020

Sto lavorando a uno script Python per esportare una mesh. Ho bisogno di esportare le normali, quindi uso questo codice:

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

(Come sottolineato da @batFinger, questo metodo prenderà le informazioni dall'ultimo ciclo che condivide lo stesso vertice. Questo va bene per il mio flusso di lavoro poiché sto unendo le normali prima di esportare, ma i lettori di questa domanda dovrebbero usare il suo metodo se vogliono normali medie per un vertice).

Il problema è che nrm_avg_listfinisce per avere elementi con Nonesopra. Ho eseguito un po 'di debug ed effettivamente questi vertici non avevano loop su di loro.

Ho provato a ripulire la mia mesh (eliminando la geometria sciolta) ma questi vertici sono ancora presenti (e non riesco a trovarli nella finestra).

Come dovrei pulire questi vertici canaglia? C'è un modo, usando l'API Python (evitando bpy.opsse possibile) per rimuovere semplicemente questi vertici?

Risposte

2 batFINGER Sep 23 2020 at 11:35

Script Bmesh per rimuovere vertici non collegati a facce

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 il tuo metodo di domanda

Il problema con il metodo della domanda è che lo stesso vert può essere un membro di molti cicli. Solo quello del vertice nell'ultimo ciclo verrà assegnato alla tua lista.

Trova con relazioni uno a molti come questa che la defaultdictclasse da collectionsquando viene creata con a listassegna a qualsiasi chiave un valore predefinito list()o a []cui può essere aggiunto.

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])

quando viene eseguito sul cubo predefinito, restituisce:

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)]

cioè ogni vert ha 3 loop per ogni faccia a cui appartiene.

La media normale potrebbe essere calcolata tramite

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

o

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

per ottenere un vettore somma normalizzato. che quando viene eseguito sul cubo predefinito modificato sopra

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

uscite

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)>

che potrebbe essere più simile al risultato che stai cercando.

Y-King Sep 23 2020 at 09:43

per selezionare i vertici con l'API standard, è necessario deselezionare tutto e lasciare la modalità di modifica. L'impostazione di .select su False non funziona, solo l'impostazione su True è supportata utilizzando questo approccio.

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')