OBJ import normals


I have created an obj file with a tool I wrote on my own. The obj is very simple: Some vertices, some normals and the connectivity (triangles).

I noticed that the normals somehow didn’t seem to get imported, then I went on to check the plugin and found this:

elif line.startswith('vn '):

So it completely ignores normals. Is this intentional or can this be fixed somehow? Does blender 2.5 have an importer with normal support? I really need my own normals.

I wrote a LWO importer (for 2.4x) that imports normals. I used it to get the true normals from MOI, a nurbs modeler. It worked but the only problem is that those true normals are lost and replaced with averaged normals if
1, you enter edit mode, or
2, you hit render.

It only was good for viewing the imported normals in the viewport or exporting them to an external renderer.

So, unless 2.5 changed normal handling, you are wasting your time.

Would be a crucial feature to have in blender to keep those normals untouched! We work alot with convertet CAD-Data and similar stuff and I allways end with nasty smoothing problems which we had to remove in postproduction. A big problem if you work on chrome parts from car interiors.

Yes it’s sad that normals are recalculated on import.

Blender’s UV unwrap tool are quite good but cant be used on models with pre-assigned vertex normals!


I wrote a patch that fixes this problem:

Hi Scorpius, thank you for your work on this issue; I tried to import geometry from MoI and quickly found out the importance of vertex normals.
One problem I see with your approach, though, is that it depends on you making new builds, which are quickly overceeded by Blender development.
Would it be possible to make this an add-on that could then be integrated also in future “standard” builds?
Thanks in advance!

I sent my patch to a few people for possible inclusion, but I heard nothing so far.

Too bad!

Did you try to post a patch here?


Hmm, sorry - I did gave up, I’m not made for coding and such things I guess. Anyway here is scorpius code (is it ok to post it?):

Index: release/scripts/ui/space_userpref.py
--- release/scripts/ui/space_userpref.py    (revision 33321)
+++ release/scripts/ui/space_userpref.py    (working copy)
@@ -376,6 +376,7 @@
         col.prop(system, "author", text="Author")
         col.prop(system, "use_scripts_auto_execute")
         col.prop(system, "use_tabs_as_spaces")
+        col.prop(system, "user_preserve_normals")
Index: source/blender/render/intern/source/convertblender.c
--- source/blender/render/intern/source/convertblender.c    (revision 33321)
+++ source/blender/render/intern/source/convertblender.c    (working copy)
@@ -61,6 +61,7 @@
 #include "DNA_particle_types.h"
 #include "DNA_scene_types.h"
 #include "DNA_texture_types.h"
+#include "DNA_userdef_types.h"
 #include "DNA_view3d_types.h"
 #include "BKE_anim.h"
@@ -3155,7 +3156,7 @@
     MSticky *ms = NULL;
     DerivedMesh *dm;
     CustomDataMask mask;
-    float xn, yn, zn,  imat[3][3], mat[4][4];  //nor[3],
+    float xn, yn, zn,  imat[3][3], mat[4][4], nmat[4][4]; //nor[3],
     float *orco=0;
     int need_orco=0, need_stress=0, need_nmap_tangent=0, need_tangent=0;
     int a, a1, ok, vertofs;
@@ -3167,6 +3168,10 @@
     mul_m4_m4m4(mat, ob->obmat, re->viewmat);
     invert_m4_m4(ob->imat, mat);
     copy_m3_m4(imat, ob->imat);
+    if(U.flag & USER_PRESERVE_NORMALS) {
+        copy_m4_m4(nmat, ob->imat);
+        transpose_m4(nmat);
+    }
@@ -3246,6 +3251,10 @@
         for(a=0; a<totvert; a++, mvert++) {
             ver= RE_findOrAddVert(obr, obr->totvert++);
             VECCOPY(ver->co, mvert->co);
+            if(U.flag & USER_PRESERVE_NORMALS) {
+                VECCOPY(ver->n, mvert->no);
+                mul_m4_v3(nmat, ver->n);
+            }
             if(do_autosmooth==0)    /* autosmooth on original unrotated data to prevent differences between frames */
                 mul_m4_v3(mat, ver->co);
@@ -3425,7 +3434,8 @@
             autosmooth(re, obr, mat, me->smoothresh);
-        calc_vertexnormals(re, obr, need_tangent, need_nmap_tangent);
+        if(!(U.flag & USER_PRESERVE_NORMALS))
+            calc_vertexnormals(re, obr, need_tangent, need_nmap_tangent); 
             calc_edge_stress(re, obr, me);
Index: source/blender/makesdna/DNA_userdef_types.h
--- source/blender/makesdna/DNA_userdef_types.h    (revision 33321)
+++ source/blender/makesdna/DNA_userdef_types.h    (working copy)
@@ -426,6 +426,7 @@
 #define USER_NONEGFRAMES        (1 << 24)
 #define USER_TOOLTIPS_PYTHON    (1 << 26)
+#define USER_PRESERVE_NORMALS    (1 << 27)
 /* helper macro for checking frame clamping */
 #define FRAMENUMBER_MIN_CLAMP(cfra) \
Index: source/blender/makesrna/intern/rna_userdef.c
--- source/blender/makesrna/intern/rna_userdef.c    (revision 33321)
+++ source/blender/makesrna/intern/rna_userdef.c    (working copy)
@@ -2478,6 +2478,10 @@
     RNA_def_property_boolean_negative_sdna(prop, NULL, "flag", USER_TXT_TABSTOSPACES_DISABLE);
     RNA_def_property_ui_text(prop, "Tabs as Spaces", "Automatically converts all new tabs into spaces for new and loaded text files");
+    prop= RNA_def_property(srna, "user_preserve_normals", PROP_BOOLEAN, PROP_NONE);
+    RNA_def_property_boolean_sdna(prop, NULL, "flag", USER_PRESERVE_NORMALS);
+    RNA_def_property_ui_text(prop, "Preserve Imported Normals", "Do not recalculate normals when rendering or editing");
     prop= RNA_def_property(srna, "prefetch_frames", PROP_INT, PROP_NONE);
     RNA_def_property_int_sdna(prop, NULL, "prefetchframes");
     RNA_def_property_range(prop, 0, 500);
Index: source/blender/editors/mesh/editmesh.c
--- source/blender/editors/mesh/editmesh.c    (revision 33321)
+++ source/blender/editors/mesh/editmesh.c    (working copy)
@@ -639,9 +639,10 @@
     /* later on: added flags for 'cylinder' and 'sphere' intersection tests in old
        game engine (2.04)
+    if(!(U.flag & USER_PRESERVE_NORMALS))
+        recalc_editnormals(em);
-    recalc_editnormals(em);
     /* init */
     eve= em->verts.first;
     while(eve) {
@@ -1307,7 +1308,8 @@
                 base->object->recalc |= OB_RECALC_DATA;
-    mesh_calc_normals(me->mvert, me->totvert, me->mface, me->totface, NULL);
+    if(!(U.flag & USER_PRESERVE_NORMALS))
+        mesh_calc_normals(me->mvert, me->totvert, me->mface, me->totface, NULL); 
     /* topology could be changed, ensure mdisps are ok */

The above posted patch won’t apply cleanly anymore, so I updated it for today’s latest SVN release (Revision: 36575).

Here’s the new patch: true-normals-blender-2.57.patch
And an untested Mac build: true-normals-blender-2.57-OSX.zip

Thanks for the OSX build scorpius!

Hi scorpius,

it is an so important patch!!!

This is an must have for Blender.

As Carrozza said before.

Please post your patch to the tracker.

Many thanks for your work.


I looked through the code, and your patch seems to allow the user to prevent Blender from recalculating the vertex normals. But my understanding is that the Blender OBJ importer does not read in the vertex normals at all, so is there a fix for that as well? Or am I misunderstanding something?

BTW, thanks for all of your hard work. I was actually searching the forums to see if I could start researching what would be required to read the vertex normals into Blender and I found this thread. Great job!

Thanks. I’m not sure about the OBJ importer. I originally wrote this patch for use with MoI, which exports to LWO with true-normals. I also wrote a corresponding LWO script (for 2.49) that imports those normals. But any script that imports normals will work.

I also use MoI.

Is there a 2.5x LWO script available that will import vertex normals?

I haven’t finished the 2.5 version yet.

Thanks, I look forward to trying it out.

At the same time, I might try to see if I can get the OBJ import to save vertex normals as well. But I have never worked on the Blender code before, so it might take me a while to figure things out.

Thanks guys, MoI people count on you! :yes: