I want a newly created cube-empty to enclose my selected vertices by script. I thought on calculating the bounding box of all selected vertices and then position and scale a new cube-empty to enclose them, but I don't know how to use the calculated bounding box positions to enclose the vertices, since a cube-empty doesn't have the bounding box attribute.
1 Answer
BMesh script.
Already had this one lying around,.. no need to use bounding box of either object or it be a requirement of empty.
- Parent empty to object of interest, this way can work solely in local coordinates.
- Find the two extrema of $x, y, z$ coordinates of selected vertices. The inner diagonal of the bounding box.
- Set the empty location to diagonal mid point.
- Giving the empty display size of 0.5 makes it unit dimensions at unit scale
(1, 1, 1)
- Scale it by diagonal vector.
Test script, run in edit mode.
import bpy
import bmesh
import numpy as np
from mathutils import Vector
from bpy import context
ob = context.edit_object
me = ob.data
bm = bmesh.from_edit_mesh(me)
x, y, z = np.array([v.co for v in bm.verts if v.select]).T
mt = bpy.data.objects.new("MT_BBox", None)
mt.empty_display_type = 'CUBE'
mt.parent = ob
mt.empty_display_size = 0.5 # unit
minp = Vector((x.min(), y.min(), z.min()))
maxp = Vector((x.max(), y.max(), z.max()))
mt.location = (minp + maxp) / 2
mt.scale = maxp - minp
context.collection.objects.link(mt)
Un-parented local axis aligned bbox in world space.
To give the bounding box global coordinates, instead use above to make a local matrix and transform it by its parents world matrix, picking up from commenting out set parent (and from mathutils import Vector, Matrix
)
#mt.parent = ob
mt.empty_display_size = 0.5 # unit
mw = ob.matrix_world
minp = Vector((x.min(), y.min(), z.min()))
maxp = Vector((x.max(), y.max(), z.max()))
L = (
Matrix.Translation((minp + maxp) / 2) @
Matrix.Diagonal(maxp - minp).to_4x4()
)
mt.matrix_world = mw @ L
context.collection.objects.link(mt)
And finally a world axis aligned bounding box
mw = ob.matrix_world
x, y, z = np.array([mw @ v.co for v in bm.verts if v.select]).T
mt = bpy.data.objects.new("MT_BBox", None)
mt.empty_display_type = 'CUBE'
#mt.parent = ob
mt.empty_display_size = 0.5 # unit
minp = Vector((x.min(), y.min(), z.min()))
maxp = Vector((x.max(), y.max(), z.max()))
mt.matrix_world = (
Matrix.Translation((minp + maxp) / 2) @
Matrix.Diagonal(maxp - minp).to_4x4()
)
context.collection.objects.link(mt)
-
$\begingroup$ Works great, only thing, is there a way to make it a separate object and not part of my selected object? bpy.ops.object.select_all(action = 'DESELECT') does not work $\endgroup$ Apr 9, 2021 at 7:55
-
$\begingroup$ To clarify it is parented to object, not "part of". In object mode select the empty bound box Alt P (clear parent) Keep transform...=>> to get the global coordinate result. Is that what you are after? $\endgroup$ Apr 9, 2021 at 8:00
-
-
$\begingroup$ Not the latter, Will require more than simply removing the set parent. (Test by giving edit object translation, rotation and scale) $\endgroup$ Apr 9, 2021 at 8:10
-
$\begingroup$ Wouldn't it be easier to iterate over 8 corners of a bounding box to get a minimum and maximum coordinate on each axis? Is your solution better just because it will work even if there's no bounding box, or is there something else I'm missing? $\endgroup$ Apr 9, 2021 at 8:36