6
$\begingroup$

If i get a normal of a vertex - i'll get it in local coordinates. For example:

bpy.context.object.data.vertices[0].normal

If an object will have rotation or scale - normal direction will be incorrect according to world orientation. How to convert the vertex normal according to the world?

$\endgroup$
6
$\begingroup$

Actually it is, when the scaling factors are not the same (as @mifth pointed out) :

normal_local = C.object.data.vertices[0].normal.to_4d()
normal_local.w = 0
normal_local = (C.object.matrix_world @ normal_local).to_3d()

If you know they are all the same, you can use :

C.object.rotation_euler.to_matrix() @ C.object.data.vertices[0].normal

Cheers,

$\endgroup$
7
  • $\begingroup$ yes, that's corect. the only problem - if object is scaled non-proportionaly (like (0.1, 0.5, 0.7)) it will make incorrect normal direction too. $\endgroup$
    – mifth
    Jul 3 '15 at 8:13
  • $\begingroup$ It takes rotation but it does not take scale now. Possibly there should be adifferent solution. :( Sorry. $\endgroup$
    – mifth
    Jul 6 '15 at 20:28
  • $\begingroup$ The solution above gives the normal the correct direction. I don't understand how you want the scale to be taken into account for the normal. Maybe you can scale the normal by the mean of the 3 scale factors, would that be what you want ? $\endgroup$ Jul 8 '15 at 5:07
  • $\begingroup$ Sorry for late response. Here is what i want to say i.imgur.com/WPpUp5B.png If scale will be different - normals will have different direction. $\endgroup$
    – mifth
    Jul 14 '15 at 11:35
  • $\begingroup$ You're right, I wasn't thinking about this case. I updated the answer, it should suit your needs :) $\endgroup$ Jul 15 '15 at 9:23
10
$\begingroup$

Normal Matrix.

EDIT. Original answer did not allow for non-uniform scale. Have added a script to reinforce the answer of @patmo141_

Create a normal matrix

$$n' = (M^{-1})^{T} \cdot n $$

as in script below

mw = ob.matrix_world
N = mw.inverted_safe().transposed().to_3x3()

enter image description here

Test script, adds a single arrow type empty at each face normal aligned with face normals in global space.

import bpy
from mathutils import Matrix

norm_length = 2

context = bpy.context
bpy.data.batch_remove((o for o in context.scene.objects if o.type == 'EMPTY'))

ob = context.object
mw = ob.matrix_world
N = mw.inverted_safe().transposed().to_3x3()

for f in ob.data.polygons:
    n = N @ f.normal
    mt = bpy.data.objects.new("n{f.index}", None)
    mt.location = mw @ f.center
    mt.rotation_euler =  n.to_track_quat().to_euler()
    mt.empty_display_type = 'SINGLE_ARROW'
    mt.empty_display_size = norm_length
    context.collection.objects.link(mt)

Note, using face normals as example, for vertex normals as per question

for v in ob.data.vertices:
    n = N @ v.normal
    mt = bpy.data.objects.new("n{f.index}", None)
    mt.location = mw @ v.co

Without inverting.

Using the technique outlined in Stop Using Normal Matrix

\begin{align*} \vec{N'}&=\frac{N_0}{a}\vec{X} + \frac{N_1}{b}\vec{Y} + \frac{N_2}{c}\vec{Z}\\ &=(\frac{N_0}{a}, \frac{N_1}{b}, \frac{N_2}{c})M \end{align*}

Test script,

  • Get the scale vector vector s
  • Normalize the rotation part Matrix M
  • There's an issue where negative scale flipping result
  • make a vector n from normal by dividing each component by scale component
  • post multiply by M to obtain result normal

edit to above: (remembering to import Vector from mathutils)

M = mw.to_3x3().normalized() 
s = mw.to_scale()
for f in ob.data.polygons:
    n = f.normal
    n = Vector((n.x / s.x, n.y / s.y, n.z / s.z)) 
    
    n = n @ M

Zero scale component.

Both these methods will have issues when any scale component is zero. The use of inverted_safe will avert risk of divide by zero error at cost of result accuracy. Will look into this.

From the bmesh directly.

As noted in answer to [link] could also apply the transform to a bmesh and update its normals. Tnen the face normals of the bmesh the calculated normals. (The mesh is not updated or written back to)

enter image description here Empties added at world coords to mimic normals of evaluated mesh

import bpy
import bmesh
from bpy import context
norm_length = 2

bpy.ops.object.mode_set()
bpy.data.batch_remove((o for o in context.scene.objects if o.type == 'EMPTY'))

ob = context.object
dg = context.evaluated_depsgraph_get()

bm = bmesh.new()
bm.from_object(ob, dg)
bm.transform(ob.matrix_world)
bm.normal_update()
for f in bm.faces:
    n = f.normal
    mt = bpy.data.objects.new("n{f.index}", None)
    mt.location = f.calc_center_median()
    mt.rotation_euler =  n.to_track_quat().to_euler()
    mt.empty_display_type = 'SINGLE_ARROW'
    mt.empty_display_size = norm_length
    context.collection.objects.link(mt)
    
bpy.ops.object.mode_set(mode='EDIT') 
$\endgroup$
5
  • $\begingroup$ Do you think we should already answer Python questions assuming Blender 2.80? It is still not released, would it not make sense to assume people do not yet use it for work? $\endgroup$ Jan 10 '19 at 12:00
  • $\begingroup$ It's quite ad hoc This one came up from another question, needed an edit, so I updated for 2.8, rather than wait for a more sensible moment in time $\endgroup$
    – batFINGER
    Jan 10 '19 at 12:09
  • $\begingroup$ I suppose that makes sense. I was just wondering how to approach this when answering other questions. I'll probably start including comments for changes in 2.80 with answers for 2.79. $\endgroup$ Jan 10 '19 at 12:25
  • $\begingroup$ ... still trying to get my head round what the transpose is doing.... :( $\endgroup$ Jul 31 '21 at 9:27
  • $\begingroup$ @RobinBetts ditto link from "don't invert" link in answer lighthouse3d.com/tutorials/glsl-12-tutorial/the-normal-matrix Any ideas re dealing with normal flipping with negative scales? $\endgroup$
    – batFINGER
    Aug 16 '21 at 11:57
5
$\begingroup$

I've run into this problem many times, I always have to search for the answer. But I use the tranpose of the inverse of the world_matrix to get the world normal.

https://computergraphics.stackexchange.com/questions/1502/why-is-the-transposed-inverse-of-the-model-view-matrix-used-to-transform-the-nor

mx_inv = C.object.matrix_world.inverted()
mx_norm = mx_inv.transposed().to_3x3()

world_no = mx_norm @ C.object.data.vertices[0].normal
$\endgroup$
0
4
$\begingroup$

you have to multiply it with the world matrix (the order matters! Matrix first) :

C.object.matrix_world @ C.object.data.vertices[0].normal

$\endgroup$
2
  • $\begingroup$ Shouldn't the matrix be first in the multiplication? $\endgroup$
    – nantille
    Sep 25 '18 at 9:46
  • $\begingroup$ @natille not sure how the math goes here, you can edit the post if you think it's wrong. $\endgroup$
    – Chebhou
    Sep 25 '18 at 16:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.