2
$\begingroup$

How can I find angles between the edges in a face, and then make a loop for all faces in an object.

I know I can set it up in a blender and just read it, but I'd like to send these angles to a text file. How to do it with Python

$\endgroup$
1
  • 1
    $\begingroup$ To clarify: As in the interior "edge angle" of an equilateral triangle is sixty degrees? $\endgroup$ – batFINGER Nov 26 '20 at 17:45
4
$\begingroup$

Project into 2D

The verts and edges are wound in order. Knowing the face normal can walk around the edges in a face, using the vector of the "incoming" edge, and vector of "outgoing" edge can calculate the internal angle as as outlined in Finding Internal Angles of a Polygon Blender winds its faces in a counter-clockwise direction.

IMO other answers fail to take this into account, see below

Blender has an angle_signed method for 2d vectors. To use here may require having an arbitrary "Greenwich" point as a reference.

  • If not colinear, The three verts of the 2 edges form a plane

  • The corner of 2 edges is the middle vert (b) Subtract this from other vert of each edge (new origin)

  • The cross product of the two edges is the axis of rotation (normal to plane)

  • Change the space to project (rotate) corner plane into XY plane to represent the corner edges as 2D xy vectors.

Test script. By way of point of difference have used an edit mode bmesh. In edit mode, select the faces you wish to see the internal angles of, then run script.

import bpy
from mathutils import Matrix, Vector
from bpy import context
from math import degrees, atan2, pi
import bmesh
# project into XY plane, 
up = Vector((0, 0, 1))

ob = context.object
me = ob.data
bm = bmesh.from_edit_mesh(me)
def edge_angle(e1, e2, face_normal):
    b = set(e1.verts).intersection(e2.verts).pop()
    a = e1.other_vert(b).co - b.co
    c = e2.other_vert(b).co - b.co
    a.negate()    
    axis = a.cross(c).normalized()
    if axis.length < 1e-5:
        return pi # inline vert
    
    if axis.dot(face_normal) < 0:
        axis.negate()
    M = axis.rotation_difference(up).to_matrix().to_4x4()  

    a = (M @ a).xy.normalized()
    c = (M @ c).xy.normalized()
    
    return pi - atan2(a.cross(c), a.dot(c))

selected_faces = [f for f in bm.faces if f.select]
for f in selected_faces:
    edges = f.edges[:]
    print("Face", f.index, "Edges:", [e.index for e in edges])
    edges.append(f.edges[0])
    
    for e1, e2 in zip(edges, edges[1:]):

        angle = edge_angle(e1, e2, f.normal)
        print("Edge Corner", e1.index, e2.index, "Angle:", degrees(angle))

Test run on concave ngon

enter image description here 2x2 grid center vert dissolved. Bottom vert moved to origin making a 270 degree internal angle between edges 5 and 1

Face 0 Edges: [1, 6, 2, 7, 3, 4, 0, 5]
Edge Corner 1 6 Angle: 45.0
Edge Corner 6 2 Angle: 180.0
Edge Corner 2 7 Angle: 90.0
Edge Corner 7 3 Angle: 180.0
Edge Corner 3 4 Angle: 90.0
Edge Corner 4 0 Angle: 180.0
Edge Corner 0 5 Angle: 45.0
Edge Corner 5 1 Angle: 270.0

For file IO consult the python docs. (Or see other answers).

Re other answers, angles when run on ngon above

@lemon

[135.00000034162267, 0.0, 90.00000250447816, 0.0, 90.00000250447816, 0.0, 135.00000034162267, 90.00000250447816]

@MohammadHosseinJamshidi

[45.00000125223908, 90.00000250447816, 180.00000500895632, 90.00000250447816, 180.00000500895632, 90.00000250447816, 180.00000500895632, 45.00000125223908]
$\endgroup$
1
  • $\begingroup$ this answer is most helpful :) $\endgroup$ – Kolkornik Nov 30 '20 at 13:56
2
$\begingroup$

You can also (more directly):

import bpy
from mathutils import Vector
import csv

def get_face_angles(obj, poly):
    # Get vertices
    vertices = [obj.data.vertices[i] for i in poly.vertices]
    # Append first and second to loop back from the last ones
    vertices.append(vertices[0])
    vertices.append(vertices[1])
    # Get angle triplets
    triplets = [(v1, v2, v3) for v1, v2, v3 in zip(vertices, vertices[1:], vertices[2:])]
    # Keep elements and get angles
    return [(poly, v1, v2, v3, (v2.co - v1.co).angle(v3.co - v2.co)) for v1, v2, v3 in triplets]

def get_faces_angles(obj):
    # Loop over the polygons and get the results
    results = []
    for p in obj.data.polygons:
        results.extend(get_face_angles(obj, p))
    return results

def save_to_file(path, angles):
    with open(path, 'w', newline = "") as f:
        writer = csv.writer(f)
        for a in angles:
            #Five columns: face index, three vertex indices, angle
            writer.writerow([a[0].index, a[1].index, a[2].index, a[3].index, a[4]])

obj = bpy.context.object

angles = get_faces_angles(obj)

save_to_file("your file name", angles)
$\endgroup$
9
  • $\begingroup$ This is not what wanted. 1) It gives unknown triangles angles which share common vertices with polygon (if a polygon wont be single-planar, your triangles may not match with real model's triangles). 2) the question is about edges of polygons not triangles (for example a 6-gon or so)- in this way for polys with more than 3-edges you always calculate smaller angles $\endgroup$ – MohammadHossein Jamshidi Nov 27 '20 at 10:21
  • $\begingroup$ @MohammadHosseinJamshidi, I could be wrong but could you provide a concrete example? $\endgroup$ – lemon Nov 27 '20 at 10:28
  • $\begingroup$ I even learned something from your code so please accept my thanks. example: consider a flat 6-gon . move 1-vertex a little up. blender itself creates its own triangles. now if you connect every permutation of 3-vertices and calculate angles, you have (also calculated whats needed but) created triangles which are not even blender's triangles. they may not match. if there's a need for a picture please tell me. $\endgroup$ – MohammadHossein Jamshidi Nov 27 '20 at 10:39
  • 1
    $\begingroup$ @MohammadHosseinJamshidi, I'm very sorry but still don't get it. I may be wrong again but if this is one face (6gon) in Blender polygons model, I do not consider the upper segment in yellow. And I either don't consider the red ones. Maybe we'll need to continue in chat at some moment. $\endgroup$ – lemon Nov 27 '20 at 11:15
  • 1
    $\begingroup$ excuse me! I'm really sorry. I forgot that the loop is over those ordered tuples. $\endgroup$ – MohammadHossein Jamshidi Nov 27 '20 at 11:40
0
$\begingroup$

use this for polygon-wise angles:

import bpy
import math

path = 'D:/01 Projects/edge_angles.txt'
obj = bpy.context.object

def angle_between_edges(obj,poly,e1,e2):
    common_vert = set(e1).intersection(set(e2))
    if common_vert==set():
        return None
    v0 = list(common_vert)[0]
    v1 = list(set(e1).difference(common_vert))[0]
    v2 = list(set(e2).difference(common_vert))[0]
    vec1 = obj.data.vertices[v1].co -obj.data.vertices[v0].co 
    vec2 = obj.data.vertices[v2].co -obj.data.vertices[v0].co 
    angle = vec1.angle(vec2)
    angle = math.degrees(angle)
    return angle

def write_angles_in_file(obj,path):
    file = open(path,'w')
    for p_i,poly in enumerate(obj.data.polygons):
        for i,e1 in enumerate(poly.edge_keys):
            for j,e2 in enumerate(poly.edge_keys):
                if i<j:
                    angle = angle_between_edges(obj,poly,e1,e2)
                    if angle==None:
                        continue
                    line = str(p_i) + ' ' + str(e1) + ' ' + str(e2) + ' ' + str(angle) + '\n'
                    file.write(line)
    file.close()

write_angles_in_file(obj,path)

and this one for all angles:

import bpy
import math

path = 'D:/01 Projects/edge_angles.txt'
obj = bpy.context.object

def collect_all_edges(obj):
    all_edges = set()
    for poly in obj.data.polygons:
        all_edges.update(set(poly.edge_keys))
    return list(all_edges)

def angle_between_edges(obj,e1,e2):
    v1 = obj.data.vertices[e1[0]].co - obj.data.vertices[e1[1]].co
    v2 = obj.data.vertices[e2[0]].co - obj.data.vertices[e2[1]].co
    # edge is not directed
    angle = min(v1.angle(v2),v1.angle(-v2))
    return angle

def write_angles_in_file(obj,path):
    all_edges = collect_all_edges(obj)
    file = open(path,'w')
    for i,e1 in enumerate(all_edges):
        for j,e2 in enumerate(all_edges):
            if i<j:
                angle = angle_between_edges(obj,e1,e2)
                angle = math.degrees(angle)
                line = str(e1) + ' ' + str(e2) + ' ' + str(angle) + '\n'
                file.write(line)
    file.close()

write_angles_in_file(obj,path)
$\endgroup$
2
  • $\begingroup$ Using a set for collecting edges won't give the wanted result as you will loose the per face view when calculating angles. $\endgroup$ – lemon Nov 27 '20 at 7:20
  • $\begingroup$ I have added a code for calculating angles per face $\endgroup$ – MohammadHossein Jamshidi Nov 27 '20 at 8:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.