For calculating simplified interactions between objects, bounding boxes are typically used—parallelepipeds whose volume completely encloses the entire mesh geometry. Because such a box has and uses only six points for calculations, unlike the mesh itself, which can have millions of points, all calculations are very fast. Every object has a built-in bounding box, and its display can be enabled in the mesh’s parameters. This bounding box operates in 3D scene space, but there could be some cases when we need to control object interactions in 2D screen space. There are no pre-calculated bounding boxes for the viewport, but we can get one ourselves using the Blender Python API.
To create an object’s bounding box in the 3D viewport, we first need to obtain the coordinates of all its points in viewport space.
To account for unapplied transformations and modifiers on the mesh, we obtain its evaluated data using depsgraph.
|
1 2 |
depsgraph = bpy.context.evaluated_depsgraph_get() object_evaluated = bpy.context.object.evaluated_get(depsgraph) |
By accessing the object by the obtained “object_evaluated” pointer, we can get the coordinates of its points, calculated taking into account the effects of modifiers and transformations.
Get a list of our object’s global coordinates. To do this, we multiply each coordinate of the evaluated mesh by its world transformation matrix.
|
1 2 3 4 |
mesh_eval = object_evaluated.to_mesh() obj_cos_global = [bpy.context.object.matrix_world @ _vert.co for _vert in mesh_eval.vertices] # [Vector((-0.10741505771875381, -0.7608726620674133, 0.46264374256134033)), Vector((-0.7500447034835815, ... ] |
Now we need to convert the object coordinates from the 3D scene space to the 2D viewport space – we need to project the mesh points onto the viewport screen. For this, we can use the “location_3d_to_region_2d()” function.
We’ll get pointers to the area, the viewport itself, and its region for drawing.
|
1 2 3 4 5 6 7 8 |
area = next((_area for _area in bpy.context.screen.areas if _area.type == 'VIEW_3D'), None) # <bpy_struct, Area at 0x000001E8C0137680> region = next((region for region in area.regions if region.type == 'WINDOW'), None) # <bpy_struct, Region at 0x000001E8C0097720> region_3d = area.spaces[0].region_3d # <bpy_struct, RegionView3D at 0x000001E8A91C2820> |
Now we can get the screen coordinates for each vertex of our object.
|
1 2 3 4 |
screen_cos = [location_3d_to_region_2d(region, region_3d, _co) \ for _co in obj_cos_global] # [Vector((419.5784606933594, 423.46630859375)), Vector((320.78887939453125, 439.01641845703125)), ...] |
Let’s define a function that will calculate the Bounding Box coordinates based on a list of vertex coordinates.
And call it, passing in paramter the resulting screen coordinates of the object.
|
1 2 3 4 5 6 7 8 9 10 11 |
def _screen_bbox_cos(verts): x, y = zip(*(p for p in verts)) return { "min_x": min(x), "min_y": min(y), "max_x": max(x), "max_y": max(y), } screen_bbox = _screen_bbox_cos(screen_cos) # {'min_x': 297.76629638671875, 'min_y': 260.05401611328125, 'max_x': 636.6661376953125, 'max_y': 518.6696166992188} |
The function returns four coordinates. These are the coordinates of the two points on the main diagonal of the calculated bounding box. From these, we can easily get the coordinates of the remaining two points of the bounding box.
We have obtained the bounding box coordinates for the object in the 3D viewport. We can then use them in the calculations we need.
For clarity and to verify the correctness of our calculations, let’s display the resulting screen bounding box in the viewport.
For drawing in the viewport, we will use the “gpu” module of the Blender Ptyhon API.
Prepare the data needed for drawing. We need the coordinates of four points to draw our Bounding Box.
|
1 2 3 4 5 6 7 8 |
verts = [ (screen_bbox['min_x'], screen_bbox['min_y'], 0.0), (screen_bbox['min_x'], screen_bbox['max_y'], 0.0), \ (screen_bbox['min_x'], screen_bbox['min_y'], 0.0), (screen_bbox['max_x'], screen_bbox['min_y'], 0.0), \ (screen_bbox['max_x'], screen_bbox['max_y'], 0.0), (screen_bbox['min_x'], screen_bbox['max_y'], 0.0), \ (screen_bbox['max_x'], screen_bbox['max_y'], 0.0), (screen_bbox['max_x'], screen_bbox['min_y'], 0.0) \ ] # [(297.76629638671875, 260.05401611328125, 0.0), (297.76629638671875, 518.6696166992188, 0.0), ...] |
Colors for each of these points.
|
1 2 3 |
color = [(1.0, 0.0, 1.0)] * 8 # [(1.0, 0.0, 1.0), (1.0, 0.0, 1.0), ...] |
And a shader.
|
1 2 3 |
shader = gpu.shader.from_builtin('SMOOTH_COLOR') # <GPUShader object at 0x000001E8BCDB0900> |
Collect all the data into a batch for drawing.
|
1 2 3 |
batch = batch_for_shader(shader, 'LINES', {"pos": verts, "color": color}) # <GPUBatch object at 0x000001E8C06E7C80> |
Define the drawing function and add it to the viewport drawing handler. We’ll also redraw the 3D viewport so that the Bounding Box we’ve rendered become immediately visible.
|
1 2 3 4 5 6 7 8 9 |
def draw(): shader.bind() batch.draw(shader) draw_handler = bpy.types.SpaceView3D.draw_handler_add(draw, (), 'WINDOW', 'POST_PIXEL') for area in bpy.context.window.screen.areas: if area.type == 'VIEW_3D': area.tag_redraw() |
Now we can clearly see the bounding box in the viewport for our object.

.blend file on Patreon