# Get hard shading normals in BPY

I'm writing an exporter and have some problems with my normals. Relevant Code:

def mesh_triangulate(me):
import bmesh
bm = bmesh.new()
bm.from_mesh(me)
bmesh.ops.triangulate(bm, faces=bm.faces)
bm.to_mesh(me)
bm.free()

meshes = [m for m in context.scene.objects if m.type=='MESH']
for obj in meshes:
# create a new mesh with all modifiers applied
mesh = obj.to_mesh(scene, True, 'RENDER', calc_tessface=False)

mesh_triangulate(mesh)
indices = list()
for poly in mesh.polygons:
for i in poly.loop_indices:
indices.append(mesh.loops[i].vertex_index)

positions = list()
for v in mesh.vertices:
positions.extend((v.co.x, v.co.y, v.co.z))

normals = list()
for n in mesh.vertices:
normals.extend((n.normal.x, n.normal.y, n.normal.z))
bpy.data.meshes.remove(mesh)


With this I always get flat shading. When I export the same object, once with smooth and once with flat shading, the indices, positions and normals are completely the same. I would expect to have each vertex multiple times for flat shading, to that I can have different normals at the same position, but this does not happen in my approach. I also need to have all modifiers applied and a triangulated mesh.

How can this be fixed?

Normals (except for normal maps, and some really strange formats) are always stored one-per-face or one-per-vertex. The renderer will calculate a normal for each pixel at display-time, but that normal is not stored in the mesh.

Smooth shading (interpolation) does not add data to the mesh, which is why it's so desirable. It is like a "post-processing" step. When rendering a given pixel (smooth shaded or not), the renderer has to use the normal to calculate incidence angle, to get a shading value. If smooth shading is off, then it just uses the normal that's stored for that face to calculate the incidence angle. If smooth shading is on, then it calculates an intermediate normal for that pixel that's a "blend" of the face's normal and the normals of the adjacent faces. That temporary "in between" normal is used to calculate shading for that pixel, and then discarded. Those "in between" values aren't stored anywhere. They're always calculated at display-time.

Some file types have a flag that indicates whether the receiving system should do this interpolation or not, but it's up to the receiving system to actually do the interpolation. That interpolation doesn't get stored in the mesh.

Some file formats (like OBJ) do store smoothing groups, but that only describes the borders where hard edges should NOT be smoothed.

It took me a while longer to get this ready for you, sorry. here is my approach to export mesh data as json. I decided to leave you the whole code in context, so that you can really try to understand how to calculate the split normals. I also left how to extract uvs, vertices, positions and how to do this for subsets of polygons.

from bpy_extras.io_utils import unpack_list

def parse_mesh(bl_mesh, faces=None):
"""
Parses a blender mesh into json arrays
Example:

Non-interleaved:
positions: [vx, vy, vz, ... ] size: multiple of 3
normals: [nx, ny, nz, ...] size: multiple of 3
uv: [u1, v1, ...] optional, multiple of 2
uv2: [u2, v2, ...] optional, multiple of 2

Interleaved: )
(... TODO)

Args:
bl_mesh ('bpy.types.Mesh') - The mesh data block to parse.
Kwargs:
faces ('list(bpy.types.MeshPolygon)') - The subset of polygons to parse.
Returns:
al_mesh ('dict') - The json mesh dictionary.
"""

# UV Textures by name
texture_uvs = bl_mesh.tessface_uv_textures.get('UVMap')

if faces is None:
faces = bl_mesh.polygons

_vertices = []
_normals = []
_uvs = []
_uvs2 = []

# Used for split normals export
face_index_pairs = [(face, index) for index, face in enumerate(faces)]

# Calculate the split normals from the shading
bl_mesh.calc_normals_split()
loops = bl_mesh.loops

for face, face_index in face_index_pairs:
# gather the face vertices
face_vertices = [bl_mesh.vertices[v] for v in face.vertices]
face_vertices_length = len(face_vertices)

vertices = [(v.co.x, v.co.y, v.co.z) for v in face_vertices]

# This is where you extract the split normals from the mesh
normals = [(loops[l_idx].normal.x, loops[l_idx].normal.y, loops[l_idx].normal.z) for l_idx in face.loop_indices]

uvs = [None] * face_vertices_length

if texture_uvs:
uv_layer = texture_uvs.data[face.index].uv
uvs = [(uv[0], uv[1]) for uv in uv_layer]

_vertices += vertices
_normals += normals
_uvs += uvs
_uvs2 += uvs2

al_mesh = OrderedDict()
al_mesh['vertices'] = unpack_list(_vertices)
al_mesh['normals'] = unpack_list(_normals)

if texture_uvs:
al_mesh['uvs'] = unpack_list(_uvs)

return al_mesh


you should perform the following operations on the blender object before calling the parse_mesh function (this function returns an altered copy of the mesh datablock, with triangulated faces and applied modifiers, so if you for example use an edge split modifier on a smooth mesh with angle of 66°, you will get excactly this result in your export.):

def prepare_mesh(obj):
# This applies all the modifiers (without altering the scene)
mesh = obj.to_mesh(context.scene, apply_modifiers=True, settings='RENDER')

# Triangulate for web export
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.triangulate(bm, faces=bm.faces)
bm.to_mesh(mesh)
bm.free()
del bm

mesh.calc_normals()
mesh.calc_tessface()

return mesh


if you need further information, ask me anytime. Or better, if you find improvements, I'd love to hear them!

• I finally had time, to try it out. It works somewhat, I get hard faces now - but now every face is hard! From the GUI I use bpy.ops.mesh.faces_shade_{smooth, flat}() but the rendering looks the same both times, while Blender internal and Cycles produce the expected results. How can i access the normals these renderers use? – JonathanK Mar 3 '17 at 16:57
• if you want to learn a bit more how split normals work you can check out this file I wrote once: custom split normals utility Especially how you can toggle between using the blender internal smoothing vs. custom split normals – Madlaina Kalunder Mar 28 '17 at 7:41
• you probably have to set the polygons to actually use the split normals if you want to apply the split normals to the mesh: obj.data.use_auto_smooth = True i think i forgot to add this in my answer. I'm not sure if this will help you - also if you use split normals, face_shade_{smooth, flat} will not change anything for your rendering, because the smoothing information is overridden by the custom split normals (if use_auto_smooth is True). – Madlaina Kalunder Mar 28 '17 at 7:55

So it seems that Blender stores normals per vertex and per face and that I would have to check whether smooth or hard shading is enabled and based on that decide from where I take my normals. For hard shading I would need duplicated vertexes (so they can have different normals, depending what face they are for), and it seems, that I would have to do it on my own (also for flat surfaces vertexes could remain equal, in which case I most likely would want to join them again...).

But, when using the edge-split modifier, I get my doubled vertexes directly, and so my exporter works as expected without any modifications. For now, this simple solution works for me, however it sucks to require this additional modifier (added by the user!).

• you can calculate vertex-split-normals upon export. This calculates the per-vertex-normals for the shading you applied. It's a bit more complex then your code but you don't have to add an edge split modifier anymore. (unless you want to use smooth angle based) i can give you a code example later if you like – Madlaina Kalunder Jul 8 '16 at 5:16
• yes, I would be very interested to see, how this would be done properly. – JonathanK Jul 8 '16 at 11:49