2
$\begingroup$

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.

enter image description here

$\endgroup$

1 Answer 1

3
$\begingroup$

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)
$\endgroup$
9
  • $\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$
    – Lala_Ghost
    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$
    – batFINGER
    Apr 9, 2021 at 8:00
  • $\begingroup$ You are right or remove: mt.parent = ob $\endgroup$
    – Lala_Ghost
    Apr 9, 2021 at 8:02
  • $\begingroup$ Not the latter, Will require more than simply removing the set parent. (Test by giving edit object translation, rotation and scale) $\endgroup$
    – batFINGER
    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

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .