Собираем генератор данных на Blender. Часть 1: Объекты

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Привет, Хабр! Меня зовут Глеб. Я работаю в компании Friflex над проектами по оцифровке спорта. Работая над idChess (приложением для распознавания и аналитики шахматных партий), мы расширяем наш датасет синтетическими данными. В качестве движка используем Blender. В этой статье рассмотрим основы взаимодействия с объектами, получение доступа через API, перемещение, масштабирование и вращение.

С помощью блендера мы генерим синтетические данные. Поучаемые изображения выглядят довольно реалистично и позволяют улучшить качество моделей. К примеру, вот так выглядит готовый к рендерингу фрагмент шахматной партии.

Далее в статье мы рассмотрим подробнее работу над объектами в Blender.

Интерфейс

Чтобы начать работу с API блендера, откроем python-консоль внутри приложения. Через нее мы будем запускать наши скрипты и наблюдать изменения на сцене.

Помимо консоли, в blender есть текстовый редактор, который позволяет запускать код прямо в приложении. Более подробно про редактор можно посмотреть тут. 

Если планируете импортировать или создавать свои библиотеки, то имейте в виду, что blender использует свое виртуальное окружение. Доступ к python-библиотекам можно получить по следующему пути: /blender/3.0/python/lib/python3.9

Объекты и коллекции

Список объектов и коллекций, с которыми мы можем работать, представлен в этом блоке:

Все объекты в списке наследуются от класса bpy.types.Object. Разберемся, как получить доступ к экземплярам.

cube = bpy.data.objects['Cube']

bpy.data.objects ведет себя, как словарь: доступны методы values, keys, items, get и т.д. А ключами являются названия объектов.

А что если мы хотим получить объекты, содержащиеся в какой-нибудь коллекции? Просто используем другой атрибут модуля bpy.data.

bpy.data.collections['Collection'].all_objects.values()
# [bpy.data.objects['Cube'], bpy.data.objects['Light'], bpy.data.objects['Camera']]

Заметим, что коллекции похожи на обычные папки за одним исключением: содержимым коллекции являются все вложенные в неё объекты — даже те, которые содержатся в дочерних коллекциях.

Важно: свойства, которые мы рассмотрим далее, являются дескрипторами. Другими словами, чтобы трансформировать объект достаточно изменить его свойство.

location

По умолчанию это свойство отвечает за смещение относительно центра сцены. Попробуем переместить объект «Cube».

print(cube.location)
# <Vector (0.0000, 0.0000, 0.0000)>
cube.location = (1, 0, 2)
# Эквивалентно
cube.location.xz = (1, 2)
# Эквивалентно
cube.location.x = 1
cube.location.z = 2
# Эквивалентно
cube.location[0] = 1
cube.location[2] = 2
print(cube.location)
# <Vector (1.0000, 0.0000, 2.0000)>

dimensions

Это свойство позволяет растягивать и сжимать объект вдоль осей. Dimensions возвращает значения в единицах измерения (по умолчанию это метры).

print(cube.dimensions)
# <Vector (2.0000, 2.0000, 2.0000)>
cube.dimensions = (4, 2, 2)
# Эквивалентно
cube.dimensions.x = 4
# Эквивалентно
cube.dimensions[0] = 4
print(cube.dimensions)
# <Vector (4.0000, 2.0000, 2.0000)>

Однако заметим, что для некоторых объектов dimension работать не будет. Об этом мы поговорим далее, а пока посмотрим на простой пример.

camera = bpy.data.objects['Camera']
print(camera.dimensions)
# <Vector (0.0000, 0.0000, 0.0000)>
camera.dimensions.x = 2
print(camera.dimensions)
# <Vector (0.0000, 0.0000, 0.0000)>

scale

Масштабировать объект можно не только с помощью свойства dimensions, но и scale. В отличии от dimensions, scale не использует единицы измерения, а меняет масштаб в относительно исходного размера.

print(cube.scale)
# <Vector (1.0000, 1.0000, 1.0000)>
cube.scale = Vector((2, 1, 1)) # Используется mathutils.Vector, встроенный в blender
# Эквиалентно
cube.scale.x = 2

rotation_euler

Для вращения объекта используется свойство rotation_euler. Оно немного отличается от location, dimensions и scale.

import math
print(cube.rotation_euler)
# <Euler (x=0.0000, y=0.0000, z=0.0000), order='XYZ'>
cube.rotation_euler = (
    math.radians(60),
    math.radians(45),
    math.radians(30)
    )
# Эквивалентно
cube.rotation_euler.x = math.radians(60)
cube.rotation_euler.y = math.radians(45)
cube.rotation_euler.z = math.radians(30)
print(cube.rotation_euler)
# <Euler (x=1.0472, y=0.7854, z=0.5236), order='XYZ'>

Предыдущий подход перезаписывает значение углов. Но если мы хотим вращать объект относительно текущего поворота, то для этого можно использовать следующий метод.

rotation = Euler((0, 0, math.radians(10))) # Используется mathutils.Euler, встроенный в blender
cube.rotation_euler.rotate(rotation)

При вращении объекта с помощью углов Эйлера, нужно помнить об одной их особенности — gimble lock. Про это можно прочитать тут.

bound_box

Это свойство доступно только для чтения. Оно возвращает 8 точек, описывающих границы объекта. В отличии от location, dimensions и scale, bound_box не использует mathutils.Vector.

print(type(cube.bound_box))
# <class 'bpy_prop_array'>
print(type(cube.bound_box[0]))
# <class 'bpy_prop_array'>

Для удобства преобразуем точки в mathutils.Vector:

def bounding_box(obj):
    return [Vector(point) for point in obj.bound_box]

У этого свойства есть еще одна небольшая особенность: по умолчанию координаты граничных точек рассчитываются относительно центра объекта, а не сцены. Чтобы привести координаты к общему центру, можно использовать следующую функцию.

def bounding_box_world(obj):
    return [obj.matrix_world @ point for point in bounding_box(obj)]

data

Ранее мы увидели, что dimensions применяется не ко всем объектам. Но что это значит? Если мы посмотрим на список объектов в нашем проекте, то заметим, что у нас есть три разные по своей природе сущности. Логично, что у камеры будет набор уникальных методов, которого не будет, например, у источника света. Но как это реализуется, если объекты Camera, Cube, Light являются экземплярами общего класса bpy.types.Object.

camera = bpy.data.objects['Camera']
camera.location = (10, 0, 0) # Меняем положения (Свойство объекта)
camera.data.lens = 5 # Меняем фокусное расстояние (Свойство камеры)

В свойстве data находится экземпляр другого класса, предоставляющий доступ к специализированным свойствам и методам. В примере выше, мы переместили камеру (свойство bpy.types.Object) и изменили фокусное расстояние (свойство bpy.types.Camera).

hide_viewport & hide_render

Эти свойства позволяют скрыть объект. hide_viewport отвечает за отображение объекта на сцене, а hide_render — за отображение на конечном изображении.

cube.hide_render = True
cube.hide_viewport = True

The end

Вы работали с blender и хотите обсудить прочитанное? Или поделиться своим опытом и знаниями, чтобы сделать статью более полной и полезной для новичков? Жду вас в комментариях!

Источник: https://habr.com/ru/company/friflex/blog/666958/


Интересные статьи

Интересные статьи

Мы начинаем писать код на языке Python, а также познакомимся с двумя библиотеками. Загрузим биржевые данные и сохраним их у себя на гугл-диске.
АннотацияТабличные данные широко распространены в различных реальных приложениях. Хотя многие широко используемые нейронные компоненты (например, свертки) и расширяемые нейронные сети (например, ResNe...
В конце года мы протестировали техническое решение по защищенной передаче данных со скоростью до 100 Гбит/ сек – с использованием программно-аппаратного комплекса «Квазар-100». Испытания прошли на уча...
Это вторая часть серии заметок о реактивном программировании, в которой представлен обзор Project Reactor, реактивной библиотеки, основанной на спецификации Reactive Streams. ...
Всем привет! Меня зовут Дмитрий Андриянов, я Flutter-разработчик в Surf. В этой статье я расскажу про бесшовную миграцию данных при установке новой версии приложения, написанного на Fl...