# Save the bounding box position of an object in a text file

I have a .blend file found here and I'm asking if anyone could assist me to save the positions (x, y, width and height) of the bounding box of the moving object for every frame in the file attached, and save them in a text file. Each line will have the x y width height of the bounding box in each frame.

So if anyone could please advise me as its kind of urgent for me.

• Object.bound_box in Blender is like a cube in object space, I assume you want AABB (axis-aligned bounding box) instead? But you say width and height, are they supposed to be relative to X and Y? One usually uses absolute positions. And why is it 2D? Do you want to simple ignore one dimension? – CodeManX Feb 19 '14 at 7:39
• @CoDEmanX thanks for your comment. Yes, I want the bounding box in 2D, as the camera will be facing the object moving, so I only need to get the x y width and height and write them in a text file, then move to the next frame and so on.. – Tak Feb 19 '14 at 10:55
• So is it supposed to be relative to the camera? Because that makes a difference in code - simple, as long as the camera moves on a global axis. Are x y width height actually left top right bottom? – CodeManX Feb 19 '14 at 12:05
• The camera will be fixed as shown in the example I shared the link, and the object will move in the scene of the camera. The x & y are the position of the top left corner of the rectangle of the bounding box facing the camera, while the width & hight are the distance from the top left corner to the top right corner and distance from the top left corner to the bottom left corner respectively. – Tak Feb 19 '14 at 12:19

## 2 Answers

This is an interactive tool, a modal operator that draws the x, y, width and height of the bounding box corners viewed from front (use ortho to check):

import bpy
import bgl
import blf
from mathutils import Vector
from bpy_extras.view3d_utils import location_3d_to_region_2d

class G:
top_left = Vector()
bottom_right = Vector()

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

def draw_callback_view(self, context):

ob = context.object
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]

bgl.glBegin(bgl.GL_LINE_STRIP)
bgl.glVertex3f(*bbox[0])
bgl.glVertex3f(*bbox[1])
bgl.glVertex3f(*bbox[2])
bgl.glVertex3f(*bbox[3])
bgl.glVertex3f(*bbox[0])
bgl.glVertex3f(*bbox[4])
bgl.glVertex3f(*bbox[5])
bgl.glVertex3f(*bbox[6])
bgl.glVertex3f(*bbox[7])
bgl.glVertex3f(*bbox[4])
bgl.glEnd()

bgl.glBegin(bgl.GL_LINES)
bgl.glVertex3f(*bbox[1])
bgl.glVertex3f(*bbox[5])
bgl.glVertex3f(*bbox[2])
bgl.glVertex3f(*bbox[6])
bgl.glVertex3f(*bbox[3])
bgl.glVertex3f(*bbox[7])
bgl.glEnd()

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)

bgl.glColor4f(0.0, 1.0, 0.0, 0.5)
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()

G.top_left = Vector((min_x, min_y, max_z))
G.bottom_right = Vector((max_x, min_y, min_z))

bgl.glColor4f(1.0, 0.9, 0.0, 1.0)
bgl.glPointSize(5)
bgl.glBegin(bgl.GL_POINTS)
bgl.glVertex3f(*G.top_left)
bgl.glVertex3f(*G.bottom_right)
bgl.glEnd()

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

def draw_callback_px(self, context):

ob = context.object
if not draw_poll(ob):
return

font_id = 0
blf.size(font_id, 20, 72)

region = context.region
rv3d = context.space_data.region_3d

text = "%.2f / %.2f" % G.top_left.xz[:]
dim = blf.dimensions(font_id, text)
loc = location_3d_to_region_2d(region, rv3d, G.top_left)
loc.x -= dim[0]
blf.position(font_id, *loc.to_3d())
blf.draw(font_id, text)

text = "%.2f / %.2f" % tuple(map(abs, (G.top_left - G.bottom_right).xz))
dim = blf.dimensions(font_id, text)
loc = location_3d_to_region_2d(region, rv3d, G.bottom_right)
loc.y -= dim[1]
blf.position(font_id, *loc.to_3d())
blf.draw(font_id, text)

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_view, 'WINDOW')
bpy.types.SpaceView3D.draw_handler_remove(self._handle_px, 'WINDOW')
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'
self._handle_view = bpy.types.SpaceView3D.draw_handler_add(draw_callback_view, args, 'WINDOW', 'POST_VIEW')
self._handle_px = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_PIXEL')

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


The relevant part for you (front view only):

import bpy
from mathutils import Vector

ob = bpy.context.object
mat = ob.matrix_world
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_z = min(b.z for b in bbox)
max_z = max(b.z for b in bbox)

top_left = Vector((min_x, max_z))
bottom_right = Vector((max_x, min_z))

# x and y
#top_left

# width and height
#tuple(map(abs, (top_left - bottom_right))


And here's some code to write out what you asked for (front view):

import bpy
from mathutils import Vector

def main():

# specify the object
ob = bpy.context.object

# change to your desired output location and file name
filepath = r"D:\temp\bounds_2d.txt"

scene = bpy.context.scene
frame_current = scene.frame_current
frame_start = scene.frame_start
frame_end = scene.frame_end

write_bounds_3d(filepath, ob, frame_start, frame_end)

scene.frame_set(frame_current)

def write_bounds_3d(filepath, ob, frame_start, frame_end):

with open(filepath, "w") as file:
for frame in range(frame_start, frame_end + 1):
bpy.context.scene.frame_set(frame)
file.write("%f %f %f %f\n" % get_bounds(ob))

def get_bounds(ob):
mat = ob.matrix_world
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)

top_left = Vector((min_x, max_y))
bottom_right = Vector((max_x, min_y))

# make flat tuple, x y width height
return top_left.to_tuple() + tuple(map(abs, (top_left - bottom_right)))

if __name__ == '__main__':
main()

• thanks @CoDEmanX for your answer, I really appreciate it!! – Tak Feb 20 '14 at 1:42
• Thanks again @CoDEmanX for your answer. I have one more question if you could please help me do it, I was going to edit and add it to this question but I thought it would be better if I posted a new question. The question is found here blender.stackexchange.com/questions/7198/… so if you could please have a look and I have provided pics showing explaining what I want. And please let me know if you have any questions or need any more clarification. I'm very thankful. – Tak Feb 20 '14 at 7:33

Taken the simple scene and comments into account and assuming that the origin of the object is centered, this should do the trick. But be warned: if you have a more complex scene or different setup it will probably fail miserably.

import os
import bpy

def get_anim_info(obj):
width, height = obj.dimensions[0:2]
# Assume origin in centered.
x = obj.location.x - width / 2
y = obj.location.y + height / 2
return "{} {} {} {}".format(x, y, width, height)

# Save the file in the same directory as the blend file
dir = os.path.split(bpy.data.filepath)[0]
f_path = os.path.join(dir, "anim.txt")

scene = bpy.context.scene
anim_info = []
obj = bpy.context.object  # Use the active object.

for frame in range(scene.frame_start, scene.frame_end + 1):
scene.frame_set(frame)
anim_info.append(get_anim_info(obj))
with open(f_path, "w") as f:
f.write("\n".join(anim_info))