- Blender Add-on -

Add-on集

ちょっとしたアドオンの作り方がわかってきたので、幾つか公開してみます。

アドオンのお作法などは見よう見まねなので、そこのとこころはご容赦を…

メッシュ(その他データ)の名前をオブジェクトの名前に合わせる

Cube.001 やら Cylinder.002 など元の名前のまま残っているメッシュデータを、オブジェクトの名前に変更します。
2つ以上のオブジェクトで共用されているメッシュデータの場合は、確認用ポップアップが出てくるので、そこで再度押すと実行されます。

DataRenamer.py

# Blender内部のデータ構造にアクセスするために必要
import bpy
import mathutils

# プラグインに関する情報
bl_info = {
    "name" : "DataRenamer",             # プラグイン名
    "author" : "Q@StudioPotpourri",     # 作者
    "version" : (0,4),                  # プラグインのバージョン
    "blender" : (2, 7, 7),              # プラグインが動作するBlenderのバージョン
    "location" : "Properties > Object > DataRenamer",   # Blender内部でのプラグインの位置づけ
    "description" : "Rename mesh",   # プラグインの説明
    "warning" : "",
    "wiki_url" : "",                    # プラグインの説明が存在するWikiページのURL
    "tracker_url" : "",                 # Blender Developer OrgのスレッドURL
    "category" : "Object"                   # プラグインのカテゴリ名
    }

class RenamerForce(bpy.types.Operator):
    """RenamerForce"""
    bl_idname = "object.renamerforce"
    bl_label = "RenameForce"
    bl_options = {'UNDO'}

    def execute(self, context):
        object = context.object
        name = object.name
        object.data.name = name
        
        return {'FINISHED'}

class Renamer(bpy.types.Operator):
    """Renamer"""

    bl_idname = "object.renamer"
    bl_label = "DataRenamer"
    bl_options = {'REGISTER','UNDO'}

    def invoke(self, context, event):
        object = context.object
        name = object.name

        if (object.name == object.data.name):
            return {'CANCELLED'}

        if (object.data.users == 1):
            return self.execute(context)
        else:
            return context.window_manager.invoke_props_popup(self, event)
        
        return {'CANCELLED'}

    def execute(self, context):
        object = context.object
        name = object.name;
        if (object.data.users == 1):
            object.data.name = name;
        
        return {'FINISHED'}

    def draw(self, context):
        layout = self.layout
        object = context.object
        data = context.object.data

        if (object.name == data.name):
            return

        col = layout.column()
        col.label(text="Data \"" + object.data.name + "\" has " + str(object.data.users) + " users.")
        col.operator("object.renamerforce", text="Rename it to \"" + object.name + "\" even so")

class OBJECT_PT_DataRenamer(bpy.types.Panel):
    bl_label = "DataRenamer"
    bl_space_type = "PROPERTIES"
    bl_region_type = "WINDOW"
    bl_context = "object"

    @classmethod
    def poll(cls, context):
        return (context.object.type == 'MESH' or context.object.type == 'LAMP' or context.object.type == 'CAMERA' or context.object.type == "CURVE")

    def draw(self, context):
        
        layout = self.layout
        object = context.object
        data = context.object.data
        
        l_row = layout.row();
        l_row.label(text=object.name, icon='OBJECT_DATA')
        if (object.type == 'MESH'):
            l_row.label(text=data.name, icon='MESH_DATA')
        if (object.type == 'CAMERA'):
            l_row.label(text=data.name, icon='CAMERA_DATA')
        if (object.type == 'CURVE'):
            l_row.label(text=data.name, icon='CURVE_DATA')
        if (object.type == 'LAMP'):
            if (data.type == 'POINT'):
                l_row.label(text=data.name, icon='LAMP_POINT')
            if (data.type == 'SUN'):
                l_row.label(text=data.name, icon='LAMP_SUN')
            if (data.type == 'SPOT'):
                l_row.label(text=data.name, icon='LAMP_SPOT')
            if (data.type == 'HEMI'):
                l_row.label(text=data.name, icon='LAMP_HEMI')
            if (data.type == 'AREA'):
                l_row.label(text=data.name, icon='LAMP_AREA')
            
        if (object.name == data.name and data.users == 1):
            l_row.label(text="users=" + str(data.users), icon='FILE_TICK')
        else:
            l_row.label(text="users=" + str(data.users), icon='DOT')
            if (object.name != data.name):
                layout.operator("object.renamer", text="Rename Single User Data")

def register():
    bpy.utils.register_module(__name__)

def unregister():
    bpy.utils.unregister_module(__name__)

if __name__ == "__main__":
    register()


ランダムに選択解除しながら Extrude を繰り返す

選択されている面をランダムに選択解除しながら Extrude を繰り返し、このような構造を作ります。
メッシュ分割された平面に適応すれば、このように簡易的な都市のような構造になります。
3D View の左側、メッシュツール内に登録されます。繰り返し回数や、一ステップごとの選択解除の確率を設定します。

Type: の選択肢は、上のような形状ではNormal方向へ伸ばすタイプ、下の街のような構造では Z directional を選びます。
(街のような構造の場合Normalでも見た目は一緒なのですが、ブロック境界のポリゴンに無駄が生じます)

RandomExtrude.py

# Blender内部のデータ構造にアクセスするために必要
import bpy
import mathutils
# Tool Self でプロパティ変更
from bpy.props import * 

# プラグインに関する情報
bl_info = {
    "name" : "Random Extrude",             # プラグイン名
    "author" : "Q@StudioPotpourri",                  # 作者
    "version" : (0,3),                  # プラグインのバージョン
    "blender" : (2, 7, 7),              # プラグインが動作するBlenderのバージョン
    "location" : "Mesh > Random Extrude",   # Blender内部でのプラグインの位置づけ
    "description" : "Repeat random deselect and extrude",   # プラグインの説明
    "warning" : "",
    "wiki_url" : "",                    # プラグインの説明が存在するWikiページのURL
    "tracker_url" : "",                 # Blender Developer OrgのスレッドURL
    "category" : "Mesh"                   # プラグインのカテゴリ名
    }

# メニュー
class CRandomExtrude(bpy.types.Operator):

    bl_idname = "mesh.random_extrude"      # ID名
    bl_label = "Random Extrude"            # メニューに表示される文字列
    bl_description = "Random Extrude"      # メニューに表示される説明文
    bl_options = {'REGISTER', 'UNDO'}

    # ツールシェルフへ表示させる値
    repeat = IntProperty(
        name = "Repeat",             
        description = "repeat ...",   
        default = 3,                    
        min = 0,                     
        max = 99)  

    deselectFirst = IntProperty(
        name = "First",    
        description = "Deselect Percent ...", 
        default = 0,
        subtype = 'PERCENTAGE',
        min = 0,                  
        max = 100)                    

    deselect = IntProperty(
        name = "",    
        description = "Deselect Percent ...", 
        default = 40,         
        subtype = 'PERCENTAGE',
        min = 0,                  
        max = 100) 

    seed = IntProperty(
        name = "Random seed",           
        description = "Random Seed ...",
        default = 3,
        min = 0)

    height = FloatProperty(
        name = "Step height",
        description = "lineSkip ...",
        default = 1,
        min = 0)

    Types = [("0", "Normal", "Normal"),
             ("1", "Z directional", "Z directional")]
    
    NormalTypes = EnumProperty(name="Type",
                               description ="Type",
                               items=Types)

    def draw(self, context):
        layout = self.layout;

        layout.prop(self, "repeat")
        layout.label("Deselect Percent:")
        row = layout.row()
        row.prop(self, "deselectFirst")
        row.prop(self, "deselect")

        layout.prop(self, "seed")
        layout.prop(self, "height")
        layout.prop(self, "NormalTypes")
        layout.prop(self, "normal")

    # 実際にプラグインが処理を実施する処理
    def execute(self, context):

        for num in range(0, self.repeat):
            deselect = self.deselect
            if num == 0:
                deselect = self.deselectFirst
            bpy.ops.mesh.select_random(percent=deselect, seed=self.seed, action='DESELECT')
            if int(self.NormalTypes) == 1:
                bpy.ops.mesh.extrude_region_move(TRANSFORM_OT_translate={"value":(0, 0, self.height)})
            else:
                bpy.ops.mesh.extrude_faces_move(TRANSFORM_OT_shrink_fatten={"value":-self.height})

        return {'FINISHED'}             # 成功した場合はFINISHEDを返す

# メニューを登録する関数
def menu_func(self, context):
    self.layout.label("Random Extrude:")
    self.layout.operator(CRandomExtrude.bl_idname)     # 登録したいクラスの「bl_idname」を指定

# プラグインをインストールしたときの処理
def register():
    bpy.utils.register_module(__name__)
    bpy.types.VIEW3D_PT_tools_meshedit.append(menu_func)

# プラグインをアンインストールしたときの処理
def unregister():
    bpy.utils.unregister_module(__name__)
    bpy.types.VIEW3D_PT_tools_meshedit.remove(menu_func)

# メイン関数
if __name__ == "__main__":
    register()

1つのオブジェクト内の選択された頂点に対してMirrorをかける

内部的に行っていることとしては、選択されている頂点を分離して Mirror モディファイアをかけて 元と合成するということになります。
選択の状態を維持したり、既にモディファイアがかかっている場合などでも機能するように幾つかの工夫があります。

一旦分離して合成するという内部の操作上、一続きのつながったメッシュの一部だけを Mirror するときには、形状が分離します。一体化させたいときには Remove Double などで重複した頂点を除去するといった操作が必要になります。
3D View の左側、メッシュツール内に登録されます。Mirrorをかける軸を設定できます。

MirrorSelectedVertices.py

# Blender内部のデータ構造にアクセスするために必要
import bpy
import mathutils
# Tool Self でプロパティ変更
from bpy.props import * 

# プラグインに関する情報
bl_info = {
    "name" : "Mirror Selected Vertices",# プラグイン名
    "author" : "Q@SturioPotpourri",     # 作者
    "version" : (0,8),                  # プラグインのバージョン
    "blender" : (2, 7, 8),              # プラグインが動作するBlenderのバージョン
    "location" : "Mesh > Mirror Selected",   # Blender内部でのプラグインの位置づけ
    "description" : "Mirror Selected Vertices",   # プラグインの説明
    "warning" : "",
    "wiki_url" : "",                    # プラグインの説明が存在するWikiページのURL
    "tracker_url" : "",                 # Blender Developer OrgのスレッドURL
    "category" : "Mesh"                   # プラグインのカテゴリ名
    }

def diff(first, second):
    second = set(second)
    return [item for item in first if item not in second]

# メニュー
class CMirrorSelectedVertices(bpy.types.Operator):

    bl_idname = "mesh.mirror_selected_vertices"           # ID名
    bl_label = "Mirror Selected"            # メニューに表示される文字列
    bl_description = "Mirror Selected"        # メニューに表示される説明文
    bl_options = {'REGISTER', 'UNDO'}

    Types = [("0", "X", "X axis mirror"),
             ("1", "Y", "Y axis mirror"),
	     ("2", "Z", "Z axis mirror")]

    AxisTypes = EnumProperty(name="Axis",
			     description ="Axis",
			     items=Types)

    def draw(self, context):
	    layout = self.layout;
	    layout.prop(self, "AxisTypes")

    # 実際にプラグインが処理を実施する処理
    def execute(self, context):
        
        obj = context.object;
        mesh = obj.data

        bpy.ops.object.mode_set(mode='OBJECT')

        # Active のみ Selected に & 現状復帰用
        selectedObj = [o for o in bpy.data.objects if o.select]
        for o in selectedObj:
            if (o != obj):
                o.select = False
        
        # 頂点数チェック & 選択されたもの復帰用
        selected = [v.index for v in mesh.vertices if v.select]
        selectedNum = len(selected)
        selectedFace = [f.index for f in mesh.polygons if f.select]
        selectedNumFace = len(selectedFace)
        selectedEdge = [e.index for e in mesh.edges if e.select]
        selectedNumEdge = len(selectedEdge)
        
        if (selectedNum == 0):
            print("Selected")
            bpy.ops.object.mode_set(mode='EDIT')
            return {'CANCELLED'}

        # モディファイアの順番調整用
        modifierlist = obj.modifiers[:]
        modifiershow = [mod.show_viewport for mod in modifierlist]
        for mod in modifierlist:
            mod.show_viewport = False

        meshlist = bpy.data.objects[:]

        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.mesh.separate();
        bpy.ops.object.mode_set(mode='OBJECT')

        meshlist2 = bpy.data.objects[:]

        differenceB = diff(meshlist2, meshlist)

        separated = differenceB[0]
        separatedMesh = separated.data

        bpy.context.scene.objects.active = separated

        modnum = len(modifierlist)
        
        modifierlist1 = separated.modifiers[:]
        bpy.ops.object.modifier_add(type="MIRROR")
        modifierlist2 = separated.modifiers[:]

        differenceC = diff(modifierlist2, modifierlist1);

        mirrorName = differenceC[0].name

        for index in range (0, modnum):
            bpy.ops.object.modifier_move_up(modifier=mirrorName)
            
        type = int(self.AxisTypes)

        if (type == 0):
            separated.modifiers[mirrorName].use_x = True
            separated.modifiers[mirrorName].use_y = False
            separated.modifiers[mirrorName].use_z = False
            
        if (type == 1):
            separated.modifiers[mirrorName].use_x = False
            separated.modifiers[mirrorName].use_y = True
            separated.modifiers[mirrorName].use_z = False

        if (type == 2):
            separated.modifiers[mirrorName].use_x = False
            separated.modifiers[mirrorName].use_y = False
            separated.modifiers[mirrorName].use_z = True

        separated.modifiers[mirrorName].use_mirror_merge = False
       
        bpy.ops.object.modifier_apply(modifier=mirrorName)

        bpy.context.scene.objects.active = obj

        bpy.ops.object.join()
        bpy.data.meshes.remove(separatedMesh)

        for vindex in range(0, selectedNum*2):
            mesh.vertices[vindex].select = True

        for eindex in range(0, selectedNumEdge*2):
            mesh.edges[eindex].select = True

        for findex in range(0, selectedNumFace*2):
            mesh.polygons[findex].select = True

        #print(selectedNum, selectedNumFace, selectedNumEdge)
            
        for (mod, show) in zip(modifierlist, modifiershow):
            mod.show_viewport = show

        for o in selectedObj:
            if (o != obj):
                o.select = True
                
        bpy.ops.object.mode_set(mode='EDIT')

        return {'FINISHED'}


# メニューを登録する関数
def menu_func(self, context):
    self.layout.operator(CMirrorSelectedVertices.bl_idname)     # 登録したいクラスの「bl_idname」を指定

# プラグインをインストールしたときの処理
def register():
    bpy.utils.register_module(__name__)
    bpy.types.VIEW3D_PT_tools_meshedit.append(menu_func)

# プラグインをアンインストールしたときの処理
def unregister():
    bpy.utils.unregister_module(__name__)
    bpy.types.VIEW3D_PT_tools_meshedit.remove(menu_func)

# メイン関数
if __name__ == "__main__":
    register()

inserted by FC2 system