I am using Blensor v2.67. I have a camera and several objects in my scene. The camera follows a path and I would like to save what the camera sees for each frame. Among other things, I would like to save the (oriented) bounding boxes of my objects. I did some research, but I believe Display bounding box around objects only displays the bounding boxes and in Save the bounding box position of an object in a text file the requirements seem to be different (2D, moving object,...). Any answer on how to do this will be greatly appreciated! Is there inherent functionality to do this or do I need to include a custom script?
-
$\begingroup$ I also wonder, if a group of objects can have a "bound box" including all the subobjects... I'm struggling with the same problem as you are. My tests have shown that duplicated groups have no information about their dimension. Is there any solution to get or compute them? I would like to use them in a driver to adjust the scale of an object according to its dimensions. $\endgroup$– IsolinCommented Jul 18, 2014 at 9:14
4 Answers
From my answer here, you can create a bounding cube around an object:
Select the target object and press CtrlShiftAltC > Origin to goemetry, and then press F6 and select bounds center.
With the target object selected, press ShiftS> Cursor to selected to move the cursor to the objects origin.
Add a cube to be the bounding box. It will automatically be centered at the 3D cursor, which is snapped to the target object's origin point.
Copy the Dimension and Rotation values from the target object to the bounding cube. These values are located on the Properties region (N in the 3D view):
You can quickly copy and paste values by hovering over the number field and pressing CtrlC/CtrlV accordingly.
Since this process of copying and pasting the values of each channel can be a bit tedious, you can use a quick bit of python to do this for you:
import bpy active = bpy.context.active_object selected = bpy.context.selected_objects for obj in selected: obj.dimensions = active.dimensions
Parent the bounding box to the target object so the relative location, rotation, and scale is kept the same. You can also set the bounding cube's Draw type to Wire in Properties > Object > Display > Maximum Draw Type.
This processes could be automated in python:
import bpy
selected = bpy.context.selected_objects
for obj in selected:
#ensure origin is centered on bounding box center
bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='BOUNDS')
#create a cube for the bounding box
bpy.ops.mesh.primitive_cube_add()
#our new cube is now the active object, so we can keep track of it in a variable:
bound_box = bpy.context.active_object
#copy transforms
bound_box.dimensions = obj.dimensions
bound_box.location = obj.location
bound_box.rotation_euler = obj.rotation_euler
This adds a bounding box object for every selected object.
-
$\begingroup$ Thanks! To export the boxes rotation etc I would also need to code a Python script I imagine? I guess its time to get into python scripting with blender then.. ;) $\endgroup$ Commented Jul 15, 2014 at 8:12
-
$\begingroup$ @user1809923 I've added an example script $\endgroup$– gandalf3Commented Jul 15, 2014 at 8:29
-
$\begingroup$ Nice! I wonder however if it is necessary to create an extra object for the bounding box. With the show_bounds function a bounding box is already displayed, so this box needs to exist somewhere. Also an object has a "bound_box" (blender.org/documentation/blender_python_api_2_71_release/…). I also wonder, if a group of objects can have a "bound box" including all the subobjects... $\endgroup$ Commented Jul 15, 2014 at 9:05
-
$\begingroup$ This seems to work nicely with single objects. In that case it actually seems to be the minimum oriented bounding box (MOBB), that I was actually looking for. However, when I try to adapt the procedure to create a MOBB for several objects by selecting, duplicating and joining them before, the bounding box is not minimal at all anymore. Any idea on how to overcome this problem? I guess I should create a new questions cause its slightly different then the one here and there: blender.stackexchange.com/questions/14072/…, right? $\endgroup$ Commented Jul 30, 2014 at 7:31
-
$\begingroup$ @user1809923 It's because the origin needs to be recentered. I'll updated my answer. $\endgroup$– gandalf3Commented Jul 30, 2014 at 7:49
By using the addon I added to your other question you can create a bounding box that has the dimensions that you can easily export to a text file.
If you want to export an individual bounding box for each object you can enable the addon and run the following -
startobjs = [o.name for o in bpy.context.selected_objects]
for objname in startobjs:
bpy.ops.object.select_all(action='DESELECT')
bpy.data.objects[objname].select = True
bpy.ops.mesh.boundbox_add()
As the addon names each bounding box with the name BoundingBox you can easily select just the bounding boxes (using select pattern in the select menu) and then export the selected objects.
Then when you export turn on the selected only option available in most exporters. If your using your own script to export to text then loop over the selected objects.
for obj in bpy.context.selected_objects:
print(obj.dimensions.x)
-
$\begingroup$ nice! However, the bounding box your script creates seems to be axis aligned. Unfortunately, I need the (actual minimum) oriented bounding box. $\endgroup$ Commented Jul 30, 2014 at 7:12
-
$\begingroup$ The script is intended to create a box around a group of objects, deciding which points and/or bounding box face to use to get a rotation would be tricky. It wouldn't be hard to adapt the script to create a bounding box for each object that matches each object. $\endgroup$– samblerCommented Jul 30, 2014 at 8:58
-
$\begingroup$ Here is a variation to the boundbox addon that will create a a box for each object that also matches rotation and scale. $\endgroup$– samblerCommented Jul 30, 2014 at 9:57
-
$\begingroup$ Thanks :) Useful! One should note however, that this now is not useable to create one bounding box for several objects anymore. $\endgroup$ Commented Jul 30, 2014 at 12:32
I am using a great tiny add-on called "Create Bounding Box" by Shane Ambler script link. It adds a bounding-box around the selected object.
Just install the add-on script by going to edit menu > preferences and install. The add-on is also compatible with Blender 2.80
For getting the minimal oriented bounding box you can use trimesh.bounds.oriented_bounds.
You will need trimesh
, networkx
and scipy
.
import bpy
from mathutils import Matrix, Vector
import numpy as np
def import_module_from_file(module_name: str, file_path: str):
import importlib.util
import sys
spec = importlib.util.spec_from_file_location(module_name, file_path)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
return module
# install trimesh and soft deps
# # https://gist.github.com/unwave/24f0ee4bfc9cc0af1bf35eda5d54d49f
# from . import ensure_site_packages
# # or
# PATH_TO_ENSURE_SITE_PACKAGES = r'C:\something\something\ensure_site_packages.py'
# ensure_site_packages = import_module_from_file('ensure_site_packages', PATH_TO_ENSURE_SITE_PACKAGES )
# deps = [("trimesh", "trimesh"), ("networkx", "networkx"), ("scipy", "scipy")]
# # may try trimesh[easy] if some soft dependencies are still missing
# # it installs a lot of things
# target = ensure_site_packages.get_site_packages_directory()
# # is .\_deps relative to __file__, intended for addons
# # or
# import site
# target = site.getusersitepackages()
# # or chose you own folder
# ensure_site_packages.ensure_site_packages(deps, target)
import trimesh
DEFAULT_BOUND_BOX = [
Vector((-0.5, -0.5, -0.5)),
Vector((-0.5, -0.5, 0.5)),
Vector((-0.5, 0.5, 0.5)),
Vector((-0.5, 0.5, -0.5)),
Vector((0.5, -0.5, -0.5)),
Vector((0.5, -0.5, 0.5)),
Vector((0.5, 0.5, 0.5)),
Vector((0.5, 0.5, -0.5))
]
# will be using the depsgraph so it will work with modified geometry
depsgraph = bpy.context.evaluated_depsgraph_get()
# visualization objects set up
collection = bpy.data.collections.get('__test_empties__')
if not collection:
collection = bpy.data.collections.new('__test_empties__')
bpy.context.scene.collection.children.link(collection)
for obj in list(bpy.data.objects):
if obj.name.startswith('__test'):
bpy.data.objects.remove(obj)
# saving selection state
selected_objects = bpy.context.selected_objects[:]
active_object = bpy.context.object
# getting bound boxes
for object in bpy.context.selected_objects[:]:
# getting evaluated geometry
object = object.evaluated_get(depsgraph)
try:
mesh = object.to_mesh(depsgraph=depsgraph)
except:
import traceback
traceback.print_exc()
continue
# will crash blender
if not mesh.vertices:
continue
# apply transforms
mesh.transform(object.matrix_world)
# get vertices
vertices = np.zeros(len(mesh.vertices) * 3)
mesh.vertices.foreach_get('co', vertices)
vertices = np.reshape(vertices, (-1, 3))
# get faces
loop_triangles = mesh.loop_triangles
faces = np.zeros(len(loop_triangles) * 3)
loop_triangles.foreach_get('vertices', faces)
faces = np.reshape(faces, (-1, 3))
# foreach_get is faster for big objects than the equivalent:
# vertices = [vert.co for vert in mesh.vertices]
# faces = [list(loop.vertices) for loop in mesh.loop_triangles]
# free memory
object.to_mesh_clear()
# creating trimesh.Trimesh
trimesh_mesh = trimesh.Trimesh(vertices=vertices, faces=faces)
# getting the minimum oriented bound box
try:
to_origin, extents = trimesh.bounds.oriented_bounds(trimesh_mesh)
# will fail when len(vertices) < 3
# use native bound_box
except:
import traceback
traceback.print_exc()
continue
# converting to Blender usable structures
dimensions = Matrix.LocRotScale(None, None, Vector(extents))
matrix_world = Matrix(to_origin).adjugated()
# an equivalent of object.bound_box
bound_box = [dimensions @ v for v in DEFAULT_BOUND_BOX]
# an equivalent of [object.matrix_world @ Vector(v) for v in object.bound_box]
# which is bound box in world space
bound_box_world = [matrix_world @ v for v in bound_box]
# creating objects for visualization
wm_translation, wm_rotation, wm_scale = matrix_world.decompose()
wm_rotation = wm_rotation.to_matrix().to_4x4()
for point in bound_box_world:
o = bpy.data.objects.new( "__test_empty__", None )
bpy.data.collections['__test_empties__'].objects.link(o)
o.matrix_world @= wm_rotation
o.location = point
bpy.ops.mesh.primitive_cube_add(enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(0.5, 0.5, 0.5))
box = bpy.context.object
box.select_set(False)
box.name = '__test_bound_box__'
box.display_type = 'WIRE'
box.matrix_world = matrix_world
box.dimensions = extents
for collection in box.users_collection:
collection.objects.unlink(box)
bpy.data.collections['__test_empties__'].objects.link(box)
object.show_bounds = True
# restoring selection state
for obj in selected_objects:
obj.select_set(True)
bpy.context.view_layer.objects.active = active_object