Sommets sans boucles

Sep 23 2020

Je travaille sur un script Python pour exporter un maillage. J'ai besoin d'exporter les normales, j'utilise donc ce code:

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

(Comme indiqué par @batFinger, cette méthode prendra les informations de la dernière boucle qui partage le même sommet. Cela convient à mon propre flux de travail puisque je fusionne les normales avant d'exporter, mais les lecteurs de cette question devraient utiliser sa méthode si ils veulent des normales moyennes pour un sommet).

Le problème est que nrm_avg_listles éléments finissent par figurer Nonedessus. A fait du débogage et effectivement ces sommets n'avaient aucune boucle sur eux.

J'ai essayé de nettoyer mon maillage (suppression de la géométrie lâche) mais ces sommets sont toujours présents (et je ne les trouve pas dans la fenêtre).

Comment nettoyer ces sommets voyous? Existe-t-il un moyen, en utilisant l'API Python (en évitant bpy.opssi possible) de supprimer simplement ces sommets?

Réponses

2 batFINGER Sep 23 2020 at 11:35

Script Bmesh pour supprimer les verts non liés à des faces

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 votre méthode de question

Le problème avec la méthode des questions est que le même vert peut être membre de plusieurs boucles. Seul celui du sommet de la dernière boucle sera affecté à votre liste.

Trouvez avec une à plusieurs relations comme celle-ci que la defaultdictclasse à partir de collectionslaquelle lorsqu'elle est créée avec a listdonne à n'importe quelle clé une valeur par défaut list()ou à []laquelle elle peut être ajoutée.

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

lorsqu'il est exécuté sur le cube par défaut, génère:

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

c'est-à-dire que chaque vert a 3 boucles pour chaque face à laquelle il appartient.

La normale moyenne peut être calculée via

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

ou

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

pour obtenir un vecteur somme normalisé. qui, lorsqu'il est exécuté sur le cube par défaut modifié ci-dessus

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

les sorties

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

qui pourrait être plus comme le résultat que vous recherchez.

Y-King Sep 23 2020 at 09:43

pour sélectionner des sommets avec l'API standard, vous devez tout désélectionner et quitter le mode d'édition. La définition de .select sur False ne fonctionne pas, seule la définition sur True est prise en charge avec cette approche.

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