# Transform Vertices by Matrix around Custom Point

I need to scale the vertices to 0 around the median point as here. Should get something like this

I use Mathutils and multiply the Matrix.Scale(as Axis I take the average of all vertex normal) by Vertex Coordinates. But then I get something like this

I don't understand how to scale Matrix by my Origin. For Origin, use the average coordinates of the selected vertices

import bpy
import bmesh
import mathutils

obj = bpy.context.edit_object
me = obj.data
bm = bmesh.from_edit_mesh(me)

verts_sel = [v for v in bm.verts if v.select is True]
verts_co = [v.co for v in verts_sel]
verts_nor = [v.normal for v in verts_sel]

axis = mathutils.Vector(sum(verts_nor, mathutils.Vector()) / len(verts_sel))
axis = axis.normalized()

origin = mathutils.Matrix.Translation(sum(verts_co, mathutils.Vector()) / len(verts_sel))
mat_sca = mathutils.Matrix.Scale(0.0, 4, axis)

mat_out = origin * mat_sca  # combine transformations

for v in verts_sel:
vert = mathutils.Vector(v.co)
v.co = vert * mat_out

bmesh.update_edit_mesh(me, True)


Define plane and project onto it.

An alternative approach is defining the plane at the average location of selected vertices, with normal calculated from the sum of vertex normals, then normalized.

Using the normal and a point on the plane can project any vector onto that plane.

From this anwer on Maths Stack Exchange

If $$A$$,$$B$$,$$C$$ are not on the same line, then $$\vec{AB}\times > \vec{BC}$$ will give you the direction of your reference plane normal vector $$\hat{n}_1$$. I think you should know how to do the normalization so that $$|\hat{n}_1|=1$$

Then the projection of $$\vec{BD}$$ on the reference plane is $$\vec{BD}-(\vec{BD}\cdot \hat{n}_1)\hat{n}_1$$

Have used mathutils.geometry.distance_point_to_plane(pt, plane_co, plane_no) to calculate the projection,

import bpy
import bmesh
from mathutils import Vector
from mathutils.geometry import distance_point_to_plane

context = bpy.context
ob = context.edit_object
me = ob.data
bm = bmesh.from_edit_mesh(me)
bm.normal_update() #  maybe unneccesary if no prior ops.
verts_sel = [v for v in bm.verts if v.select is True]
n = len(verts_sel)
verts_co = [v.co for v in verts_sel]
verts_no = [v.normal for v in verts_sel]

plane_co = sum(verts_co, Vector()) / n
plane_no = sum(verts_no, Vector()).normalized()

for v in verts_sel:
d = distance_point_to_plane(v.co, plane_co, plane_no)
v.co -= d * plane_no

bmesh.update_edit_mesh(me)


Rather than using the mathutils routine this is the equivalent of

for v in verts_sel:
v.co -= (v.co - plane_co).dot(plane_no) * plane_no


where $$\vec{BD}$$ is v.co - plane_co

Alternatively using matrices.

bmesh.ops.transform() lets us define both a transform matrix and a space matrix. In this case we need the calculated median point to be the origin of our new space, ie a simple translation matrix. For the transform matrix will use the scale matrix from question.

T = Matrix.Translation(-plane_co)
S = Matrix.Scale(0, 4, plane_no)
bmesh.ops.transform(bm, verts=verts_sel, matrix=S, space=T)

• Thank you very much! Which method do you think is better? – Mansur Oct 22 '18 at 8:17
• Added a bit re the theory. I think the problem by nature is a projection onto a plane. Which is equiv of scaling to zero on axis. v.co -= mat_scale * (v.co - plane_co) Have a hunch this one may be quicker with only a dot product and a couple of vector sums, vs two matrix multiplications per vert. Best way is to time it I suppose. Good question re demonstrating creating and using the scale matrix, even if I didn't use it lol. – batFINGER Oct 22 '18 at 12:27

Actually there are two steps, (1) scaling the vectors w.r.t to normalized normal, then (2) translating them w.r.t to normalized vector, so you have to do it separately.

You are doing correctly and only one step is remaining. After the transformation of vectors (vert * mat_out), you have to translate them w.r.t to the origin.

import bpy
import bmesh
import mathutils

obj = bpy.context.edit_object
me = obj.data
bm = bmesh.from_edit_mesh(me)

verts_sel = [v for v in bm.verts if v.select is True]
verts_co = [v.co for v in verts_sel]
verts_nor = [v.normal for v in verts_sel]

axis = mathutils.Vector(sum(verts_nor, mathutils.Vector()) / len(verts_sel))
axis = axis.normalized()

origin = mathutils.Matrix.Translation(sum(verts_co, mathutils.Vector()) / len(verts_sel))
mat_sca = mathutils.Matrix.Scale(0.0, 4, axis)

mat_out = mat_sca  # Do not combine transformations

for v in verts_sel:
vert = mathutils.Vector(v.co)
# you have translate the "transferred vectors (vert * mat_out)" w.r.t origin
v.co = origin * (vert * mat_out)

bmesh.update_edit_mesh(me, True)


But you can also achieve same result without script,

• Thank you very much! About the second way I of course know, I do addon for additional functions of sculpting. Can you give any information about working with matrices for training? – Mansur Oct 22 '18 at 5:13
• There is a problem if the surface is offset from the center(just movesphere to up and apply pivot to center) then your algorithm behaves incorrectly – Mansur Oct 22 '18 at 6:47
• @Mansur I am not expert in python. But there are very good tutorials on YouTube by CGCookie and Blender Campus for blender scripting. – 3DSinghVFX Oct 22 '18 at 17:02