Useful mini scripts

Share the little scripts you find useful.

I’ll endeavor to keep a table of contents up here.

All my scripts here are licensed by the Boost License 1.0. BA wants everything licensed as Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License, which you agreed to by joining the forum.

All right, that’s all I’ve got for now. If you have something awesome that you’d like to share here, please do! I’ll add it to the TOC.

Mods, it just occurred to me that threads tend to close after a couple days. Can this be set to “perpetually open”?

5 Likes

Show Message

import bpy

def show_message(message="", title="A Message", 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)

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

1 Like

Edge Loop Length

Select a string of connected edges.
This script will tell you how long it is.

import bpy

def show_message(message="", title="A Message", 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)


obj = bpy.context.active_object
mode = obj.mode

looplength = 0

if obj.type == 'MESH':
	bpy.ops.object.mode_set(mode="OBJECT")

	edges = [e for e in obj.data.edges if e.select == True]

	if edges:
		verts = obj.data.vertices
		for edge in edges:
			looplength += (verts[edge.vertices[0]].co - verts[edge.vertices[1]].co).length
		ShowMessageBox(str(looplength), 'LOOP LENGTH')
	else:
		bpy.context.window_manager.clipboard = ""
		ShowMessageBox('no edges selected', 'LOOP LENGTH', 'ERROR')

bpy.ops.object.mode_set(mode=mode)

Find Concave Faces

Sometimes Blender will complain about concave faces in your mesh. Finding them… lol, good luck.

In EDIT mode, deselect all vertices/faces, then run this script.
This script selects FACES that are not convex.

import bpy
import bmesh

obj = bpy.context.edit_object
me = obj.data

bm = bmesh.from_edit_mesh(me)
bm.faces.active = None

for face in bm.faces:
	face.select_set(False)
	for loop in face.loops:
		if not loop.is_convex:
			face.select_set(True)
			break

bmesh.update_edit_mesh(me)

Find Vertex Parents

Ever want to know which vertices your vertex-parented object is parented to?

  • Select the object parented to your mesh’s vertices
  • Shift-Select the mesh object
  • Run this script

Result: Mesh object in Edit mode with the three vertices selected

import bpy
import bmesh

act_obj = bpy.context.active_object # should be the mesh object, last object selected

# now get the other object - should be the empty
if bpy.context.selected_objects[0] != act_obj:
	sel_obj = bpy.context.selected_objects[0]
else:
	sel_obj = bpy.context.selected_objects[1]

parent_verts = sel_obj.parent_vertices
	
bpy.ops.object.editmode_toggle()
	
mesh = act_obj.data
bm = bmesh.from_edit_mesh(mesh)
bm.verts.ensure_lookup_table()

bpy.ops.mesh.select_all(action='DESELECT')

for x in parent_verts:
	bm.verts[x].select = True

Get Icon Value
& Memoize

This next one isn’t mine, actually, but it is in my toolkit because when scripting Blender’s UI you sometimes just have to have an icon’s number, not its name.

However, even though Dzadik’s function works blindingly fast, it still strikes me as wrong to be scraping through all the icons every single time you want one when drawing your script’s UI panel. Better to look it up once.

So let’s also implement that singular lookup via some feel-good memoization.

import bpy


# Demeter Dzadik
# https://blender.stackexchange.com/a/299657/62918
def get_icon_value(icon_name: str) -> int:
	icon_items = bpy.types.UILayout.bl_rna.functions["prop"].parameters["icon"].enum_items.items()
	icon_dict = {tup[1].identifier : tup[1].value for tup in icon_items}
	return icon_dict[icon_name]


# Memoize the only icon value(s) we need to look up ourselves
def memoize_icon(icon_name):
	value = get_icon_value(icon_name)
	globals()[icon_name] = lambda: value
	return value

INFO = lambda: memoize_icon('INFO')
ERROR = lambda: memoize_icon('ERROR')
#etc

Remove Invalid Drivers

This is another one that isn’t mine, but it addresses an unresolved issue with Blender: when you delete stuff that has drivers attached, the drivers don’t get deleted and don’t go away… but you can’t see them or find them (not without digging in with the Python Console).

This little script cleans away all the little orphan drivers.
Check out the SE thread for the full context.

# Delete all orphaned drivers
# by batFINGER
# https://blender.stackexchange.com/a/212465/62918

import bpy
from bpy import data
from bpy.types import bpy_prop_collection

colls = [
	p for p in dir(data)
	if isinstance(getattr(data, p), bpy_prop_collection)
	]

for p in colls:
	for ob in getattr(data, p, []):
		ad = getattr(ob, "animation_data", None)
		if not ad:
			continue
		bung_drivers = []
		# find bung drivers
		for d in ad.drivers:
			try:
				ob.path_resolve(d.data_path)
			except ValueError:
				bung_drivers.append(d)
		# remove bung drivers
		while bung_drivers:
			ad.drivers.remove(
					bung_drivers.pop()
					)
1 Like

Reset Stretch-to

Resets all Stretch To constraints in the selected armature.
This is a fairly obvious script that has been implemented many times. Here’s my version.

import bpy

obj = bpy.context.active_object
arm = obj.data

for bone in arm.bones:
	pbone = obj.pose.bones[bone.name]
	for cons in pbone.constraints:
		if cons.type == 'STRETCH_TO':
			# length of 0 makes it auto-reset when it next updates
			cons.rest_length = 0
1 Like

That will be incredibly useful to me. Thanks for sharing it. :slight_smile:

1 Like

Find Bone Constraints

For each bone constraint that “targets” (references) the selected bone,
print the bone name and the bone’s constraint type & name.

import bpy

aname = bpy.context.object.name
bname = bpy.context.active_pose_bone.name

cs = list()

for b in bpy.context.object.pose.bones:
	for c in b.constraints:
		try:
			if c.target.name == aname:
				if c.subtarget == bname:
					cs.append( (b.name, c.type, c.name) )
		except:
			pass

if len(cs) == 0:
	print( "Bone '" + bname + "' does not appear in any other bone's constraints" )
else:
	print( "Bone '" + bname + "' found in the following bone constraints:" )
	blen = 0
	for (bname, ctype, cname) in cs:
		blen = max(blen, len(bname))
	for (bname, ctype, cname) in cs:
		print( f'  {bname:<{blen}}  {ctype}({cname})' )

thanks! thanks! beautiful sharing, thanks!!!

1 Like