Editing normals?

How can I edit normals through a script?

I’m trying a basic thing just to see if it works:

ob = bpy.context.active_object
me = ob.data
for v in me.vertices:
    if v.select:
        v.normal = Vector((0,1,0))

But the change to the normals don’t show up in Edit mode. If I change v.co the change shows up right away. What else do I need to do for normals?

you can’t actually edit normals this way- blender uses split normals for this purpose. split normals are basically an ‘extra’ set of custom normals and they are stored per loop. luckily, this is a common enough operation that there is a shortcut to set split normal data per-vert instead of per-loop:

import bpy

obj = bpy.context.object
obj.data.use_auto_smooth = True
obj.data.normals_split_custom_set_from_vertices( [(1, 0, 0) for v in obj.data.vertices] )

note that the object must be set to use auto-smooth for you to actually see the split normals.

From here, I see there’s normals_split_custom_set_from_vertices() and normals_split_custom_set()

https://docs.blender.org/api/current/bpy.types.Mesh.html

Both functions accept an array of normals? So are you essentially passing it a full list of the new normals in the same order as the vertices? So if I really want to just change the selected vertices, I’ll generate my own list and pass that, something like this:

newNormals =[]
for v in me.vertices:
    if v.select:
        newNormals.append(Vector((1,0,0)))
    else:
        newNormals.append(v.normal)

me.normals_split_custom_set_from_vertices( newNormals )

So what does normals_split_custom_set() use instead of vertices? It still just takes a list of normals as a parameter, but the order should match something else?

normals_split_custom_set() works on loops- which is where the actual split normals data is stored. normals_split_custom_set_from_vertices() is really just a shortcut that allows people to set custom vert normals. in a closed mesh with no open boundaries, you should have four times the number of loops as you have verts- this is actually what lets you create “split” normals. if it weren’t for this, you’d actually have to detach faces/edges to make hard edges (this is actually how Blender worked many years ago).

1 Like

How i can get data of the custom normals? For example, after execution script below

>>> me = C.active_object.data
>>> me.normals_split_custom_set( [ (1,0,0) for l in me.loops ] )
>>> me.update()

the loops contains different data:

>>> me.loops[49].normal
Vector((0.0, 0.0, 0.0))

the loop normals collection don’t contain any information until you run calc_normals, then the internal split normal data is transfered to the collection for scripting purposes.

https://docs.blender.org/api/current/bpy.types.MeshLoop.html#bpy.types.MeshLoop.normal

you need to call me.calc_normals_split() first.

2 Likes

Not sure what’s missing but this method doesn’t work - normals are the same as before:

>>> me = C.active_object.data
>>> print(me.loops[0].normal)
<Vector (0.9483, 0.2400, 0.2075)>
>>> me.normals_split_custom_set( [ (1,0,0) for l in me.loops ] )
>>> me.calc_normals_split()
>>> me.update()
>>> print(me.loops[0].normal)
<Vector (0.9483, 0.2400, 0.2075)>

>>> print(me.vertices[0].normal)
<Vector (0.9493, 0.1998, 0.2426)>
>>> me.normals_split_custom_set_from_vertices( [ (1,0,0) for l in me.vertices ] )
>>> me.calc_normals_split()
>>> me.update()
>>> print(me.vertices[0].normal)
<Vector (0.9493, 0.1998, 0.2426)>

Update:

Turn out adding use_auto_smooth = True helps for loops (vertex normals seem unchangable though):

>>> me = C.active_object.data
>>> print(me.loops[0].normal)
<Vector (0.0000, 0.0000, 0.0000)>
>>> me.normals_split_custom_set( [ (1,0,0) for l in me.loops ] )
>>> me.use_auto_smooth = True
>>> me.calc_normals_split()
>>> me.update()
>>> print(me.loops[0].normal)
<Vector (1.0000, 0.0000, -0.0000)>