ふと思い立ち、Blender + Pythonの勉強がてらアドオンを作りを始めました。
作りきれてちょ〜嬉しいので、はじめて作ったObject系アドオン「TQ Circular Array」をお披露目します。
なお、記事執筆時点では「Blender 2.8 beta(macOS版)April 24, 03:02:20 – 1b839e85e142」で動作確認しています。
TQ Circular Arrayの紹介
機能
- 対象となるオブジェクト位置を中心として(Cursorではない)
- X/Y/Z軸に対し
- 指定した半径の円周上に
- オブジェクトを同じ大きさで複製する
その名も「TQ Circular Array」です。TQは「Takashi Q. Hanamura Photography」からTとQを持ってきただけ(笑)
同じことを標準機能でやるには手間がかかります。しかも、Array前に拡大縮小や回転していると(状態を確定していないと)、Array数を増やすたびに縮小したり回転してしまいます。
標準機能はおそらく「そういう機能」なのでしょうが、僕は単純に複製されるのがしっくりくるので、ボタンひとつで単純に複製されるアドオンを作りました。
紹介画像
▼円盤状の平面に
▼楔を等間隔に打ち込む、みたいな使い方ができます。
紹介動画
ソースコード(2019.4.26版)
'''
対象のオブジェクト位置を中心として
X/Y/Z軸に対して
指定した半径の円周上にオブジェクトを
同じ大きさで複製する
'''
import bpy
from bpy.props import (
EnumProperty,
FloatProperty,
IntProperty,
PointerProperty
)
from bpy.types import Panel
import math
bl_info = {
"name": "TQ Circular Array",
"description": "Make an array in a circular shape",
"author": "Takashi Q. Hanamura",
"version": (0, 1, 0, 0),
"blender": (2, 80, 0),
"support": "TESTING", # テスト版
"category": "Object",
"location": "View3D > Sidebar",
"warning": "",
"wiki_url": "",
"tracker_url": ""
}
# Array対象オブジェクト
bpy.types.Scene.CircularArray_target = PointerProperty(
type=bpy.types.Object)
# 回転軸(デフォルト:X軸)
bpy.types.Scene.CircularArray_axis = bpy.props.EnumProperty(
items=[("x", "X", "", 1), ("y", "Y", "", 2), ("z", "Z", "", 3)],
description="Axis of rotation to make an array")
# 回転半径
bpy.types.Scene.CircularArray_radius = bpy.props.FloatProperty(
default=5,
min=0.001,
description="Array radius")
# 複製する個数
bpy.types.Scene.CircularArray_count = bpy.props.IntProperty(
default=2,
min=1,
max=50,
description="Array count")
# 定数
EMPTY = "tq_circle_empty" # Array用Emptyオブジェクト名
# Operationクラス
class TQ_OT_CircularArray_Operator(bpy.types.Operator):
bl_idname = "object.circular_array"
bl_label = "Circular Array"
bl_description = "Make an array in a circular shape"
bl_options = {'REGISTER', 'UNDO'}
def draw(self, context):
layout = self.layout
layout.prop(self, "CircularArray_target")
def execute(self, context):
# Array対象オブジェクト
target_ob = bpy.context.scene.CircularArray_target
if target_ob is not None:
# Array回転軸
axis = bpy.context.scene.CircularArray_axis
# Array半径
radius = bpy.context.scene.CircularArray_radius
# Array数
count = bpy.context.scene.CircularArray_count
# Array対象オブジェクトをアクティブ
bpy.ops.object.select_all(action="DESELECT")
target_ob.select_set(state=True)
bpy.context.view_layer.objects.active = target_ob
# Array対象オブジェクトが等倍で複製されるよう現在の変形状態を確定
bpy.ops.object.transform_apply(
location=False,
rotation=True,
scale=True)
# Array対象オブジェクトをEDITモードで変形(移動)
trans_x, trans_y, trans_z = 0, 0, 0
if axis == 'x':
trans_y = radius
rotation_axis = 0 # X軸
elif axis == 'y':
trans_z = radius
rotation_axis = 1 # Y軸
else:
trans_x = radius
rotation_axis = 2 # Z軸
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.transform.translate(
value=(trans_x, trans_y, trans_z),
orient_type="GLOBAL",
orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)),
orient_matrix_type="GLOBAL",
constraint_axis=(False, False, False),
mirror=True,
proportional="DISABLED",
proportional_edit_falloff="SMOOTH",
proportional_size=1
)
# Array対象オブジェクトをOBJECTモードで変形(回転)
bpy.ops.object.mode_set(mode="OBJECT")
rad = float(360 / count) * (math.pi / 180)
bpy.context.object.rotation_euler[rotation_axis] = rad
# Array Modifierを追加
bpy.context.scene.cursor.location = bpy.context.object.location
bpy.ops.object.origin_set(
type="ORIGIN_CURSOR",
center="MEDIAN"
)
array_mod = target_ob.modifiers.new(
type="ARRAY",
name="TQ_Circular_Array"
)
array_mod.use_relative_offset = False
array_mod.use_object_offset = True
array_mod.count = count
# Emptyオブジェクトを追加
circle_empty = bpy.data.objects.new(EMPTY, None)
bpy.context.scene.collection.objects.link(circle_empty)
circle_empty.location = target_ob.location
circle_empty.empty_display_size = 1
circle_empty.empty_display_type = "ARROWS"
array_mod.offset_object = circle_empty
# 親子設定
bpy.data.objects[circle_empty.name].select_set(state=True)
bpy.ops.object.parent_set(type="OBJECT", keep_transform=False)
bpy.data.objects[circle_empty.name].select_set(state=False)
else:
# エラー
self.report({'WARNING'}, "Please select object.")
return {'FINISHED'}
# Panelクラス
class TQ_PT_CircularArray_Panel(bpy.types.Panel):
bl_label = "Circular Array"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "Circular Array"
bl_context = "objectmode"
def draw(self, context):
layout = self.layout
# Array対象のオブジェクト
row = layout.row()
row.prop_search(context.scene, "CircularArray_target",
context.scene, "objects", text="Target")
# Array回転軸
row = layout.row()
row.prop(context.scene, "CircularArray_axis",
text="Mirror Axis", expand=True)
# Array半径
row = layout.row()
row.prop(context.scene, "CircularArray_radius",
text="Radius")
# Array数
row = layout.row()
row.prop(context.scene, "CircularArray_count",
text="Count")
# 実行ボタン
layout.separator()
row = layout.row()
row.operator(TQ_OT_CircularArray_Operator.bl_idname,
text="Array")
# クラス一覧
classes = (
TQ_OT_CircularArray_Operator,
TQ_PT_CircularArray_Panel
)
# クラスの登録
def register():
for cls in classes:
bpy.utils.register_class(cls)
# クラスの解除
def unregister():
for cls in classes:
bpy.utils.unregister_class(cls)
# Add-On Entry
if __name__ == "__main__":
register()
課題
(1)リアルタイム反映させたい
▼元ネタとなったFast CarveのCircle Arrayでは専用のウィジェットでArray数を増減できます。それが即座に結果に反映されます。
これやりたいんですが今の自分の力ではできず・・・要調査です。
(2)複数つなげられない
BlenderのModifierは複数のModifierをつなげられます。たとえばWireframe → Subdivision Surface → Boolean・・・のように。
TQ Circular Arrayは標準のArray Modifierを使っていますが、TQ Circular Array → TQ Circular Array・・・とつなげていくと意図しない複製がなされます。
▼一度TQ Circular Arrayして作られたオブジェクト。これをZ軸に対して複製すると
▼こうなるのが僕の想定する動作です。
▼しかし、実際にはしっちゃかめっちゃかになります(笑)
TQ Circular Arrayを連続で使いたい場合は一度Applyしてからにしてください。
(これ、なんとかできるのかしら・・・)
偶然の産物
ちなみに、しっちゃかめっちゃか状態でチョチョイとやるとこのような動画が作れます。偶然見つけたんですが、Generative Artとしては使えるかも。
おわりに
課題はあるもののひとまず作り切って大満足!
アドオンを使ってモデリングするというより、「ただ作りたい」という気持ちの方が強かったりします(笑)そんなんでもいいよね。
数年前、松本生活時代にPythonを始めたものの、具体的にこういうのが作りたい → 作ったにまで達したものは実は少ないのです。
今回ず〜っとやりたいと思っていたCGに活かすことができました。長い間点と点だったものが繋がった瞬間でもあるんです。
そういうことも含め、思い出深いアドオン開発になりました。








