Для корректного расчета упрощенных взаимодействий между объектами обычно используются так называемые Bounding Box — параллелепипеды, которые своим объемом полностью охватывают всю геометрию меша. За счет того что такой параллелепипед имеет и использует для расчетов всего шесть точек, в отличие от самого меша, который может иметь миллионы точек, все вычисления производятся очень быстро. Встроенный Bounding Box есть у каждого объекта, и его отображение можно включить в параметрах меша. Этот Bounding Box работает в пространстве трехмерной сцены, а бывают случаи, когда нужно контролировать взаимодействие объектов в двухмерном пространстве экрана. Для вьюпорта предрасчитанных Bounding Box нет, однако при помощи Blender Python API мы можем получить его сами.
Для того чтобы построить Bounding Box объекта в 3D вьюпорте, для начала нам нужно получить координаты всех его точек в пространстве вьюпорта.
Чтобы учесть непримененные трансформации и модификаторы меша, получим его перерасчитанные данные через depsgraph.
|
1 2 |
depsgraph = bpy.context.evaluated_depsgraph_get() object_evaluated = bpy.context.object.evaluated_get(depsgraph) |
Обращаясь к объекту через полученный указатель object_evaluated мы можем получить координаты его точек, рассчитанные с учетом действия модификаторов и трансформаций.
Получим список глобальных координат нашего объекта. Для этого каждую координату перерасчитанного меша мы умножаем на его мировую матрицу трансформации.
|
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, ... ] |
Теперь нам нужно перевести координаты объекта из трехмерного пространства сцены в двухмерное пространство вьюпорта. Проще говоря спроецировать точки меша на экран вьюпорта. Для этого мы можем использовать функцию location_3d_to_region_2d().
Получим указатели на рабочую область, собственно вьюпорт, и его регион отрисовки.
|
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> |
Теперь получим экранные координаты для каждого вертекса нашего объекта.
|
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)), ...] |
Определим функцию, которая будет рассчитывать координаты Bounding Box на основании списка координат вертексов.
И сразу вызовем ее, передав в нее полученные экранные координаты объекта.
|
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} |
Функция возвращает четыре координаты. Это координаты двух точек главной диагонали рассчитанного Bounding Box. Из них мы легко можем получить координаты оставшихся двух точек Bounding Box.
Мы получили координаты Bounding Box для объекта в 3D вьюпорте. И далее мы можем использовать их в нужных нам расчетах.
Для наглядности и проверки правильности наших расчетов давайте отобразим во вьюпорте полученный экранный Bounding Box.
Для рисования во вьюпорте мы будем использовать модуль gpu.
Подготовим нужные для отрисовки данные. Нам требуются координаты четырех точек, по которым мы отрисуем наш 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), ...] |
Цвета для каждой из этих точек.
|
1 2 3 |
color = [(1.0, 0.0, 1.0)] * 8 # [(1.0, 0.0, 1.0), (1.0, 0.0, 1.0), ...] |
И шейдер для рисования.
|
1 2 3 |
shader = gpu.shader.from_builtin('SMOOTH_COLOR') # <GPUShader object at 0x000001E8BCDB0900> |
Соберем все данные в пакет для отрисовки.
|
1 2 3 |
batch = batch_for_shader(shader, 'LINES', {"pos": verts, "color": color}) # <GPUBatch object at 0x000001E8C06E7C80> |
Определим функцию отрисовки и добавим ее в обработчик отрисовки вьюпорта. Так же перерисуем 3D вьюпорт, чтобы отрисованный нами Bounding Box сразу же отобразился на экране.
|
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() |
Теперь мы наглядно видим рамочку Bounding Box во вьюпорте для нашего объекта.

.blend file on Patreon