Cursor to Circle Center
Select three points. Run the script.
The 3D cursor is positioned at the center of the circle described by those three points.
The radius of the circle is reported both as popup and to the clipboard.
Which makes it convenient to then add a circle and paste the correct radius into the HUD.
import bpy
import bmesh
from mathutils import Vector
from mathutils.geometry import intersect_line_line
from math import pi
def show_message(message="", title="Cursor to Circle Center", icon='INFO'):
"""Display a message to the user, via popup, print, and clipboard."""
def draw(self, context):
self.layout.label(text=message)
bpy.context.window_manager.popup_menu(draw, title=title, icon=icon)
bpy.context.window_manager.clipboard = message
print(title + ":\n " + icon + ": " + message)
class Error(Exception):
def __init__(self, message):
self.message = message
def cursor_to_circle_center():
obj = bpy.context.active_object
if obj.type != 'MESH':
raise Error("Selected object must be a MESH")
if bpy.context.object.mode != 'EDIT':
raise Error("Switch to Edit mode and select three perimeter vertices")
mesh = bmesh.from_edit_mesh(obj.data)
if len(mesh.select_history) != 3:
raise Error("You must select exactly three perimeter vertices")
points = [Vector(v.co) for v in mesh.select_history]
# Let’s choose the two shortest legs of the triangle
segments = [
[points[0], points[1]],
[points[1], points[2]],
[points[2], points[0]]
]
segments = sorted(segments, key=lambda pair: (pair[0] - pair[1]).magnitude)
# Find the shared point --> P1
p0,p1 = segments[0]
p2,p3 = segments[1]
if p0 == p2: p0,p1,p2 = p1,p0,p3
elif p0 == p3: p0,p1,p2 = p1,p0,p2
elif p1 == p2: p0,p1,p2 = p0,p1,p3
else: p0,p1,p2 = p0,p1,p2
if (p0-p1).angle(p2-p1, pi) > pi / 180 * 179:
raise Error("The three points must be sufficiently non-linear")
# Get the normal to the plane defined by the three points
N = (p0-p1).cross(p2-p1)
# if N == Vector((0,0,0)):
# raise Error("The three points must be non-linear")
# Find a circle bisector given two points on circumference
def bisector(a, b):
N2 = (a-b).cross(N) # perpendicular to line segment and original triangle’s normal
p = (a - b) / 2 + b # midway point on line segment
return (p, p+N2) # ...and midway point between line segment point normals
# All right! Given two bisectors in the same plane, find their intersection point
# (Blender gives us a pair of points. Ideally they have the same value, but float arithmetic
# may produce something slightly off. Either way, their center point is the circle’s center.)
a, b = intersect_line_line(*bisector(p0,p1), *bisector(p2,p1))
p = (a - b) / 2 + b
# Position the 3D cursor
bpy.context.scene.cursor.location = (p.x, p.y, p.z)
# Tell the user the radius
R = (p - p1).magnitude
show_message(message=str(R), title="Radius")
try:
cursor_to_circle_center()
except Error as e:
show_message(e.message, icon='ERROR')