Geometry exported from NURBS-based modeling applications relies on custom vertex normals for accurate shading. Blender does not support this, it uses averaged normals that are constantly recalculated with editing operations.
However, it is possible to modify the normals through Python and these modifications should be retained as long as the mesh isn’t edited. Therefore, I have modified the OBJ importer to take the vertex normals into account:
Index: import_obj.py
===================================================================
--- import_obj.py (revision 4618)
+++ import_obj.py (working copy)
@@ -22,7 +22,9 @@
# Contributors: Campbell Barton, Jiri Hnidek, Paolo Ciccone
"""
-This script imports a Wavefront OBJ files to Blender.
+This script imports a Wavefront OBJ files to Blender. This is a modified version that will
+import vertex normals. Caution: The imported normals will be overwritten when entering
+Edit Mode.
Usage:
Run this script from "File->Import" menu and then load the desired OBJ file.
@@ -383,17 +385,17 @@
mtl.close()
-def split_mesh(verts_loc, faces, unique_materials, filepath, SPLIT_OB_OR_GROUP):
+def split_mesh(verts_loc,verts_nor,faces, unique_materials, filepath, SPLIT_OB_OR_GROUP):
"""
Takes vert_loc and faces, and separates into multiple sets of
- (verts_loc, faces, unique_materials, dataname)
+ (verts_loc, verts_nor, faces, unique_materials, dataname)
"""
filename = os.path.splitext((os.path.basename(filepath)))[0]
if not SPLIT_OB_OR_GROUP:
# use the filename for the object name since we arnt chopping up the mesh.
- return [(verts_loc, faces, unique_materials, filename)]
+ return [(verts_loc, verts_nor, faces, unique_materials, filename)]
def key_to_name(key):
# if the key is a tuple, join it to make a string
@@ -413,19 +415,21 @@
if oldkey != key:
# Check the key has changed.
try:
- verts_split, faces_split, unique_materials_split, vert_remap = face_split_dict[key]
+ verts_split, nors_split,faces_split, unique_materials_split, vert_remap = face_split_dict[key]
except KeyError:
faces_split = []
verts_split = []
+ nors_split=[]
unique_materials_split = {}
vert_remap = {}
- face_split_dict[key] = (verts_split, faces_split, unique_materials_split, vert_remap)
+ face_split_dict[key] = (verts_split,nors_split,faces_split, unique_materials_split, vert_remap)
oldkey = key
face_vert_loc_indices = face[0]
+ use_nor = len(verts_nor)==len(verts_loc)
# Remap verts to new vert list and add where needed
for enum, i in enumerate(face_vert_loc_indices):
map_index = vert_remap.get(i)
@@ -433,7 +437,8 @@
map_index = len(verts_split)
vert_remap<i> = map_index # set the new remapped index so we only add once and can reference next time.
verts_split.append(verts_loc[i]) # add the vert to the local verts
-
+ if use_nor:
+ nors_split.append(verts_nor[i])
face_vert_loc_indices[enum] = map_index # remap to the local index
matname = face[2]
@@ -443,7 +448,9 @@
faces_split.append(face)
# remove one of the itemas and reorder
- return [(value[0], value[1], value[2], key_to_name(key)) for key, value in list(face_split_dict.items())]
+ return [(verts_split, nors_split, faces_split, unique_materials_split,key_to_name(key)) \
+ for key, (verts_split,nors_split,faces_split,unique_materials_split,vert_remap) \
+ in list(face_split_dict.items())]
def create_mesh(new_objects,
@@ -451,6 +458,7 @@
use_ngons,
use_edges,
verts_loc,
+ verts_nor,
verts_tex,
faces,
unique_materials,
@@ -589,6 +597,7 @@
# verts_loc is a list of (x, y, z) tuples
me.vertices.foreach_set("co", unpack_list(verts_loc))
+
# faces is a list of (vert_indices, texco_indices, ...) tuples
# XXX faces should contain either 3 or 4 verts
@@ -671,6 +680,7 @@
me.edges.foreach_set("vertices", unpack_list(edges))
# me_edges.extend( edges )
+
# del me_edges
# Add edge faces.
@@ -702,6 +712,10 @@
mesh_untessellate(me, fgon_edges)
+ if verts_nor:
+ if len(verts_nor)==len(verts_loc):
+ me.vertices.foreach_set("normal", unpack_list(verts_nor))
+
# XXX slow
# if unique_smooth_groups and sharp_edges:
# for sharp_edge in sharp_edges.keys():
@@ -862,6 +876,7 @@
time_main = time.time()
verts_loc = []
+ verts_nor = []
verts_tex = []
faces = [] # tuples of the faces
material_libs = [] # filanems to material libs this uses
@@ -913,7 +928,7 @@
verts_loc.append((float_func(line_split[1]), float_func(line_split[2]), float_func(line_split[3])))
elif line_start == b'vn':
- pass
+ verts_nor.append((float_func(line_split[1]), float_func(line_split[2]), float_func(line_split[3])))
elif line_start == b'vt':
verts_tex.append((float_func(line_split[1]), float_func(line_split[2])))
@@ -1125,13 +1140,14 @@
else:
SPLIT_OB_OR_GROUP = False
- for verts_loc_split, faces_split, unique_materials_split, dataname in split_mesh(verts_loc, faces, unique_materials, filepath, SPLIT_OB_OR_GROUP):
+ for verts_loc_split, verts_nor_split, faces_split, unique_materials_split, dataname in split_mesh(verts_loc,verts_nor,faces, unique_materials, filepath, SPLIT_OB_OR_GROUP):
# Create meshes from the data, warning 'vertex_groups' wont support splitting
create_mesh(new_objects,
has_ngons,
use_ngons,
use_edges,
verts_loc_split,
+ verts_nor_split,
verts_tex,
faces_split,
unique_materials_split,
Averaged Normals, showing artifacts:
Custom Normals, using the modified importer:
Usage:
You can download the modified importer script here. You [I]must overwrite the existing import_obj.py file in the Blender script directory:
(Assuming version 2.67)
<Blender Path>/2.67/scripts/addons/io_scene_obj
Simply import an OBJ with vertex normals, as usual. For the custom normals to be visible, the mesh must be in “Smooth” mode. Do not enter Edit Mode with this mesh, or the normals will be lost! You can use the example file shown above (created in MoI) for testing.
Warning:
This experimental modification of the OBJ is largely untested and comes with no warranties, whatsoever. It is almost guaranteed to set your dog on fire and kill your house - use it at your own risk! If you find any bugs, you can report them here and I might consider fixing them.
Update (July 4th):
I fixed the obvious issue where import fails with a mesh that doesn’t define vertex normals (I’m not kidding when I say this is largely untested).
Also: It is legal for OBJ files to define faces that use vertex normals which do not match the vertex coordinates by index. Supporting this would make things a bit more complicated, so unless someone actually needs this, I won’t bother. Such files will currently fail to import normals, silently.
Update (September 26th):
Maccesh posted a improved version of the script. Thanks!