Система скриптов в Blender предоставляет широкие возможности по упрощению и ускорению рабочего процесса, позволяя перекладывать выполнение рутинных операций на систему и расширяя возможности работы благодаря доступу к скриптовому языку. Однако, написав удачный скрипт, который будет часто использоваться в разных проектах, неудобно каждый раз заново подключать его в каждый новый проект. К тому же такой скрипт вполне может потребовать улучшений в виде окон и полей с редактируемыми параметрами. Превратив скрипт в полноценный аддон, можно оснастить его дополнительным функционалом и подключить в систему дополнений Blender.
Возьмем простой скрипт создания трубы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
import math import bpy n = 8 r1 = 1 r2 = 0.5 h = 1 verts = [] ring1 = [] ring2 = [] ring3 = [] ring4 = [] faces = [] for i in range(n): grad = (360 * i / n) * math.pi / 180 ring1.append([r1 * math.cos(grad), r1 * math.sin(grad), 0]) ring2.append([r2 * math.cos(grad), r2 * math.sin(grad), 0]) ring3.append([r1 * math.cos(grad), r1 * math.sin(grad), h]) ring4.append([r2 * math.cos(grad), r2 * math.sin(grad), h]) if i == 0: faces.append([i - 1 + n, i, i + 2 * n, i - 1 + 3 * n]) faces.append([i - 1 + 2 * n, i - 1 + 4 * n, i + 3 * n, i + n]) faces.append([i - 1 + n, i - 1 + 2 * n, i + n, i]) faces.append([i - 1 + 3 * n, i + 2 * n, i + 3 * n, i - 1 + 4 * n]) else: faces.append([i - 1, i, i + 2 * n, i - 1 + 2 * n]) faces.append([i - 1 + n, i - 1 + 3 * n, i + 3 * n, i + n]) faces.append([i - 1, i - 1 + n, i + n, i]) faces.append([i - 1 + 2 * n, i + 2 * n, i + 3 * n, i - 1 + 3 * n]) verts.extend(ring1) verts.extend(ring2) verts.extend(ring3) verts.extend(ring4) tubeMesh = bpy.data.meshes.new("Tube") tubeMesh.from_pydata(verts, [], faces) tubeMesh.update() tube = bpy.data.objects.new("Tube", tubeMesh) bpy.context.scene.objects.link(tube) bpy.ops.object.select_all(action="DESELECT") tube.select = True bpy.context.scene.objects.active = tube |
Дополним его функционал до полноценного аддона.
Для превращения скрипта в аддон Blender нужно выполнить 4 обязательных требования Blender API:
- В первую очередь весь код, который на данный момент представляет собой простую последовательность команд, нужно обернуть в класс. API требует, чтобы пользовательские классы обязательно наследовались от нескольких предопределенных классов:
123456bpy.types.Panelbpy.types.Menubpy.types.Operatorbpy.types.PropertyGroupbpy.types.KeyingSetbpy.types.RenderEngineТак же API требует, чтобы все пользовательские классы были статическими, поэтому в них не требуется определять конструктор __init__ и деструктор __del__.
В нашем примере от аддона требуется выполнить действие по созданию меша. В классе bpy.types.Operator определена функцию execute, которая выполняется в момент обращения к классу через API Blender – как раз то, что нужно для выполнения какого-то определенного действия. Поэтому оборачиваем код скрипта в класс и наследуем его от bpy.types.Operator. Переопределяем функцию execute и заносим в нее весь исполняемый код нашего скрипта. Функция execute должна возвращать указание о успешном выполнении {‘FINISHED’}:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849import mathimport bpyclass createTube(bpy.types.Operator):def execute(self, context):n = 8r1 = 1r2 = 0.5h = 1verts = []ring1 = []ring2 = []ring3 = []ring4 = []faces = []for i in range(n):grad = (360 * i / n) * math.pi / 180ring1.append([r1 * math.cos(grad), r1 * math.sin(grad), 0])ring2.append([r2 * math.cos(grad), r2 * math.sin(grad), 0])ring3.append([r1 * math.cos(grad), r1 * math.sin(grad), h])ring4.append([r2 * math.cos(grad), r2 * math.sin(grad), h])if i == 0:faces.append([i - 1 + n, i, i + 2 * n, i - 1 + 3 * n])faces.append([i - 1 + 2 * n, i - 1 + 4 * n, i + 3 * n, i + n])faces.append([i - 1 + n, i - 1 + 2 * n, i + n, i])faces.append([i - 1 + 3 * n, i + 2 * n, i + 3 * n, i - 1 + 4 * n])else:faces.append([i - 1, i, i + 2 * n, i - 1 + 2 * n])faces.append([i - 1 + n, i - 1 + 3 * n, i + 3 * n, i + n])faces.append([i - 1, i - 1 + n, i + n, i])faces.append([i - 1 + 2 * n, i + 2 * n, i + 3 * n, i - 1 + 3 * n])verts.extend(ring1)verts.extend(ring2)verts.extend(ring3)verts.extend(ring4)tubeMesh = bpy.data.meshes.new("Tube")tubeMesh.from_pydata(verts, [], faces)tubeMesh.update()tube = bpy.data.objects.new("Tube", tubeMesh)bpy.context.scene.objects.link(tube)bpy.ops.object.select_all(action="DESELECT")tube.select = Truebpy.context.scene.objects.active = tubereturn {'FINISHED'}Класс, наследующий bpy.types.Operator становится полноправным оператором Blender API.
Кроме функции execute операторы имеют следующие предопределенные переопределяемые функции:
- poll – выполняется перед выполнением самого оператора, и если в ходе ее выполнения произошла ошибка – сам оператор далее не выполняется
- invoke – используется для интерактивных операций, таких как перетаскивание элементов
- draw – вызывается для создания графических элементов, панелей
- modal – используется в операторах, которые требуют периодического вызова, например при разрезании нескольких ребер. Не прерывают работу после возвращения {‘FINISH’}
- cancel – вызывается при отмене выполнения оператора
- Для того, чтобы созданный класс подключился к Blender API, нужно его зарегистрировать, вызвав функцию bpy.utils.register_class(), указав в ее параметрах имя регистрируемого класса:
1 |
bpy.utils.register_class(createTube) |
При завершении работы зарегистрированный класс требуется разрегистрировать:
1 |
bpy.utils.unregister_class(createTube) |
Так как мы оформляем наш класс в виде аддона, операцию регистрации класса нужно выполнить в момент инициализации аддона, а операцию разрегистрации класса – в момент его отключения. Для этого в API предусмотрено следующее условие: если в коде аддона определены функции register и unregister, они будут вызваны: первая при подключении аддона, вторая – при его отключении.
1 2 3 |
def register(): def unregister(): |
Добавим в конец кода нашего аддона эти две функции и проведем в них соответственно регистрацию и разрегистрацию нашего класса:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
import math import bpy class createTube(bpy.types.Operator): def execute(self, context): n = 8 r1 = 1 r2 = 0.5 h = 1 verts = [] ring1 = [] ring2 = [] ring3 = [] ring4 = [] faces = [] for i in range(n): grad = (360 * i / n) * math.pi / 180 ring1.append([r1 * math.cos(grad), r1 * math.sin(grad), 0]) ring2.append([r2 * math.cos(grad), r2 * math.sin(grad), 0]) ring3.append([r1 * math.cos(grad), r1 * math.sin(grad), h]) ring4.append([r2 * math.cos(grad), r2 * math.sin(grad), h]) if i == 0: faces.append([i - 1 + n, i, i + 2 * n, i - 1 + 3 * n]) faces.append([i - 1 + 2 * n, i - 1 + 4 * n, i + 3 * n, i + n]) faces.append([i - 1 + n, i - 1 + 2 * n, i + n, i]) faces.append([i - 1 + 3 * n, i + 2 * n, i + 3 * n, i - 1 + 4 * n]) else: faces.append([i - 1, i, i + 2 * n, i - 1 + 2 * n]) faces.append([i - 1 + n, i - 1 + 3 * n, i + 3 * n, i + n]) faces.append([i - 1, i - 1 + n, i + n, i]) faces.append([i - 1 + 2 * n, i + 2 * n, i + 3 * n, i - 1 + 3 * n]) verts.extend(ring1) verts.extend(ring2) verts.extend(ring3) verts.extend(ring4) tubeMesh = bpy.data.meshes.new("Tube") tubeMesh.from_pydata(verts, [], faces) tubeMesh.update() tube = bpy.data.objects.new("Tube", tubeMesh) bpy.context.scene.objects.link(tube) bpy.ops.object.select_all(action="DESELECT") tube.select = True bpy.context.scene.objects.active = tube return {'FINISHED'} def register(): bpy.utils.register_class(createTube) def unregister(): bpy.utils.unregister_class(createTube) |
- Любой класс, зарегистрированный в Blender API, должен иметь уникальный идентификатор, чтобы можно было к нему обращаться по этому идентификатору.
В качестве идентификатора необходимо использовать строковую константу с предопределенным именем bl_idname. Такая константа обязана присутствовать в любом подключаемом к API Blender классе. Кроме обязательного предопределенного имени константы к ней есть еще одно требование – в ее значении должна присутствовать точка. Скорее всего это сделано для более удобной группировки идентификаторов классов внутри API.
Для определения идентификатора нашего класса создадим такую переменную и присвоим ей значение ‘mesh.create_tube’:
1 |
bl_idname = 'mesh.create_tube' |
После чего функционал (функция execute) нашего класса, который после регистрации в API становится оператором, может быть вызван через:
1 |
bpy.ops.mesh.create_tube() |
Так же в области видимости класса должна быть объявлена текстовая константа bl_label, содержащая в значении текст. Этот текст будет использоваться при отображении панелей или меню с участием нашего класса.
1 |
bl_label = 'Create Tube' |
Определим необходимые константы в классе нашего аддона:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
import math import bpy class createTube(bpy.types.Operator): bl_idname = 'mesh.create_tube' bl_label = 'Create Tube' def execute(self, context): n = 8 r1 = 1 r2 = 0.5 h = 1 verts = [] ring1 = [] ring2 = [] ring3 = [] ring4 = [] faces = [] for i in range(n): grad = (360 * i / n) * math.pi / 180 ring1.append([r1 * math.cos(grad), r1 * math.sin(grad), 0]) ring2.append([r2 * math.cos(grad), r2 * math.sin(grad), 0]) ring3.append([r1 * math.cos(grad), r1 * math.sin(grad), h]) ring4.append([r2 * math.cos(grad), r2 * math.sin(grad), h]) if i == 0: faces.append([i - 1 + n, i, i + 2 * n, i - 1 + 3 * n]) faces.append([i - 1 + 2 * n, i - 1 + 4 * n, i + 3 * n, i + n]) faces.append([i - 1 + n, i - 1 + 2 * n, i + n, i]) faces.append([i - 1 + 3 * n, i + 2 * n, i + 3 * n, i - 1 + 4 * n]) else: faces.append([i - 1, i, i + 2 * n, i - 1 + 2 * n]) faces.append([i - 1 + n, i - 1 + 3 * n, i + 3 * n, i + n]) faces.append([i - 1, i - 1 + n, i + n, i]) faces.append([i - 1 + 2 * n, i + 2 * n, i + 3 * n, i - 1 + 3 * n]) verts.extend(ring1) verts.extend(ring2) verts.extend(ring3) verts.extend(ring4) tubeMesh = bpy.data.meshes.new("Tube") tubeMesh.from_pydata(verts, [], faces) tubeMesh.update() tube = bpy.data.objects.new("Tube", tubeMesh) bpy.context.scene.objects.link(tube) bpy.ops.object.select_all(action="DESELECT") tube.select = True bpy.context.scene.objects.active = tube return {'FINISHED'} def register(): bpy.utils.register_class(createTube) def unregister(): bpy.utils.unregister_class(createTube) |
Если в области видимости класса определяются другие константы, рекомендуется снабжать их все префиксом bl_.
- Для окончательного оформления аддона нужно сделать его описание. Это требование не является в полной мере обязательным, но его все равно стоит выполнять. Написав несколько аддонов без описаний, даже их автору очень сложно разобраться в их назначении. Для составления описания аддона служит словарь с предопределенным именем bl_info, который имеет следующие предопределенные пункты:
- name – название аддона
- author – ФИО автора
- version – версия аддона
- blender – версия Blender под которую разрабатывался аддон
- category – категория, в которую аддон будет помещен
- location – указание на то, где искать панель аддона
- url – указание на исходный код аддона (откуда он распространяется)
- description – строка с более подробным описанием аддона
По минимуму стоит заполнять пункты name, category и blender.
Добавим в начало кода нашего аддона описание для него:
1 2 3 4 5 6 7 8 |
bl_info = { 'name': 'Create Tube Addon', 'author': 'Nikita', 'version': (0, 0, 1), 'blender': (2, 77, 0), 'category': 'Add Mesh', 'description': 'This addon adds tube mesh to scene' } |
Полный текст аддона теперь выглядит так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
bl_info = { 'name': 'Create Tube Addon', 'author': 'Nikita', 'version': (0, 0, 1), 'blender': (2, 77, 0), 'category': 'Add Mesh', 'description': 'This addon adds tube mesh to scene' } import math import bpy class createTube(bpy.types.Operator): bl_idname = 'mesh.create_tube' bl_label = 'Create Tube' def execute(self, context): n = 8 r1 = 1 r2 = 0.5 h = 1 verts = [] ring1 = [] ring2 = [] ring3 = [] ring4 = [] faces = [] for i in range(n): grad = (360 * i / n) * math.pi / 180 ring1.append([r1 * math.cos(grad), r1 * math.sin(grad), 0]) ring2.append([r2 * math.cos(grad), r2 * math.sin(grad), 0]) ring3.append([r1 * math.cos(grad), r1 * math.sin(grad), h]) ring4.append([r2 * math.cos(grad), r2 * math.sin(grad), h]) if i == 0: faces.append([i - 1 + n, i, i + 2 * n, i - 1 + 3 * n]) faces.append([i - 1 + 2 * n, i - 1 + 4 * n, i + 3 * n, i + n]) faces.append([i - 1 + n, i - 1 + 2 * n, i + n, i]) faces.append([i - 1 + 3 * n, i + 2 * n, i + 3 * n, i - 1 + 4 * n]) else: faces.append([i - 1, i, i + 2 * n, i - 1 + 2 * n]) faces.append([i - 1 + n, i - 1 + 3 * n, i + 3 * n, i + n]) faces.append([i - 1, i - 1 + n, i + n, i]) faces.append([i - 1 + 2 * n, i + 2 * n, i + 3 * n, i - 1 + 3 * n]) verts.extend(ring1) verts.extend(ring2) verts.extend(ring3) verts.extend(ring4) tubeMesh = bpy.data.meshes.new("Tube") tubeMesh.from_pydata(verts, [], faces) tubeMesh.update() tube = bpy.data.objects.new("Tube", tubeMesh) bpy.context.scene.objects.link(tube) bpy.ops.object.select_all(action="DESELECT") tube.select = True bpy.context.scene.objects.active = tube return {'FINISHED'} def register(): bpy.utils.register_class(createTube) def unregister(): bpy.utils.unregister_class(createTube) |
Аддон готов в минимальном исполнении. Можно подключить его в Blender, открыть окно Python Console и вызвать оператор командой bpy.ops.mesh.create_tube().
Вызывать созданный функционал через Python Console очень неудобно. Доработаем немного функционал аддона – добавим в меню создания мешей (Shift+a – Mesh) пункт для вызова нашего оператора – создания трубы.
Добавим определение функции, которая будет формировать структуру нужного меню. В нашем случае – одна кнопка с иконкой расширения и меткой нашего оператора (текст будет браться из константы bl_label). Функционал нашего оператора привяжем к этой кнопке.
1 2 |
def addToAddMeshMenu(self, context): self.layout.operator("mesh.create_tube", icon="PLUGIN") |
Объект layout содержит в себе структуру определяемого меню, в которую мы добавили один пункт, связав его с нашим оператором через его bl_idname.
Для того, чтобы этот пункт добавлялся в меню в момент регистрации аддона и удалялся при его разрегистрации, добавим в функции register и unregister нужные команды:
1 2 3 4 5 |
# добавление в меню Add Mesh bpy.types.INFO_MT_mesh_add.append(addToAddMeshMenu) # удаление bpy.types.INFO_MT_mesh_add.remove(addToAddMeshMenu) |
Полный код аддона:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
bl_info = { 'name': 'Create Tube Addon', 'author': 'Nikita', 'version': (0, 0, 1), 'blender': (2, 77, 0), 'category': 'Add Mesh', 'description': 'This addon adds tube mesh to scene' } import math import bpy class createTube(bpy.types.Operator): bl_idname = 'mesh.create_tube' bl_label = 'Create Tube' def execute(self, context): n = 8 r1 = 1 r2 = 0.5 h = 1 verts = [] ring1 = [] ring2 = [] ring3 = [] ring4 = [] faces = [] for i in range(n): grad = (360 * i / n) * math.pi / 180 ring1.append([r1 * math.cos(grad), r1 * math.sin(grad), 0]) ring2.append([r2 * math.cos(grad), r2 * math.sin(grad), 0]) ring3.append([r1 * math.cos(grad), r1 * math.sin(grad), h]) ring4.append([r2 * math.cos(grad), r2 * math.sin(grad), h]) if i == 0: faces.append([i - 1 + n, i, i + 2 * n, i - 1 + 3 * n]) faces.append([i - 1 + 2 * n, i - 1 + 4 * n, i + 3 * n, i + n]) faces.append([i - 1 + n, i - 1 + 2 * n, i + n, i]) faces.append([i - 1 + 3 * n, i + 2 * n, i + 3 * n, i - 1 + 4 * n]) else: faces.append([i - 1, i, i + 2 * n, i - 1 + 2 * n]) faces.append([i - 1 + n, i - 1 + 3 * n, i + 3 * n, i + n]) faces.append([i - 1, i - 1 + n, i + n, i]) faces.append([i - 1 + 2 * n, i + 2 * n, i + 3 * n, i - 1 + 3 * n]) verts.extend(ring1) verts.extend(ring2) verts.extend(ring3) verts.extend(ring4) tubeMesh = bpy.data.meshes.new("Tube") tubeMesh.from_pydata(verts, [], faces) tubeMesh.update() tube = bpy.data.objects.new("Tube", tubeMesh) bpy.context.scene.objects.link(tube) bpy.ops.object.select_all(action="DESELECT") tube.select = True bpy.context.scene.objects.active = tube return {'FINISHED'} def addToAddMeshMenu(self, context): self.layout.operator("mesh.create_tube", icon="PLUGIN") def register(): bpy.utils.register_class(createTube) bpy.types.INFO_MT_mesh_add.append(addToAddMeshMenu) def unregister(): bpy.utils.unregister_class(createTube) bpy.types.INFO_MT_mesh_add.remove(addToAddMeshMenu) |
Если вы установили аддон, его нужно переустановить – удалить нажатием на кнопку Remove в окне User Preferences – Add-ons – Add Mesh: Create Tube Addon и установить заново из дополненного файла.
После установки и активации аддона в меню добавления меша появится новый пункт Create Tube.
При добавлении объектов в сцену, в N-панели обычно выводится панель, в которой указываются параметры создаваемого объекта для присваивания им начальных значений. Добавим такую же панель для установки начальных параметров трубы в наш аддон.
Для того, чтобы при создании трубы в N-панели выводилась панель создаваемого объекта, в описание класса нужно добавить переменную с предопределенным именем bl_options. Эта переменная – нумерованное множество, от содержания которого зависит, какие опции будут доступны оператору. Обязательным значением является “REGISTER” – собственно регистрация опций (это значение используется по умолчанию, когда переменная явно не определена). Добавим в bl_options опцию “UNDO” для того, чтобы оператор был включен в стек отмены – возврата операций (undo – redo) и ему была доступна отдельная панель в N-панели.
1 |
bl_options = {"REGISTER", "UNDO"} |
Если просто добавить эту константу в класс createTube панель будет создаваться, он она останется пустой.
Начальные параметры создаваемой трубы заданы в описании функции execute:
1 2 3 4 |
n = 8 r1 = 1 r2 = 0.5 h = 1 |
- n – количество ребер
- r1 – внешний радиус трубы
- r2 – внутренний радиус трубы
- h – высота трубы
Превратим их из простых переменных в настоящие свойства оператора. Свойства оператора задаются через bpy.props._тип_свойства следующим образом:
1 2 3 4 5 |
property = bpy.props.IntProperty( name = "Name", description = "Description", default = 0 ) |
Таким образом задано свойство property целочисленного типа со значением по умолчанию равным 0. Обращаться к нему внутри функций класса-оператора нужно через указатель self:
1 |
a = self.property |
Зададим начальные параметры нашего оператора в виде свойств:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
n = bpy.props.IntProperty( name = "Edges", description = "Edges count", default = 8, min = 3 ) r1 = bpy.props.FloatProperty( name = "O-Radius", description = "Outer radius", default = 1 ) r2 = bpy.props.FloatProperty( name = "I-Radius", description = "Inner radius", default = 0.5 ) h = bpy.props.FloatProperty( name = "Height", description = "Height", default = 1.0 ) |
Непосредственное задание этих параметров в начале функции execute нужно удалить. Внутри функции execute обращение к этим свойствам осуществлять через self.
Так как входные параметры нашего оператора заданы через bpy.props, при создании меша они автоматически добавляются в панель свойств меша в N-панели и ими можно управлять.
Полный и окончательный код аддона выглядит так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
bl_info = { 'name': 'Create Tube Addon', 'author': 'Nikita', 'version': (0, 0, 1), 'blender': (2, 77, 0), 'category': 'Add Mesh', 'description': 'This addon adds tube mesh to scene' } import math import bpy class createTube(bpy.types.Operator): bl_idname = 'mesh.create_tube' bl_label = 'Create Tube' bl_options = {"REGISTER", "UNDO"} n = bpy.props.IntProperty( name = "Edges", description = "Edges count", default = 8, min = 3 ) r1 = bpy.props.FloatProperty( name = "O-Radius", description = "Outer radius", default = 1 ) r2 = bpy.props.FloatProperty( name = "I-Radius", description = "Inner radius", default = 0.5 ) h = bpy.props.FloatProperty( name = "Height", description = "Height", default = 1.0 ) def execute(self, context): verts = [] ring1 = [] ring2 = [] ring3 = [] ring4 = [] faces = [] for i in range(self.n): grad = (360 * i / self.n) * math.pi / 180 ring1.append([self.r1 * math.cos(grad), self.r1 * math.sin(grad), 0]) ring2.append([self.r2 * math.cos(grad), self.r2 * math.sin(grad), 0]) ring3.append([self.r1 * math.cos(grad), self.r1 * math.sin(grad), self.h]) ring4.append([self.r2 * math.cos(grad), self.r2 * math.sin(grad), self.h]) if i == 0: faces.append([i - 1 + self.n, i, i + 2 * self.n, i - 1 + 3 * self.n]) faces.append([i - 1 + 2 * self.n, i - 1 + 4 * self.n, i + 3 * self.n, i + self.n]) faces.append([i - 1 + self.n, i - 1 + 2 * self.n, i + self.n, i]) faces.append([i - 1 + 3 * self.n, i + 2 * self.n, i + 3 * self.n, i - 1 + 4 * self.n]) else: faces.append([i - 1, i, i + 2 * self.n, i - 1 + 2 * self.n]) faces.append([i - 1 + self.n, i - 1 + 3 * self.n, i + 3 * self.n, i + self.n]) faces.append([i - 1, i - 1 + self.n, i + self.n, i]) faces.append([i - 1 + 2 * self.n, i + 2 * self.n, i + 3 * self.n, i - 1 + 3 * self.n]) verts.extend(ring1) verts.extend(ring2) verts.extend(ring3) verts.extend(ring4) tubeMesh = bpy.data.meshes.new("Tube") tubeMesh.from_pydata(verts, [], faces) tubeMesh.update() tube = bpy.data.objects.new("Tube", tubeMesh) bpy.context.scene.objects.link(tube) bpy.ops.object.select_all(action="DESELECT") tube.select = True bpy.context.scene.objects.active = tube return {'FINISHED'} def addToAddMeshMenu(self, context): self.layout.operator("mesh.create_tube", icon="PLUGIN") def register(): bpy.utils.register_class(createTube) bpy.types.INFO_MT_mesh_add.append(addToAddMeshMenu) def unregister(): bpy.utils.unregister_class(createTube) bpy.types.INFO_MT_mesh_add.remove(addToAddMeshMenu) |
Аддон полностью готов. Можно проводить его установку и активацию. И использовать для быстрого создания трубообразных объектов.
Мини-бонус:
Во многих аддонах в конце кода встречается конструкция:
1 2 |
if __name__ == "__main__" : register() |
Этот код нужен исключительно в процессе разработки аддона. Если код аддона или ссылка на него в IDE набраны в окне Text Editor – при наличии этого участка кода по нажатию на кнопку Run Script будет выполняться активация аддона в Blender, что позволяет сразу же протестировать его работу. В завершенном аддоне этот код совершенно не нужен.
Статья шикарная, но устаревшая – не могли бы вы подправить статью для версии Blender 2.8 и выше.
P.S. В Blender 2.92 данный аддон не регистрируется и Blender просит откорректировать аддон до версии 2.8