# How to match two Normals direction?

I write a simple script.
Create two Cube and change vertice[0] a little ~
Then, match their Location and Normals direction.
But , There’s some sticky in Normal-match-part.
I’ve try many many times.
So. I really need your help.

Thanks for your Help~

``````

import bpy

import random
from math import degrees

def parallel(vct, p):
"""
vct : vector line
p : points
return : parallel vector with vct
"""
#tmp var in caculate
pt1 = p.copy()
for i in pt1:
i+=1
#new point in vct line
pt1 = pt1.project(vct)
pt2 = p.project(vct)
#offset
offset = pt2-p
pp = pt1-offset
#final
newVct = pp
print("
new point:%s" % (pp))
if newVct[0]==newVct[1]==newVct[2]==0:
print('error vector.zero')
return newVct

def MatchPoint0(context, ob1, ob2):
"""
static = ob1
dynamic = ob2
"""
vt1 = ob1.data.vertices[0]
vt2 = ob2.data.vertices[0]
print('Check location ::%s' % (vt1.co))

#location match
m1 = ob1.matrix_world.copy()
m2 = ob2.matrix_world.copy()
loc1 = m1 * vt1.co
loc2 = m2 * vt2.co
bpy.ops.object.select_all(action='DESELECT')
context.scene.objects.active = ob2
ob2.select = True
bpy.ops.transform.translate(value=loc1 - loc2)
context.scene.cursor_location = loc1
bpy.ops.object.origin_set(type='ORIGIN_CURSOR', center='MEDIAN')

#So, We can keep on caculate rotation match
#Caculate again cuz of ob2 is moved
m2 = ob2.matrix_world.copy()
n1 = m1 * vt1.normal.copy().normalized()
n2 = m2 * vt2.normal.copy().normalized()
angle = n1.angle(n2)
print(degrees(angle))
dot = n1.dot(n2)
print(dot)

###
### I'm Confusing in this section too much time, How to match there two Normals? Please ~
###
if dot&gt;0:
angle *= -1
cross = n1.cross(n2)
cross = parallel(cross, loc1)
bpy.ops.transform.rotate(value=angle, axis=cross)
return

def PrepareMesh(ob):
"""init"""
bpy.ops.object.editmode_toggle()
bpy.ops.mesh.select_all(action = 'DESELECT')
bpy.ops.object.editmode_toggle()
vt0 = ob.data.vertices[0]
vt0.select = True
for i in range(len(vt0.co)):
vt0.co[i] += random.uniform(0.1, 1)
ob.data.update()
ob.data.show_normal_vertex = True
print('ob Name: %s -vt0- ::
location: %s
normal: %s
' % (ob.name, vt0.co, vt0.normal))
return

def main(context):
print('

---&gt;&gt;&gt;')
# Creating and Preparing
bpy.ops.mesh.primitive_cube_add(view_align=True, location=(0, 0, 0), rotation=(10, 20, 30))
ob1 = context.active_object
PrepareMesh(ob1)
ob2 = bpy.ops.mesh.primitive_cube_add(view_align=True, location=(3, 0.5, 0), rotation=(40, 50, 60))
ob2 = context.active_object
PrepareMesh(ob2)

# Main working
# static = ob1
# dynamic = ob2
MatchPoint0(context, ob1, ob2)

class SimpleOperator(bpy.types.Operator):
"""Tooltip"""
bl_idname = "object.simple_operator"
bl_label = "Simple Object Operator"

def execute(self, context):
main(context)
return {'FINISHED'}

def register():
bpy.utils.register_class(SimpleOperator)

def unregister():
bpy.utils.unregister_class(SimpleOperator)

if __name__ == "__main__":
register()

# test call
bpy.ops.object.simple_operator()

``````

I think you need to remove the ‘parallel’ call. If you have the angle between 2 vectors then you just rotate around their cross product to align them. You might want to verify that “n1.dot(n2) != 1”. In that case they are the same vector and I think the cross product will give you something funny.

Noraml-Match-Part have been checked many many times.
(Line 39 ~ Line 59)

Still Need Help ～～

Thanks~

``````
import bpy

import random
from math import degrees, radians
from mathutils import Vector

def MergeMeshSoWeCanCheckTheNormals(context, ob1, ob2):
context.scene.objects.active = ob2
ob1.select = ob2.select = True
bpy.ops.object.join()
ob = context.active_object
bpy.ops.object.editmode_toggle()
return

def MatchPoint0(context, ob1, ob2):
"""
static = ob1
dynamic = ob2
"""
vt1 = ob1.data.vertices[0]
vt2 = ob2.data.vertices[0]

#location match
m1 = ob1.matrix_world.copy()
m2 = ob2.matrix_world.copy()
loc1 = m1 * vt1.co.copy()
loc2 = m2 * vt2.co.copy()
bpy.ops.object.select_all(action='DESELECT')
context.scene.objects.active = ob2
ob2.select = True
bpy.ops.transform.translate(value=loc1 - loc2)
context.scene.cursor_location = loc1
bpy.ops.object.origin_set(type='ORIGIN_CURSOR', center='MEDIAN')

#So, We can keep on caculate rotation match
#Caculate again cuz of ob2 is moved
m2 = ob2.matrix_world.copy()
n1 = m1 * vt1.normal.copy()
n2 = m2 * vt2.normal.copy()

angle = n1.angle(n2)
cross = n1.cross(n2)

print('before::
axis: %s
angle: %s
' % (cross, angle))
euler = m2.Rotation(angle, 4, cross).to_euler()
ob2.rotation_euler = euler
#bpy.ops.transform.rotate(value=angle, axis=cross)

bpy.ops.object.editmode_toggle()
bpy.ops.object.editmode_toggle()
m2 = ob2.matrix_world.copy()
n1 = m1 * vt1.normal.copy()
n2 = m2 * vt2.normal.copy()
angle = n1.angle(n2)
cross = n1.cross(n2)
print('after::
axis: %s
angle: %s
' % (cross, angle))
return

def PrepareMesh(ob):
"""init"""
bpy.ops.object.editmode_toggle()
bpy.ops.mesh.select_all(action = 'DESELECT')
bpy.ops.object.editmode_toggle()
vt0 = ob.data.vertices[0]
vt0.select = True
for i in range(len(vt0.co.copy())):
vt0.co[i] += random.uniform(0.1, 1)
ob.data.update()
ob.data.show_normal_vertex = True
print('ob Name: %s -vt0- ::
location: %s
normal: %s
' % (ob.name, vt0.co, vt0.normal))
return

def main(context):
print('

---&gt;&gt;&gt;')
# Creating and Preparing
bpy.ops.mesh.primitive_cube_add(view_align=True, location=(0, 0, 0), rotation=(10, 20, 30))
ob1 = context.active_object
PrepareMesh(ob1)
ob2 = bpy.ops.mesh.primitive_cube_add(view_align=True, location=(3, 0.5, 0), rotation=(40, 50, 60))
ob2 = context.active_object
PrepareMesh(ob2)

# Main working
# static = ob1
# dynamic = ob2
MatchPoint0(context, ob1, ob2)
MergeMeshSoWeCanCheckTheNormals(context, ob1, ob2)
# Goto 3dView check two normals

class SimpleOperator(bpy.types.Operator):
"""Tooltip"""
bl_idname = "object.simple_operator"
bl_label = "Simple Object Operator"

def execute(self, context):
main(context)
return {'FINISHED'}

def register():
bpy.utils.register_class(SimpleOperator)

def unregister():
bpy.utils.unregister_class(SimpleOperator)

if __name__ == "__main__":
register()

# test call
bpy.ops.object.simple_operator()

``````

I know how to match the elements in the same mesh.
Match the elments in the different that’s seems more difficult.

Help me ~ Please ~

``````
import bpy

import random
from math import degrees, radians
from mathutils import Vector

def MergeMeshSoWeCanCheckTheNormals(context, ob1, ob2):
context.scene.objects.active = ob2
ob1.select = ob2.select = True
bpy.ops.object.join()
ob = context.active_object
bpy.ops.object.editmode_toggle()
return

def MatchPoint0(context, ob1, ob2):
"""
static = ob1
dynamic = ob2
"""
vt1 = ob1.data.vertices[0]
vt2 = ob2.data.vertices[0]

#location match
m1 = ob1.matrix_world.copy()
m2 = ob2.matrix_world.copy()
loc1 = m1 * vt1.co.copy()
loc2 = m2 * vt2.co.copy()
bpy.ops.object.select_all(action='DESELECT')
context.scene.objects.active = ob2
ob2.select = True
bpy.ops.transform.translate(value=loc1 - loc2)
context.scene.cursor_location = loc1
bpy.ops.object.origin_set(type='ORIGIN_CURSOR', center='MEDIAN')

#So, We can keep on caculate rotation match
#Caculate again cuz of ob2 is moved
m2 = ob2.matrix_world.copy()
n1 = (m1 * vt1.normal.copy()).normalized()
print('v1  ::  lNor:%s gNor:%s' % (vt1.normal.copy(), n1))
n2 = (m2 * vt2.normal.copy()).normalized()
print('v2  ::  lNor:%s gNor:%s' % (vt2.normal.copy(), n2))

angle = n1.angle(n2)
cross = n1.cross(n2)
dot = n1.dot(n2)

print('before::
axis: %s
angle: %s
dot: %s
' % (cross, angle, dot))
if dot&gt;0:
angle *= -1
#euler = m2.Rotation(angle, 4, cross).to_euler()
#ob2.rotation_euler = euler
bpy.ops.transform.rotate(value=angle, axis=cross)

##############################################################
m2 = ob2.matrix_world.copy()
n1 = (m1 * vt1.normal.copy()).normalized()
n2 = (m2 * vt2.normal.copy()).normalized()
angle = n1.angle(n2)
cross = n1.cross(n2)
dot = n1.dot(n2)
print('before::
axis: %s
angle: %s
dot: %s' % (cross, angle, dot))
return

def PrepareMesh(ob):
"""init"""
bpy.ops.object.editmode_toggle()
bpy.ops.mesh.select_all(action = 'DESELECT')
bpy.ops.object.editmode_toggle()
vt0 = ob.data.vertices[0]
vt0.select = True
for i in range(len(vt0.co.copy())):
vt0.co[i] += random.uniform(0.1, 1)
ob.data.update()
ob.data.show_normal_vertex = True
print('ob Name: %s -vt0- ::
location: %s
normal: %s
' % (ob.name, vt0.co, vt0.normal))
return

def main(context):
print('

---&gt;&gt;&gt;')
# Creating and Preparing
bpy.ops.mesh.primitive_cube_add(view_align=True, location=(0, 0, 0), rotation=(10, 20, 30))
ob1 = context.active_object
PrepareMesh(ob1)
ob2 = bpy.ops.mesh.primitive_cube_add(view_align=True, location=(3, 0.5, 0), rotation=(40, 50, 60))
ob2 = context.active_object
PrepareMesh(ob2)

# Main working
# static = ob1
# dynamic = ob2
context.tool_settings.mesh_select_mode = (True, False, False)
MatchPoint0(context, ob1, ob2)
MergeMeshSoWeCanCheckTheNormals(context, ob1, ob2)
# Goto 3dView check two normals

class SimpleOperator(bpy.types.Operator):
"""Tooltip"""
bl_idname = "object.simple_operator"
bl_label = "Simple Object Operator"

def execute(self, context):
main(context)
return {'FINISHED'}

def register():
bpy.utils.register_class(SimpleOperator)

def unregister():
bpy.utils.unregister_class(SimpleOperator)

if __name__ == "__main__":
register()

# test call
bpy.ops.object.simple_operator()

``````

The problem is in how you transform the normals. The python API does not make an automatic distinction between position and normal vectors, but they need to be transformed differently by matrices.

For normals the translation component should be ignored (as normals are invariant under translations). That’s the cause of the mismatch here. Another issue happens when an object has non-uniform scale, in that case you need to use the inverse transpose.

Here’s the modified code that should work, note NormalTransformMatrix:

``````import bpy

import random
from math import degrees, radians
from mathutils import Vector

def MergeMeshSoWeCanCheckTheNormals(context, ob1, ob2):
context.scene.objects.active = ob2
ob1.select = ob2.select = True
bpy.ops.object.join()
ob = context.active_object
bpy.ops.object.editmode_toggle()
return

def NormalTransformMatrix(m):
m_normal = m.inverted().transposed()
m_normal[0][3] = 0.0
m_normal[1][3] = 0.0
m_normal[2][3] = 0.0

return m_normal

def MatchPoint0(context, ob1, ob2):
"""
static = ob1
dynamic = ob2
"""
vt1 = ob1.data.vertices[0]
vt2 = ob2.data.vertices[0]

#location match
m1 = ob1.matrix_world.copy()
m2 = ob2.matrix_world.copy()
loc1 = m1 * vt1.co.copy()
loc2 = m2 * vt2.co.copy()
bpy.ops.object.select_all(action='DESELECT')
context.scene.objects.active = ob2
ob2.select = True
bpy.ops.transform.translate(value=loc1 - loc2)
context.scene.cursor_location = loc1
bpy.ops.object.origin_set(type='ORIGIN_CURSOR', center='MEDIAN')

#So, We can keep on caculate rotation match
#Caculate again cuz of ob2 is moved
m1 = NormalTransformMatrix(ob1.matrix_world.copy())
m2 = NormalTransformMatrix(ob2.matrix_world.copy())
n1 = (m1 * vt1.normal.copy()).normalized()
print('v1  ::  lNor:%s gNor:%s' % (vt1.normal.copy(), n1))
n2 = (m2 * vt2.normal.copy()).normalized()
print('v2  ::  lNor:%s gNor:%s' % (vt2.normal.copy(), n2))

angle = n1.angle(n2)
cross = n1.cross(n2)
dot = n1.dot(n2)

print('before::
axis: %s
angle: %s
dot: %s
' % (cross, angle, dot))
if dot&gt;0:
angle *= -1
#euler = m2.Rotation(angle, 4, cross).to_euler()
#ob2.rotation_euler = euler
bpy.ops.transform.rotate(value=angle, axis=cross)

##############################################################
m1 = NormalTransformMatrix(ob1.matrix_world.copy())
m2 = NormalTransformMatrix(ob2.matrix_world.copy())
n1 = (m1 * vt1.normal.copy()).normalized()
n2 = (m2 * vt2.normal.copy()).normalized()
angle = n1.angle(n2)
cross = n1.cross(n2)
dot = n1.dot(n2)
print('before::
axis: %s
angle: %s
dot: %s' % (cross, angle, dot))
return

def PrepareMesh(ob):
"""init"""
bpy.ops.object.editmode_toggle()
bpy.ops.mesh.select_all(action = 'DESELECT')
bpy.ops.object.editmode_toggle()
vt0 = ob.data.vertices[0]
vt0.select = True
for i in range(len(vt0.co.copy())):
vt0.co[i] += random.uniform(0.1, 1)
ob.data.update()
ob.data.show_normal_vertex = True
print('ob Name: %s -vt0- ::
location: %s
normal: %s
' % (ob.name, vt0.co, vt0.normal))
return

def main(context):
print('

---&gt;&gt;&gt;')
# Creating and Preparing
bpy.ops.mesh.primitive_cube_add(view_align=True, location=(0, 0, 0), rotation=(10, 20, 30))
ob1 = context.active_object
PrepareMesh(ob1)
ob2 = bpy.ops.mesh.primitive_cube_add(view_align=True, location=(3, 0.5, 0), rotation=(40, 50, 60))
ob2 = context.active_object
PrepareMesh(ob2)

# Main working
# static = ob1
# dynamic = ob2
context.tool_settings.mesh_select_mode = (True, False, False)
MatchPoint0(context, ob1, ob2)
MergeMeshSoWeCanCheckTheNormals(context, ob1, ob2)
# Goto 3dView check two normals

class SimpleOperator(bpy.types.Operator):
"""Tooltip"""
bl_idname = "object.simple_operator"
bl_label = "Simple Object Operator"

def execute(self, context):
main(context)
return {'FINISHED'}

def register():
bpy.utils.register_class(SimpleOperator)

def unregister():
bpy.utils.unregister_class(SimpleOperator)

if __name__ == "__main__":
register()

# test call
bpy.ops.object.simple_operator()

``````

By the way, most of the .copy() calls in the code are not needed, though they don’t hurt.

Dear brecht.

Thank you very much!!!