毫无疑问,Operator是Blender中最为核心的一个对象,而且里面包罗万象(不仅可以操作几何对象,所有的菜单都可以控制,因为Blender其实就是一个三维图形的超级命令解释器嘛!)。
我们先定义一个简单的Operator。把下面的代码复制到文本编辑中,点击“执行脚本”。
import bpyclass 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工具。
继续,选中“Tool Name”项,并没有出现上面的execute中定义的“Hello World”,怎么回事!?
因为Blender把输出信息定义到标准输出了,如果从控制台窗口启动,将从控制台上看到输出的信息。Blender并没有把信息输出到信息窗口和python控制台,这是一个让图形界面使用者有点困惑的地方,先按下不表,后面再想办法解决。
现在,换一种方式来定义Operator:
import bpyclass 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的类的继承特性。这是官方文档的例子,但我觉得更符合对象的定义应该是:
import bpyclass 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的习惯。测试了下,两种方式倒是都能运行。
上面直接把Operator的执行类注册到系统了,下面采用更通用的做法:
import bpyclass 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_floatimport bpyclass 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 typebpy.types.Object.my_float = bpy.props.FloatProperty()# removedel bpy.types.Object.my_float
对类对象同样适用:
class MyPropGroup(bpy.types.PropertyGroup): passMyPropGroup.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 bpybl_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 bpyclass 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 registrationaddon_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就没有这个问题了。