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:
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!
The OC_wellsliced.stl
STL file is here.
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()
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.