I'm trying to use Blender as assets creation tool for my own stuff. I added width and height custom properties on objects, and I want the bounding box to be shown in the 3D view.

Currently, I achieved this by adding non-selectable objects and meshes, but they would be still on the Outliner view, which may confuse my users. So is there any way to draw lines in the 3D view without using meshes?

up vote 3 down vote accepted

I assume you refer to axis-aligned bounding boxes (AABB), because regular bounding boxes are a built-in feature.

You can use draw handlers to draw in supported spaces with OpenGL (bgl module). draw_handler_add() is not documented, but you can find examples in Text Editor, Templates menu.

Here's a modified version of my bound box draw handler:

import bpy
import bgl
from mathutils import Vector

def draw_poll(ob):
    return ob is not None and ob.type in ('MESH', 'CURVE', 'SURFACE', 'META', 'FONT')

def draw_callback_view(self, context):

    for ob in context.selected_objects:
        if not draw_poll(ob):
            return

        mat = ob.matrix_world

        # 50% alpha, 2 pixel width line
        bgl.glEnable(bgl.GL_BLEND)
        bgl.glColor4f(1.0, 0.0, 0.0, 0.5)
        bgl.glLineWidth(2)

        bbox = [mat * Vector(b) for b in ob.bound_box]

        min_x = min(b.x for b in bbox)
        max_x = max(b.x for b in bbox)
        min_y = min(b.y for b in bbox)
        max_y = max(b.y for b in bbox)
        min_z = min(b.z for b in bbox)
        max_z = max(b.z for b in bbox)

        if ob == context.object:
            bgl.glColor4f(*self._color1)
        else:
            bgl.glColor4f(*self._color2)

        bgl.glBegin(bgl.GL_LINE_STRIP)
        bgl.glVertex3f(min_x, min_y, min_z)
        bgl.glVertex3f(min_x, max_y, min_z)
        bgl.glVertex3f(max_x, max_y, min_z)
        bgl.glVertex3f(max_x, min_y, min_z)
        bgl.glVertex3f(min_x, min_y, min_z)
        bgl.glVertex3f(min_x, min_y, max_z)
        bgl.glVertex3f(min_x, max_y, max_z)
        bgl.glVertex3f(max_x, max_y, max_z)
        bgl.glVertex3f(max_x, min_y, max_z)
        bgl.glVertex3f(min_x, min_y, max_z)
        bgl.glEnd()

        bgl.glBegin(bgl.GL_LINES)
        bgl.glVertex3f(max_x, min_y, min_z)
        bgl.glVertex3f(max_x, min_y, max_z)
        bgl.glVertex3f(min_x, max_y, min_z)
        bgl.glVertex3f(min_x, max_y, max_z)
        bgl.glVertex3f(max_x, max_y, min_z)
        bgl.glVertex3f(max_x, max_y, max_z)
        bgl.glEnd()

    # restore opengl defaults
    bgl.glLineWidth(1)
    bgl.glDisable(bgl.GL_BLEND)
    bgl.glColor4f(0.0, 0.0, 0.0, 1.0)


class ModalDrawOperator(bpy.types.Operator):
    """Draw a line with the mouse"""
    bl_idname = "view3d.modal_operator"
    bl_label = "Simple Modal View3D Operator"

    def modal(self, context, event):
        context.area.tag_redraw()

        if event.type in {'ESC'}:
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            self._handle = None
            return {'CANCELLED'}

        return {'PASS_THROUGH'}

    def invoke(self, context, event):
        if context.area.type == 'VIEW_3D':
            # the arguments we pass the the callback
            args = (self, context)
            # Add the region OpenGL drawing callback
            # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
            theme = context.user_preferences.themes[0]
            self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_view, args, 'WINDOW', 'POST_VIEW')
            self._color1 = theme.view_3d.object_active[:] + (0.5,)
            self._color2 = theme.view_3d.object_selected[:] + (0.5,)

            context.window_manager.modal_handler_add(self)
            return {'RUNNING_MODAL'}
        else:
            self.report({'WARNING'}, "View3D not found, cannot run operator")
            return {'CANCELLED'}


def register():
    bpy.utils.register_class(ModalDrawOperator)


def unregister():
    bpy.utils.unregister_class(ModalDrawOperator)

if __name__ == "__main__":
    register()

    for area in bpy.context.screen.areas:
        if area.type == 'VIEW_3D':
            context = bpy.context.copy()
            context['area'] = area
            bpy.ops.view3d.modal_operator(context, 'INVOKE_DEFAULT')
            break
  • Cool! I'll try it! – jiandingzhe Aug 14 '15 at 2:11
  • I have a try and it works. Thanks for a lot! But currently I don't know where to store the handler returned by draw_handler_add(). – jiandingzhe Aug 14 '15 at 6:01
  • My drawing is not invoked by an operator, but it is rendered according to a BoolProperty in each Object. – jiandingzhe Aug 14 '15 at 6:02
  • 1
    The handle is stored in the operator itself, but you could also use a global I guess (a None check should be added though). A property callback (update parameter) could invoke the operator, I'm not sure if this is a good idea though. You could start it once, not terminate it on left / right mouse click (possible not escape either) and add an additional condition to the draw poll function, to not draw anything if your custom property is set to not draw AABBs (the operator would still run in the background, which isn't that great...) – CoDEmanX Aug 14 '15 at 7:04

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.