3
$\begingroup$

I know how to display face area using 3d view port. However, I could not find a way to return the value of the surface area. Does anyone know where Blender stores face info? Thanks so much in advance!

#

Thank you, Lemon! Sorry, I cannot leave comments because of low reputation. I actually tried bm.face.calc_area() before, however, what it gives me is different than the area value showed in 3d view port when I choose a non-flat face. It works if I choose a flat face. Does bm.face.calc_area() returns face area on uv map?

#

Thanks for all the replies! Martin and Lemon, you're right, I ignore the triangulation. When I calculate the face area of a quad face, it's 0.0002683, which is different than the value given by face info 0.0002705. Then I convert quads topology to triangles, I saw the sub tri areas of the quads are separately 0.00014476175 and 0.0001255185, which can be added up to 0.0002702. If I keep dividing my quad face, I think I will the value very close to 0.0002705. Thanks so much for solving my problem. And sorry for my bad wording, I'm working on a human face model, the flat face can be considered as the forehead area, while the non-planar face can be found in nose area.

Can I ask another question? Since the 'calc_area' gives me the face area in 3d view, how can I get the face area in uv mapping. We know that there is distortion caused by unwrapping a 3d object to 2d. If I get the areas before and after uv unwrapping, I can calculate the distortion. Any idea on calculating face area in uv mapping?

I also tried 'ob.data.polygons', which gives me the same result as 'bmface.calc_area'.

Thanks a lot in advance!!

#

[What I'm trying to do is to calculate the face areas before and after uv unwrapping and see the distortion from 3d to 2d. I know blender provides a visualization tab to display the distortion degree, which is 'stretch' in uv panel. After applying 'stretch' on uv map, I can see colors from dark blue to red, representing the distortion between uv and 3d coordinates. However, I cannot get the data deciding these colors, that's why I want to get the areas before and after unwrapping. Thanks a lot in advance!!]

# I decided to calculate face area on uv mapping according to the vertices on this face, I share my code here:

import bpy

me = bpy.context.object.data

uv_layer = me.uv_layers.active.data

uv_area_ls = []

for poly in me.polygons:

coords = []

for loop_index in range(poly.loop_start, poly.loop_start + poly.loop_total):

    uv_coords = uv_layer[loop_index].uv

    coords.append((uv_coords[0],uv_coords[1]))

if len(coords) == 4:

    coords_new = [coords[1],coords[0],coords[3],coords[2]]

if len(coords) == 3:

    coords_new = cords

area = PolygonArea(coords_new)

uv_area_ls.append(area)

(Note that: I used PolygonArea function in this post https://stackoverflow.com/questions/24467972/calculate-area-of-polygon-given-x-y-coordinates)

$\endgroup$
5
  • 2
    $\begingroup$ Use bmesh module docs.blender.org/api/current/… $\endgroup$
    – lemon
    Sep 5, 2019 at 7:41
  • $\begingroup$ I wonder, what exactly should be considered to be the area of a non-planar face... It's kind of hard to imagine. The surface illusion defined by the normals? The sum of triangles that make it up? Triangulation of non-planar faces can be done in at least 2 ways - maybe that's why you can sometimes see different results? $\endgroup$ Sep 6, 2019 at 5:59
  • $\begingroup$ I think triangulation is the problem (even if not tested for area). Though have tested for volume and triangulation has influence on it @MartinZ (docs.blender.org/api/current/…) $\endgroup$
    – lemon
    Sep 6, 2019 at 6:13
  • $\begingroup$ To calculate it by yourself from ABC a triangle this is half length of AB cross product AC. $\endgroup$
    – lemon
    Sep 6, 2019 at 6:17
  • $\begingroup$ related: blender.stackexchange.com/a/47676/30849 $\endgroup$
    – Leander
    Sep 6, 2019 at 9:47

1 Answer 1

6
$\begingroup$

You can use bmesh to do that.

The code is fully commented below.

Using mesh triangulation

The only non intuitive part about bmesh and uv access is you need to get it using face loops.

Face loops are elements that can be indexed by the corresponding bmesh loops layer so that you can get the corresponding value in the loop (last line in the code before the print).

Note also that uv 2D coordinates are converted to a 3D vector so that tri_area function can work. This is a facility. You may want to write a more specific calculation for 2D tri area.

import bpy
import bmesh
from mathutils import Vector

# 3D tri area ABC is half the length of AB cross product AC 
def tri_area( co1, co2, co3 ):
    return (co2 - co1).cross( co3 - co1 ).length / 2.0

# Get the object
obj = bpy.context.object

# Construct bmesh
bm = bmesh.new()
bm.from_mesh( obj.data )

# Triangulate it so that we can calculate tri areas
bmesh.ops.triangulate( bm, faces = bm.faces )

# Ensure faces access
bm.faces.ensure_lookup_table()

# Get the uv map
uv_loop = bm.loops.layers.uv['UVMap']

# enumerate the faces
for face in bm.faces:
    # Get the face area (can also be 'face.calc_area()')
    face_area = tri_area( *(v.co for v in face.verts) )
    # Get corresponding uv area
    uv_area = tri_area( *(Vector( (*l[uv_loop].uv, 0) ) for l in face.loops) )
    print( face.index, face_area, uv_area )

Using triangulation on the fly

The interest is to have the final result at the polygon level so that areas can be directly compared to the mesh as it is.

The principle is nearly the same but we use bm.calc_loop_triangles() which gives us several loops of tris each referencing the initial faces.

As the areas for each face will be obtain by part (one tri at a time), we use a dictionary to cumulate the results (nothing gives a guaranty that each face is cut continuously in the resulting loop).

import bpy
import bmesh
from mathutils import Vector

# 3D tri area ABC is half the length of AB cross product AC 
def tri_area( co1, co2, co3 ):
    return (co2 - co1).cross( co3 - co1 ).length / 2.0

# Get the object
obj = bpy.context.object

# Construct bmesh
bm = bmesh.new()
bm.from_mesh( obj.data )

# Ensure faces access
bm.faces.ensure_lookup_table()

# Triangulate it so that we can calculate tri areas
triangle_loops = bm.calc_loop_triangles()

# Get the uv map
uv_loop = bm.loops.layers.uv['UVMap']

# Initialize face entries
areas = {face: (0.0, 0.0) for face in bm.faces} 

# enumerate the loops
for loop in triangle_loops:
    # Get the face
    face = loop[0].face
    # Get current areas
    face_area, uv_area = areas[face]
    # Add tri surface area
    face_area += tri_area( *(l.vert.co for l in loop) )
    # Add corresponding uv surface area
    uv_area += tri_area( *(Vector( (*l[uv_loop].uv, 0) ) for l in loop) )
    # Set the result in the dictionary
    areas[face] = (face_area, uv_area)

# Print results
for face in areas.keys():
    face_area, uv_area = areas[face]
    print( face.index, face_area, uv_area )

Note: triangulation calculation is not trivial and several algorithms may give different results, so for coherency in all cases we need to ask Blender apis how the triangulation must be done.

$\endgroup$
1
  • $\begingroup$ Thanks so much for your elegant solution and detailed explanation! It works, many thanks!! $\endgroup$
    – goodxting
    Sep 10, 2019 at 8:55

You must log in to answer this question.

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