毫無疑問,Operator是Blender中最爲核心的一個對象,並且裏面一應俱全(不只能夠操做幾何對象,全部的菜單均可以控制,由於Blender其實就是一個三維圖形的超級命令解釋器嘛!)。python
咱們先定義一個簡單的Operator。把下面的代碼複製到文本編輯中,點擊「執行腳本」。
shell
import bpy class SimpleOperator(bpy.types.Operator): bl_idname = "object.simple_operator" bl_label = "Tool Name" def execute(self, context): print("Hello World") return {'FINISHED'} bpy.utils.register_class(SimpleOperator)
在三維視窗中,鼠標點擊,按下空格鍵,輸入「tool」,將會列出這個「Tool Name」的Operator工具。app
繼續,選中「Tool Name」項,並無出現上面的execute中定義的「Hello World」,怎麼回事!?ide
由於Blender把輸出信息定義到標準輸出了,若是從控制檯窗口啓動,將從控制檯上看到輸出的信息。Blender並無把信息輸出到信息窗口和python控制檯,這是一個讓圖形界面使用者有點困惑的地方,先按下不表,後面再想辦法解決。工具
如今,換一種方式來定義Operator:
測試
import bpy class BaseOperator: def execute(self, context): print("Hello World BaseClass") return {'FINISHED'} class SimpleOperator(bpy.types.Operator, BaseOperator): bl_idname = "object.simple_operator" bl_label = "Tool Name" bpy.utils.register_class(SimpleOperator)
這裏用到了python的類的繼承特性。這是官方文檔的例子,但我以爲更符合對象的定義應該是:this
import bpy class BaseOperator: bl_idname = "object.simple_operator" bl_label = "Tool subclass." class SimpleOperator(BaseOperator,bpy.types.Operator): def execute(self, context): print("Hello World,i am subclass.") return {'FINISHED'} bpy.utils.register_class(SimpleOperator)
酸菜蘿蔔,各有所愛。一種是行爲優先,符合腳本化思路;第二種是標識優先,符合OO的習慣。測試了下,兩種方式卻是都能運行。spa
上面直接把Operator的執行類註冊到系統了,下面採用更通用的作法:code
import bpy class SimpleOperator(bpy.types.Operator): """ See example above """ def register(): bpy.utils.register_class(SimpleOperator) def unregister(): bpy.utils.unregister_class(SimpleOperator) if __name__ == "__main__": register()
一個更綜合一點的例子:對象
# Create new property group with a sub property # bpy.data.materials[0].my_custom_props.sub_group.my_float import bpy class MyMaterialSubProps(bpy.types.PropertyGroup): my_float = bpy.props.FloatProperty() class MyMaterialGroupProps(bpy.types.PropertyGroup): sub_group = bpy.props.PointerProperty(type=MyMaterialSubProps) def register(): bpy.utils.register_class(MyMaterialSubProps) bpy.utils.register_class(MyMaterialGroupProps) bpy.types.Material.my_custom_props = bpy.props.PointerProperty(type=MyMaterialGroupProps) def unregister(): del bpy.types.Material.my_custom_props bpy.utils.unregister_class(MyMaterialGroupProps) bpy.utils.unregister_class(MyMaterialSubProps) if __name__ == "__main__": register()
由於「一切皆對象」,Python腳本還能夠建立動態類型。以下:
# add a new property to an existing type bpy.types.Object.my_float = bpy.props.FloatProperty() # remove del bpy.types.Object.my_float
對類對象一樣適用:
class MyPropGroup(bpy.types.PropertyGroup): pass MyPropGroup.my_float = bpy.props.FloatProperty() #等價於下面的語法: class MyPropGroup(bpy.types.PropertyGroup): my_float = bpy.props.FloatProperty()
動態定義的對象:
for i in range(10): idname = "object.operator_%d" % i def func(self, context): print("Hello World", self.bl_idname) return {'FINISHED'} opclass = type("DynOp%d" % i,(bpy.types.Operator, ), {"bl_idname": idname, "bl_label": "Test", "execute": func}, ) bpy.utils.register_class(opclass)
有點頭大,很少解釋了。運行下看:
>>> bpy.ops.object.operator_1() Hello World OBJECT_OT_operator_1 {'FINISHED'} >>> bpy.ops.object.operator_2() Hello World OBJECT_OT_operator_2 {'FINISHED'}
如今,咱們建立一個操做幾何對象的Addon:
import bpy bl_info = { "name": "Move X Axis", "category": "Object", } class ObjectMoveX(bpy.types.Operator): """My Object Moving Script""" # blender will use this as a tooltip for menu items and buttons. bl_idname = "object.move_x" #unique identifier for buttons and menu items to reference. bl_label = "Move X by One" # display name in the interface. bl_options = {'REGISTER', 'UNDO'} # enable undo for the operator. # execute() is called by blender when running the operator. def execute(self, context): # The original script scene = context.scene for obj in scene.objects: obj.location.x += 1.0 return {'FINISHED'} #this lets blender know the operator finished successfully. def register(): bpy.utils.register_class(ObjectMoveX) def unregister(): bpy.utils.unregister_class(ObjectMoveX) # This allows you to run the script directly from blenders text editor # to test the addon without having to install it. if __name__ == "__main__": register()
一個擴展了菜單和快捷鍵映射的例子:
bl_info = { "name": "Cursor Array", "category": "Object",} import bpy class ObjectCursorArray(bpy.types.Operator): """Object Cursor Array""" bl_idname = "object.cursor_array" bl_label = "Cursor Array" bl_options = {'REGISTER', 'UNDO'} total = bpy.props.IntProperty(name="Steps", default=2, min=1, max=100) def execute(self, context): scene = context.scene cursor = scene.cursor_location obj = scene.objects.active for i in range(self.total): obj_new = obj.copy() scene.objects.link(obj_new) factor = i / self.total obj_new.location = (obj.location * factor) + (cursor * (1.0 - factor)) return {'FINISHED'} def menu_func(self, context): self.layout.operator(ObjectCursorArray.bl_idname) # store keymaps here to access after registration addon_keymaps = [] def register(): bpy.utils.register_class(ObjectCursorArray) bpy.types.VIEW3D_MT_object.append(menu_func) # handle the keymap wm = bpy.context.window_manager # Note that in background mode (no GUI available), # keyconfigs are not available either, so we have to check this # to avoid nasty errors in background case. kc = wm.keyconfigs.addon if kc: km = wm.keyconfigs.addon.keymaps.new(name='Object Mode', space_type='EMPTY') kmi = km.keymap_items.new(ObjectCursorArray.bl_idname, 'SPACE', 'PRESS', ctrl=True, shift=True) kmi.properties.total = 4 addon_keymaps.append((km, kmi)) def unregister(): # Note: when unregistering, it's usually good practice to do it in reverse order you registered. # Can avoid strange issues like keymap still referring to operators already unregistered... # handle the keymap for km, kmi in addon_keymaps: km.keymap_items.remove(kmi) addon_keymaps.clear() bpy.utils.unregister_class(ObjectCursorArray) bpy.types.VIEW3D_MT_object.remove(menu_func) if __name__ == "__main__": register()
複製到文本編輯框,點擊「運行腳本」運行下看:
運行這個菜單項,結果以下:
當選中之後,能夠選擇一次建立多少個Cube對象。
注意:直接運行這個腳本,將會致使菜單項屢次加載。可是放到Addon就沒有這個問題了。