I do not want to use Blender's built-in functions for computing centroids as explained here as it seems that they do not give me the kind of centroid I expect to get. I want to compute a new centroid for my meshes given the following description. First, I want to compute the centers of faces (triangle) of a mesh centroid of a mesh. Then I need to compute the faces area. The new centroid is the average of the mesh faces' centers, weighted by their area. How can I do this using Blender's Python API?
-
I've assumed by without using Blender built in functions you mean without using the transform origin operators. – batFINGER Feb 22 '18 at 16:34
3 Answers
Bmesh approach
The bmesh module has for faces the methods to calculate centre and area.
There are three methods to calculate the centre of the face.
calc_center_bounds()Return bounds center of the face. Returns: a 3D vector. Return type: mathutils.Vector
calc_center_median()Return median center of the face. Returns: a 3D vector. Return type: mathutils.Vector
calc_center_median_weighted()Return median center of the face weighted by edge lengths. Returns: a 3D vector. Return type: mathutils.Vector
I've used calc_center_median() for $x_i$ and BMFace.calc_area() for the weight $w_i$ for each face of index $i$ the weighted mean is
$$\tilde x = \frac {w_0x_0 + w_1x_1 + ... + w_nx_n}{w_0 + w_1 + ... + w_n}$$
import bpy
import bmesh
from mathutils import Vector
context = bpy.context
ob = context.object
me = ob.data
bm = bmesh.new()
bm.from_mesh(me)
weights = [f.calc_area() for f in bm.faces]
weighted_centres = [f.calc_area() * f.calc_center_median()
for f in bm.faces]
local_weighted_mean = sum(weighted_centres, Vector()) / sum(weights)
print(local_weighted_mean)
# set as new origin
for v in bm.verts:
v.co -= local_weighted_mean
mw = ob.matrix_world
mw.translation = mw * local_weighted_mean
bm.to_mesh(me)
me.update()
Result of running on newly added Suzanne. Scene cursor shows old origin.
Note: if modifiers are used on the object to triangulate, or subdivide, can (for example) use
bm.from_object(ob, context.scene, render=True)
to calculate weighted origin of modified mesh.
- 84,216
- 10
- 108
- 233
You can do that with some simple math
import bpy
import mathutils
def unit_normal(vert1, vert2, vert3):
determinant = mathutils.Vector((
mathutils.Matrix((
(1.0, vert1[1], vert1[2]),
(1.0, vert2[1], vert2[2]),
(1.0, vert3[1], vert3[2]))).determinant(),
mathutils.Matrix((
(vert1[0], 1.0, vert1[2]),
(vert2[0], 1.0, vert2[2]),
(vert3[0], 1.0, vert3[2]))).determinant(),
mathutils.Matrix((
(vert1[0], vert1[1], 1.0),
(vert2[0], vert2[1], 1.0),
(vert3[0], vert3[1], 1.0))).determinant()))
return determinant.normalized()
def calc_area_of_polygon(mesh, polygon):
total = mathutils.Vector((0.0,0.0,0.0))
last_vertex = mathutils.Vector(mesh.vertices[polygon.vertices[-1]].co)
for vertex in polygon.vertices:
this_vertex = mathutils.Vector(mesh.vertices[vertex].co)
total += this_vertex.cross(last_vertex)
last_vertex = this_vertex
result = total.dot(unit_normal(
mesh.vertices[polygon.vertices[0]].co,
mesh.vertices[polygon.vertices[1]].co,
mesh.vertices[polygon.vertices[2]].co,
))
return abs(result/2)
def calc_center_of_polygon(mesh: bpy.types.Mesh, polygon: bpy.types.MeshPolygon):
result = mathutils.Vector()
for vertex in polygon.vertices:
result += mathutils.Vector(mesh.vertices[vertex].co)
return result / len(polygon.vertices)
def calc_center(mesh: bpy.types.Mesh):
total_weight = 0.0
total_center = mathutils.Vector()
for polygon in mesh.polygons:
weight = calc_area_of_polygon(mesh, polygon)
total_weight += weight
total_center += weight * calc_center_of_polygon(mesh, polygon)
return total_center / total_weight
print(calc_center(bpy.data.meshes['Cube']))
-
-
I reviewed your changes. But please be aware that you need to use the
bpy.types.Meshobject like in the last line of the example. In your review you wanted to use it by usingbpy.types.Objectwhich is not my intention so I rejected it. Sorry if my try gave some bad results. – J. Bakker Feb 27 '18 at 07:23 -
Oops, apologies on my part for the se approval, thought it was mostly Re: vertex vs vertice "crime" didn't get as far as to see the
mesh.dataedit. Will check more carefully in future. – batFINGER Feb 28 '18 at 10:29 -
Update for newer versions of blender (I'm running 3.6)
@batFINGER 's answer worked for me but I had to switch:
mw.translation = mw * local_weighted_mean
with:
mw.translation = mw @ local_weighted_mean
due to an error "Element-wise multiplication: not supported between 'Matrix' and 'Vector' types"
- 1