Frame change handler
Cube is moving further away, Suzy is scaling up and down, cone is rotating thru. Camera rotated.
Proof of concept script.
For each object that I wish to label have added a text object (arbitrarily, could be an image plane) as a child, and given it a custom property named "label" to mark it as the label for its parent.
Theoretically this could be set up with drivers and or constraints, will however use a handler method instead.
By default a text object faces Z up. Will rotate it such that it faces the plane of the scene camera. Have used the highest (largest Y coord in camera space) bounding box corner as the origin of the text object. Can see the switch with rotating cone.
The scaling is using same method explained here, https://blender.stackexchange.com/a/107005/15543 where it is implemented with drivers. Just as RIchard has divided by 10, have used a scale factor of 0.1.
import bpy
from mathutils import Vector, Matrix
scale_factor = 0.1
def label(scene, depsgraph):
cam = scene.camera.evaluated_get(depsgraph)
mw = cam.matrix_world
mwi = mw.inverted()
R = mw.to_3x3().normalized().to_4x4()
labels = [o.evaluated_get(depsgraph) for o in scene.objects
if "label" in o.keys()
]
for label in labels:
ob = label.parent
omw = ob.matrix_world
bbox = [mwi @ (omw @ Vector(b)) for b in ob.bound_box]
bbox.sort(key=lambda v:v.y)
M = R.copy()
p = bbox.pop()
M.translation = mw @ p
S = Matrix.Diagonal(
(scale_factor * -p.z,) * 3).to_4x4()
label.matrix_world = M @ S
bpy.app.handlers.frame_change_post.clear()
bpy.app.handlers.frame_change_post.append(label)
Setting Up.
Here is a quick little script that adds a font object to each selected object as its child and assigns it a "label" custom property to mark it as a label.
import bpy
context = bpy.context
for ob in context.selected_objects:
text = bpy.data.curves.new("label", type='FONT')
text.body = ob.name
label = bpy.data.objects.new("Label", text)
label.parent = ob
label.matrix_world = ob.matrix_world
label["label"] = ob.name
context.collection.objects.link(label)
Empties
As suggested by @clutch-lemon here is similar, but adding an empty as a child of each selected object.
import bpy
context = bpy.context
for ob in context.selected_objects:
label = bpy.data.objects.new("Label", None)
label.parent = ob
label.matrix_world = ob.matrix_world
label["label"] = ob.name
context.collection.objects.link(label)