2
$\begingroup$

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?

$\endgroup$
1
  • $\begingroup$ I've assumed by without using Blender built in functions you mean without using the transform origin operators. $\endgroup$
    – batFINGER
    Feb 22, 2018 at 16:34

2 Answers 2

5
$\begingroup$

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()

enter image description here 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.

$\endgroup$
1
$\begingroup$

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']))
$\endgroup$
4
  • $\begingroup$ Your code actually has some bugs. It gives very incorrect numbers. $\endgroup$
    – Amir
    Feb 27, 2018 at 6:40
  • $\begingroup$ I reviewed your changes. But please be aware that you need to use the bpy.types.Mesh object like in the last line of the example. In your review you wanted to use it by using bpy.types.Object which is not my intention so I rejected it. Sorry if my try gave some bad results. $\endgroup$
    – J. Bakker
    Feb 27, 2018 at 7:23
  • $\begingroup$ 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.data edit. Will check more carefully in future. $\endgroup$
    – batFINGER
    Feb 28, 2018 at 10:29
  • $\begingroup$ @batFINGER no problem. will update the vertex stuff :-) $\endgroup$
    – J. Bakker
    Feb 28, 2018 at 10:38

This site is temporarily in read-only mode and not accepting new answers.

Not the answer you're looking for? Browse other questions tagged .