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
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
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
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
[135.00000034162267, 0.0, 90.00000250447816, 0.0, 90.00000250447816, 0.0, 135.00000034162267, 90.00000250447816]
[45.00000125223908, 90.00000250447816, 180.00000500895632, 90.00000250447816, 180.00000500895632, 90.00000250447816, 180.00000500895632, 45.00000125223908]
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)
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)
for i,e1 in enumerate(poly.edge_keys): for j,e2 in enumerate(poly.edge_keys): if i<j:
suggests for e.g. 10th edge, you will list 9 angles, between 1st and 10th edge, 2nd and 10th edge... and finally 9th and 10th edge. Now, when you compare edges, you check for common vertices, so that fixes it, but still, if 10th edge is the last edge and connects with the 1st edge, then you will list 2 angles instead of 1. Sure, the first edge counters it by having 0 edges satisfying i < j
, but you end up with wrong ordering: i.imgur.com/xJ7FNS5.png
$\endgroup$
Commented
May 11, 2021 at 15:29