pythonblendermeshnormalspyvista

Vertex normals look different in PyVista and Blender


I'm working with a mesh of a cave, and have manually set all the face normals to be 'correct' (all faces facing outside) using Blender (Edit mode-> choose faces -> flip normal). I also visualised the vertex normals in Blender, and they are all pointed outwards all through the surface:

Blender normals of cave scan

The mesh is then exported as an STL file.

Now, however, when I visualise the same thing in Pyvista with the following code:

import pyvista as pv
cave = pv.read("data/OC_wellsliced.stl")
cave.plot_normals()

The normals point in very different (below), and sometimes opposite directions. Any help in understanding this discrepancy would be greatly appreciated!

screenshot from pyvista showing normal vector plot with a bunch of them pointing inward

The OC_wellsliced.stl STL file is here.


Solution

  • The convenience functions for your case seem a bit too convenient.

    What plot_normals() does under the hood is that it accesses cave.point_normals, which in turn calls cave.compute_normals(). The default arguments to compute_normals() include consistent_normals=True, which according to the docs does

    Enforcement of consistent polygon ordering.

    There are some other parameters which hint at potential black magic going on when running this filter (e.g. auto_orient_normals and non_manifold_ordering, even though the defaults seem safe).

    So what seems to happen is that your mesh (which is non manifold, i.e. it has open edges) breaks the magic that compute_normals tries to do with the default "enforcement of polygon ordering". Since you already enforced the correct order in Blender, you can tell pyvista (well, VTK) to leave your polygons alone and just compute the normals as they are. This is not possible through plot_normals(), so you need a bit more work:

    import pyvista as pv
    
    # read data
    cave = pv.read("OC_wellsliced.stl")
    
    # compute normals
    # cave = cave.compute_normals()  # default (wrong) behaviour
    cave = cave.compute_normals(consistent_normals=False)  # correct behaviour
    
    # plot the normals manually; plot_normals() won't work
    plotter = pv.Plotter()
    plotter.add_mesh(cave, color='silver')
    plotter.add_mesh(cave.glyph(geom=pv.Arrow(), orient='Normals'), color='black')
    plotter.view_vector([-1, -1, 1])
    plotter.show()
    

    screenshot of fixed normals, all pointing outward

    You can uncomment the default call to compute_normals() to reproduce the original behaviour of plot_normals(). More importantly, you now have point and cell arrays called 'Normals' on your mesh that you can use for any kind of postprocessing. And these are guaranteed to be sane, because they are exactly what we plotted in the above figure.


    I now notice that you also said "in very different [...] directions"; I was focussing on the sign flips. Unfortunately it's very hard to see the normals in your Blender screenshot, so I can't tackle that. It's possible that point normals (as opposed to face normals) are computed differently between the two. Cell normals should be well-defined for flat polygonal cells.