8
$\begingroup$

In my scene I have multiple objects (30+). In the viewport I have the handy control of selecting (in the Properties Editor) Object> Display> Name and copying to selected.

This is beautiful for diagrams etc. as wherever my view is at whatever distance these labels have been placed instantly, always face me and are always the same size.

How can you get these to display in a rendered view?

Is the ONLY option to manually create a label for each object, get them to track to your camera (Probably have to track to an empty miles behind your camera rotation otherwise they offset if your camera isn't facing exactly front on to an object) and add some sort of scaling property to change depending on how far away you are?

$\endgroup$
2
  • $\begingroup$ One option would be to develop a python script to iterate through all objects in the scene and create and position a text object at the appropriate location and scale, replacing them when re-run. To get this to dynamically update you would probably need to invoke it from the ‘frame change’ handler so that they were repositioned on each frame. There may even be an addon for something similar. $\endgroup$ Commented Feb 16, 2018 at 19:38
  • $\begingroup$ I'm pretty basic on VB.net so wouldn't know where to start with this. I have a feeling though from peoples answers on different forums that the only way is a python script. Could be a good thing as it'd get me started on the programming $\endgroup$ Commented Feb 19, 2018 at 9:10

3 Answers 3

8
$\begingroup$

For the labels to be visible in the render they must be created as objects within the scene. The following script will process each object in turn and automatically create a Text object set to the object's name and position the Text in front of the camera so that it is in line with the object (1.0 blender unit infront of the camera) :

# Script to automatically generate labels for all objects in the scene and position them in the camera view
# Generated labels are prefixed with '_generated.' and replaced each time the script is run.
# Note that any objects with name that happens to start with '_generated.' will be automatically deleted!
#
# This script will automatically create a material named '_generated.Label' and assign it to all generated labels.
# The generated material can be modified to affect the appearance of the labels.

import bpy
from bpy_extras.object_utils import world_to_camera_view

scn = bpy.context.scene
cam = scn.camera

# Pickup the label material if it exists
mat = bpy.data.materials.get("_generated.Label")

if mat is None:
    # Material doesn't exist, create it
    mat = bpy.data.materials.new(name="_generated.Label")

# Get each object in turn
for obj in scn.objects:
    if obj.name.startswith('_generated.'):
        # Delete the old label
        scn.objects.unlink(obj)
        bpy.data.objects.remove(obj)
    else:
        # Create a new label
        bpy.ops.object.text_add()
        tobj = bpy.context.object

        # Set the name of the label to include the prefix so we can identify them and set the text
        tobj.name = '_generated.'+obj.name
        tobj.data.body = obj.name

        # Position the label between the camera and the object
        diffloc = obj.location - cam.location
        dist = (diffloc[0]**2 + diffloc[1]**2 + diffloc[2]**2)**0.5
        if dist == 0:
            tobj.location = cam.location
        else:
            tobj.location = cam.location + (diffloc / dist * 1.0)  #1.0 blender unit from the camera

        # Set the rotation the same as the camera and scale it appropriate to the distance
        tobj.rotation_euler = cam.rotation_euler
        tobj.scale = (0.03,0.03,0.03)

        # Assign material to object
        if tobj.data.materials:
            # assign to 1st material slot
            tobj.data.materials[0] = mat
        else:
            # no slots
            tobj.data.materials.append(mat)        

Open a Text Editor window and click 'New' and name it 'objectnames.py'. Paste the above text into the window and click Run Script to run it.

The script examines each object in turn and calculates a position in line with the object where a label needs to be positioned for it to overlay the object. A new Text object is created at that location, orientated to face the camera, and assigned a material.

labels positioned infront of camera

Once the script is run, switching to Camera view should show the labels positioned over each object in the scene. Changing the generated material '_generated.Label' will affect the appearance of all labels in the render.

The line :

tobj.location = cam.location + (diffloc / dist * 1.0)

affects the location of the labels - with the '1.0' indicating how far from the camera the labels should be positioned - amend it and re-run the script to position them closer or further from the camera.

Similarly, the line setting 'tobj.scale = (....)' sets the scale of the labels - again, amend that line to change the scale if desired (larger values give larger labels).

Amend the '_generated.Label' material to affect how the labels are rendered. For example, to give them an emission colour without the emission affecting the rest of the scene you may have something like the following :

material

This can produce the following result :

rendered

For animated scenes you will need to run the script for each frame of the animation to reposition the labels. This can be achieved via a "handler" which can be registered to execute each time the frame changes. This can be achieved with the following code :

# Script to automatically generate labels for all objects in the scene and position them in the camera view
# Generated labels are prefixed with '_generated.' and replaced each time the script is run.
# Note that any objects with name that happens to start with '_generated.' will be automatically deleted!
#
# This script will automatically create a material named '_generated.Label' and assign it to all generated labels.
# The generated material can be modified to affect the appearance of the labels.
#
# Scene Custom Property '_generated_label_scale' can be set to a numeric to set the scale of the labels.
# Create an object group named 'Labels' and place any objects within it to only render labels for those specific objects.

import bpy

def handler_object_labels(scn):

    cam = scn.camera

    labelscale = scn["_generated_label_scale"]

    # Pickup the label material if it exists
    mat = bpy.data.materials.get("_generated.Label")

    if mat is None:
        # Material doesn't exist, create it
        mat = bpy.data.materials.new(name="_generated.Label")

    # Get each object in turn
    for obj in scn.objects:
        if obj.name.startswith('_generated.'):
            # Delete the old label
            curve = obj.data
            scn.objects.unlink(obj)
            bpy.data.objects.remove(obj)
            bpy.data.curves.remove(curve)
        else:
            # Check if group 'Labels' exists and if object within that group
            if bpy.data.groups.get("Labels") != None:
                if (bpy.data.groups["Labels"].objects.get(obj.name) == None):
                    continue

            # Create a new label
            curve = bpy.data.curves.new(type="FONT",name="_generated."+obj.name)
            tobj = bpy.data.objects.new("_generated."+obj.name,curve)
            bpy.context.scene.objects.link(tobj)

            # Set the name of the label to include the prefix so we can identify them and set the text
            tobj.name = '_generated.'+obj.name
            tobj.data.body = obj.name

            camloc = cam.matrix_world.to_translation()

            # Position the label between the camera and the object
            diffloc = obj.location - camloc
            dist = (diffloc[0]**2 + diffloc[1]**2 + diffloc[2]**2)**0.5
            if dist == 0:
                tobj.location = camloc
            else:
                tobj.location = camloc + (diffloc / dist * 1.0)  #1.0 blender unit from the camera

            # Set the rotation the same as the camera and scale it appropriate to the distance
            tobj.rotation_euler = cam.matrix_world.to_euler()
            tobj.scale = (labelscale, labelscale, labelscale)

            # Assign material to object
            if tobj.data.materials:
                # assign to 1st material slot
                tobj.data.materials[0] = mat
            else:
                # no slots
                tobj.data.materials.append(mat)

    bpy.context.scene.update()

if not '_generated_label_scale' in bpy.context.scene.keys():
    bpy.context.scene["_generated_label_scale"] = 0.06

if handler_object_labels in bpy.app.handlers.frame_change_post:
    bpy.app.handlers.remove(handler_object_labels)
bpy.app.handlers.frame_change_post.append(handler_object_labels)

Note that this script uses a Scene Custom Property (_generated_label_scale) to control the scale of the labels (defaults to 0.06). By default, labels will be created for all objects in the scene. However, if a group named 'Labels' is found then only those objects within the group are given labels (so select all the objects you want to have labels, select Object/Group/Create New Group from the menu, then on the Objects propert panel rename the group to 'Labels').

Note that the labels are only refreshed on change of frame - so if you move objects or the camera you'll need to swap frame before the labels re-arrange.

This can produce the following result :

animated

Blend file included

$\endgroup$
6
  • $\begingroup$ This is GENIUS!! Does exactly what I want!!!! Thank you so much. What do I do if I want just labels for certain layers, for example just everything in layer 2 and 3 is labelled and everything else is ignored $\endgroup$ Commented Feb 22, 2018 at 9:26
  • $\begingroup$ @SeanKilburn No problem - glad to help. It would be fairly straightforward to check which layers each object is in and affect the label based on that - I believe there are easy to access flags for the layer on object.data. I’m in the process of making a change to automatically trigger the labels on change of frame using a handler - just a couple of issues with tidying up as it’s eating memory a bit as I noticed it isn’t freeing the label text objects properly. I’ll see if I can include selection based on layer when I update it for that. $\endgroup$ Commented Feb 22, 2018 at 9:55
  • $\begingroup$ Thanks again, I hope you post your updated script when it's ready $\endgroup$ Commented Feb 22, 2018 at 14:10
  • 1
    $\begingroup$ Thank you so much for this you've made me look like the man at my job!! $\endgroup$ Commented Feb 28, 2018 at 12:03
  • 1
    $\begingroup$ Great! Well done Rich‼️‼️ $\endgroup$
    – Chris
    Commented May 5, 2021 at 7:21
1
$\begingroup$

Update for Python API changes since 2.8 Sorry, it's not a handler, but i have done some debugging and browsing for errors to make it work with the new Blender versions.

# Script to automatically generate labels for all objects in the scene and position them in the camera view
# Generated labels are prefixed with '_generated.' and replaced each time the script is run.
# Note that any objects with name that happens to start with '_generated.' will be automatically deleted!
#
# This script will automatically create a material named '_generated.Label' and assign it to all generated labels.
# The generated material can be modified to affect the appearance of the labels.
#
# Scene Custom Property '_generated_label_scale' can be set to a numeric to set the scale of the labels.
# Create an object group named 'Labels' and place any objects within it to only render labels for those specific objects.

import bpy



if not '_generated_label_scale' in bpy.context.scene.keys():
    bpy.context.scene["_generated_label_scale"] = 0.06


scn = bpy.context.collection
cam = bpy.data.objects['Camera']

labelscale = bpy.data.scenes['Scene']["_generated_label_scale"]

# Pickup the label material if it exists
mat = bpy.data.materials.get("_generated.Label")

if mat is None:
    # Material doesn't exist, create it
    mat = bpy.data.materials.new(name="_generated.Label")

# Get each object in turn
for obj in scn.objects:
    print(obj.name)
    if obj.name.startswith('_generated.'):
        # Delete the old label
        curve = obj.data
        scn.objects.unlink(obj)
        bpy.data.objects.remove(obj)
        bpy.data.curves.remove(curve)
    else:
        # Check if group 'Labels' exists and if object within that group
        if bpy.data.collections.get("Labeled") != None:
            if (bpy.data.collections["Labeled"].objects.get(obj.name) == None):
                continue

        # Create a new label
        curve = bpy.data.curves.new(type="FONT", name="_generated."+obj.name)
        tobj = bpy.data.objects.new("_generated."+obj.name, curve)
        tobj.name = '_generated.'+obj.name
        tobj.data.body = obj.name
        bpy.data.scenes['Scene'].collection.objects.link(tobj)
        #        bpy.context.collection.objects.link(tobj)

        # Set the name of the label to include the prefix so we can identify them and set the text
     

        camloc = cam.matrix_world.to_translation()

        # Position the label between the camera and the object
        # tobj.location = obj.location
        diffloc = obj.location - camloc
        dist = (diffloc[0]**2 + diffloc[1]**2 + diffloc[2]**2)**0.5
        if dist == 0:
            tobj.location = camloc
        else:
            # 1.0 blender unit from the camera
            tobj.location = camloc + (diffloc / dist * 1.0)

        # Set the rotation the same as the camera and scale it appropriate to the distance
        tobj.rotation_euler = cam.matrix_world.to_euler()
        tobj.scale = (labelscale, labelscale, labelscale)

        # Assign material to object
        if tobj.data.materials:
            # assign to 1st material slot
            tobj.data.materials[0] = mat
        else:
            # no slots
            tobj.data.materials.append(mat)

```
$\endgroup$
-2
$\begingroup$

As far as I was able to test the answer is yes Sean.

Look at my screenshots below (Blender v2.77, default scene and cube) Default Cube - Object mode Default Cube - Edit mode

Look at Outliner, red square: It only shows the Cubes mesh symbol underneath the Cube_Object_Name. Nothing "extra" (like the visually render-able labels you want) are being added by turning on/off the (Viewport / Display only) settings in the Object -> Properties -> Display settings.

$\endgroup$
2
  • $\begingroup$ It's not quite what I'm after, because they don't display when the image is rendered. They're only there in the view port $\endgroup$ Commented Feb 19, 2018 at 9:07
  • $\begingroup$ So I guess there is nothing else left to do for you - to manually and/or Python-scriptwise add separate object labels as different objects to your Blender scene and use a [Track-To] Object constraint to force the labels to keep looking at the camera in the scene - like you already had suggested yourself. $\endgroup$ Commented Feb 20, 2018 at 18:10

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .