1
$\begingroup$

I'm working on a Python script to export a mesh. I need to export normals, so I use this 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

(As pointed out by @batFinger, this method will take the info from the last loop that shares the same vertex. This is fine for my own workflow since I'm merging normals before exporting, but readers of this question should use his method if they want average normals for a vertex).

The problem is nrm_avg_list ends up having elements with None on it. Did some debugging and effectively these vertices had no loops on them.

Did try to clean up my mesh (deleting loose geometry) but these vertices are still present (and I can't find them in the viewport).

How should I clean these rogue vertices? Is there a way, using the Python API (avoiding bpy.ops if possible) to simply remove these vertices?

$\endgroup$
2
$\begingroup$

Bmesh script to remove verts not linked to any 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 your question method

At issue with the question method is that the same vert can be a member of many loops. Only that of the vertex in last loop will be assigned to your list.

Find with one to many relationships like this that the defaultdict class from collections when created with a list gives any key a default value of list() or [] which can be appended to.

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

when run on default cube, outputs:

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

ie every vert has 3 loops for each face it belongs.

The average normal could be calculated via

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

or

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

to get a normalized sum vector. which when run on default cube edited into above

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

outputs

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

which could be more like the result you are after.

$\endgroup$
6
  • $\begingroup$ A bit off-topic, maybe.. a Python question. Is that a 2-arg signature of sum ..? sum(iterable,element constructor)? Built-in? Or where from? I can't find refs. $\endgroup$ – Robin Betts Sep 23 '20 at 5:36
  • 1
    $\begingroup$ Its the start value, see docs docs.python.org/3/library/functions.html#sum (RTFM lol) which by default is zero. Hence without some starting point eg zero vector will dummy spit adding int or float to vector. $\endgroup$ – batFINGER Sep 23 '20 at 5:41
  • $\begingroup$ Well ... I'm going to have to retreat to a corner with my cuddly keyboard and suck my dummy for a bit.. got to figure out how sum is adding non-numerics.. presumably picking up Vector 's __add__. Ahh. Many refs wrong. $\endgroup$ – Robin Betts Sep 23 '20 at 5:54
  • 1
    $\begingroup$ A full size mail order dummy? For a sum of three item list. start + item0 + item1 + item2 or for empty list start sum([]) == 0 and sum([], Vector()) == Vector((0, 0, 0)) that's pretty much the ins and outs of it. $\endgroup$ – batFINGER Sep 23 '20 at 5:58
  • 1
    $\begingroup$ Yep. This works sum([(1,), (2, 2), (3, 3, 3)], ()) could be used as a non uniform list raveller. Optimized for numerics IMO tho if it doesn't spit the dummy (eg any sniff of a str, str addition is an abomination) its hopefully quicker than for loop incrementing. and looks better. Numpy is the beast that is doing my head in. $\endgroup$ – batFINGER Sep 23 '20 at 6:39
0
$\begingroup$

to select vertices with standard API, you need to deselect everything and leave editmode. Setting .select to False doesn’t work, only setting to True is supported using this approach.

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')
$\endgroup$
1
  • $\begingroup$ As stated in the question, I'm trying to avoid bpy.ops as much as possible. $\endgroup$ – Leonardo Herrera Sep 23 '20 at 14:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.