ThinkSSL🔒 一键申购 5分钟快速签发 30天无理由退款 购买更放心 广告
*NOTE: This document uses Python global in a way which is bad practice, this document should be edited to remove misuse of globals --Ideasman42 12:48, 9 September 2013 (CEST).* 注意:这个文档使用了大量的Python全局变量,这是一种不好的做法。此文档应该被编辑以消除滥用全局变量 --Ideasman42 12:48, 9 September 2013 (CEST). # **Interface</br>用户接口** ----- *Most scripts need to communicate with the user in some way. A script may be invoked from a menu or from a button in a panel, and it may take its input from sliders, checkboxes, drop-down menus or inputs boxes. User interface elements are implemented as Python classes. Two types of interface elements are discussed in these notes:* 大多数脚本需要以某种方式与用户进行沟通。可以从菜单调用一个脚本或是面板中的一个按钮,可能是一个滑块输入、复选框、下拉菜单或输入框。用户界面元素作为Python类来实现。以下讨论了两个界面元素的类型: * *A panel is a class derived from bpy.types.Panel. It has properties and a draw function, which is called every time the panel is redrawn.*</br>一个面板,由bpy.types.Panel驱动。它具有属性和draw函数,每次调用的时候都会被重新绘制。 * *An operator is a class derived from bpy.types.Operator. It has properties, an execute function, and optionally an invoke function. Operators can be registred to make them appear in menus. In particular, a button is an operator. When you press the button, its execute function is called.*</br>一个Operators由bpy.types.Operator驱动。它包含参数,execute函数和可选调用函数。Operators可以被注册使其出现在菜单上。特别说明,一个按钮就是Operators,当你按下按钮的时候,它就会调用execute函数。 *Both panels and operators must be registered before they can be used. The simplest way to register everything in a file is to end it with a call to bpy.utils.register_module(__name__).* operators和panels都需要先注册才能使用。一般注册任何东西都会在代码的最后调用bpy.utils.register_module(__name__)。 *The interface part of the API is probably less stable than other parts, so the code in this section may break in future releases.* 这部分的API与其他相比可能会有一些的不稳定,所以这部分代码很可能会在以后失效。 ## **Panels and buttons</br>面板和按钮** *This program adds five different panels to the user interface in different places. Each panel has a name and a button. The same operator is used for all buttons, but the text on it is can be changed with the text argument. When you press the button, Blender prints a greeting in the terminal.* 在这个程序里面会添加五个不同的面板到不同的UI上。每个面板都有一个名字和按钮。所有的按钮都执行相同的操作,但是文本会受到文本参数的改变。当你按下按钮的时候,Blender会在终端输出欢迎信息。 *The button operator can be invoked without arguments, as in the first panel:* 在第一个面板里面,按钮会调用无参数的操作。 `````python self.layout.operator("hello.hello") ````` *Blender will then search for an operator with the bl_idname hello.hello and place it in the panel. The text on the button defaults to its bl_label, i.e. Say Hello. The OBJECT_OT_HelloButton class also has a custom string property called country. It can be used for passing arguments to the button. If the operator is invoked without argument, the country property defaults to the empty string.* Blender会搜索带有包含bl_idname为hello.hello的operator并且将其放到面板上。在按钮上的文字默认由bl_label记录,Say Hello。OBJECT_OT_HelloButton类包含了一个名为country的自定义字符串参数。他可以用于传递参数到按钮。如果operator调用没有参数,则country属性默认为空字符串。 *A bl_idname must be a string containing only lowercase letters, digits and underscores, plus exactly one dot; hello.hello satisfies these criteria. Apart from that there are apparently no restrictions in the bl_idname.* bl_idname是必须只包含小写字母的字符串,数字和下划线,以及一个点;hello.hello 满足这些标准。除此之外bl_idname没有其他限制 *The button's default appearance and behaviour can be modified. Let us invoke the button in the following way:* 该按钮的默认外观和行为可以被修改。我们用下面的方法来调用按钮。 `````python self.layout.operator("hello.hello", text='Hej').country = "Sweden" ````` *The text on this button is Hej, and the value of the country property is "Sweden". When we press this button, Blender prints the following to the terminal window.* 下一个按钮是Hej,而country属性的值是 "Sweden"。当我们按下这个按钮的时候,Blender会在终端输出下面的内容。 > Hello world from Sweden! *At the end of the file, everything is registered with a call to* 在文件的最后,通过调用下面的代码注册所有的东西。 `````python bpy.utils.register_module(__name__) ````` *Our newly defined button operator can now be used as any other Blender operator. Here is a session from the Blender's python console:* 我们新定义的按钮操作现在可以被任何Blender操作所调用。这是一个从Blender的Python控制台发起的会话: ~~~ >>> bpy.ops.hello.hello(country = "USA") Hello world from USA! {'FINISHED'} ~~~ *Another way to invoke our new operator is to hit **【Space】**. A selector with all available operators pops up at the mouse location. Prune the selection by typing a substring of our operator's bl_label in the edit box. The operator with default parameters is executed and Hello world! is printed in the terminal window.* 其他调用我们新的操作的方法是按下**【空格键】**,一个可用的操作选择器会从我们的鼠标位置弹出。修改编辑框输入操作的bl_label的字串(???)。操作将会执行默认参数并在终端窗口输出Hello world! ![](https://box.kancloud.cn/615b8568839ea36c77ed5b5fd3c34789_1182x643.png) `````python #---------------------------------------------------------- # File hello.py #---------------------------------------------------------- import bpy # # Menu in tools region # Tools菜单区域 # class ToolsPanel(bpy.types.Panel): bl_label = "Hello from Tools" bl_space_type = "VIEW_3D" bl_region_type = "TOOLS" def draw(self, context): self.layout.operator("hello.hello") # # Menu in toolprops region # Toolprops 菜单区域 # class ToolPropsPanel(bpy.types.Panel): bl_label = "Hello from Tool props" bl_space_type = "VIEW_3D" bl_region_type = "TOOL_PROPS" def draw(self, context): self.layout.operator("hello.hello", text='Hej').country = "Sweden" # # Menu in UI region # UI菜单区域 # class UIPanel(bpy.types.Panel): bl_label = "Hello from UI panel" bl_space_type = "VIEW_3D" bl_region_type = "UI" def draw(self, context): self.layout.operator("hello.hello", text='Servus') # # Menu in window region, object context # Window菜单,Object分页 # class ObjectPanel(bpy.types.Panel): bl_label = "Hello from Object context" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "object" def draw(self, context): self.layout.operator("hello.hello", text='Bonjour').country = "France" # # Menu in window region, material context # Window菜单,材质分页 # class MaterialPanel(bpy.types.Panel): bl_label = "Hello from Material context" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "material" def draw(self, context): self.layout.operator("hello.hello", text='Ciao').country = "Italy" # # The Hello button prints a message in the console # Hello button在控制台输出信息 # class OBJECT_OT_HelloButton(bpy.types.Operator): bl_idname = "hello.hello" bl_label = "Say Hello" country = bpy.props.StringProperty() def execute(self, context): if self.country == '': print("Hello world!") else: print("Hello world from %s!" % self.country) return{'FINISHED'} # # Registration # All panels and operators must be registered with Blender; otherwise # they do not show up. The simplest way to register everything in the # file is with a call to bpy.utils.register_module(__name__). # 注册 # 所有面板和操作都必须在Blender内注册,否则他们将不会被显示。最简单 # 的注册所有东西的方法是在文件的最后调用bpy.utils.register_module(__name__) # bpy.utils.register_module(__name__) ````` *The Filebrowser space requires a CHANNELS bl_region_type:* 在文件浏览器bl_region_type通道: `````python import bpy class FILEBROWSER_PT_hello(bpy.types.Panel): bl_label = "Hello World Filebrowser Panel" bl_space_type = "FILE_BROWSER" bl_region_type = "CHANNELS" def draw(self, context): layout = self.layout obj = context.object row = layout.row() row.label(text="Hello world!", icon='WORLD_DATA') def register(): bpy.utils.register_class(FILEBROWSER_PT_hello) def unregister(): bpy.utils.unregister_class(FILEBROWSER_PT_hello) if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) ````` ## **Panel layout and several arguments</br>面板布局和一些参数** *This program illustrates how to organize your panel layout. When the script is run, a panel is created in the tool props area, with buttons placed in a non-trivial fashion. * 该程序说明如何组织你的面板布局。当程序执行,一个面板会被创建在 tool props 区域,其中有一些按钮以不凡的方式被放置。 ![](https://box.kancloud.cn/80cd1e0e0925a98b76f7e50c885a30af_380x494.png) *The script also shows one method to send several arguments to an operator. The OBJECT_OT_Button class has two properties, number and row, and prints the values of these properties to the terminal. Being integer properties, they both default to 0 if not set. Thus, if we press buttons 7, 8 and 23, the script prints:* 这个代码还展示了一个方法去给操作传输一些参数。OBJECT_OT_Button类包含两个属性,number和row,并且把这些属性打印到终端。作为整数属性,如果没有设置他们默认值都是0。因此,如果我们按下按钮7,8和23,这份脚本会输出: >Row 0 button 7 >Row 3 button 0 >Row 0 button 0 *But what if we want to set both the number and row properties, i.e. invoke the operator with two arguments? This can not be done directly, but we can create a third property loc, which is a string that is parsed by the operator if non zero. If we press button 13, the script prints:* 但是如果我们希望设置number和row属性,例如,调用操作的时候带有两个参数该如何实现?这并不能直接完成,但是我们可以创建第三方属性loc,这是一个如果非零将不被操作解析的字符串,如果我们按下按钮13,则脚本会输出: >Row 4 button 13 `````python #---------------------------------------------------------- # File layout.py #---------------------------------------------------------- import bpy # Layout panel class LayoutPanel(bpy.types.Panel): bl_label = "Panel with funny layout" bl_space_type = "VIEW_3D" bl_region_type = "TOOL_PROPS" def draw(self, context): layout = self.layout layout.label("First row") row = layout.row(align=True) row.alignment = 'EXPAND' row.operator("my.button", text="1").number=1 row.operator("my.button", text="2", icon='MESH_DATA').number=2 row.operator("my.button", icon='LAMP_DATA').number=3 row = layout.row(align=False) row.alignment = 'LEFT' row.operator("my.button", text="4").number=4 row.operator("my.button", text="", icon='MATERIAL').number=5 row.operator("my.button", text="6", icon='BLENDER').number=6 row.operator("my.button", text="7", icon='WORLD').number=7 layout.label("Third row", icon='TEXT') row = layout.row() row.alignment = 'RIGHT' row.operator("my.button", text="8").row=3 row.operator("my.button", text="9", icon='SCENE').row=3 row.operator("my.button", text="10", icon='BRUSH_INFLATE').row=3 layout.label("Fourth row", icon='ACTION') row = layout.row() box = row.box() box.operator("my.button", text="11", emboss=False).loc="4 11" box.operator("my.button", text="12", emboss=False).loc="4 12" col = row.column() subrow = col.row() subrow.operator("my.button", text="13").loc="4 13" subrow.operator("my.button", text="14").loc="4 14" subrow = col.row(align=True) subrow.operator("my.button", text="15").loc="4 15" subrow.operator("my.button", text="16").loc="4 16" box = row.box() box.operator("my.button", text="17").number=17 box.separator() box.operator("my.button", text="18") box.operator("my.button", text="19") layout.label("Fifth row") row = layout.row() split = row.split(percentage=0.25) col = split.column() col.operator("my.button", text="21").loc="5 21" col.operator("my.button", text="22") split = split.split(percentage=0.3) col = split.column() col.operator("my.button", text="23") split = split.split(percentage=0.5) col = split.column() col.operator("my.button", text="24") col.operator("my.button", text="25") # Button class OBJECT_OT_Button(bpy.types.Operator): bl_idname = "my.button" bl_label = "Button" number = bpy.props.IntProperty() row = bpy.props.IntProperty() loc = bpy.props.StringProperty() def execute(self, context): if self.loc: words = self.loc.split() self.row = int(words[0]) self.number = int(words[1]) print("Row %d button %d" % (self.row, self.number)) return{'FINISHED'} # Registration bpy.utils.register_module(__name__) ````` ## **Panel properties</br>面板属性** *Properties were discussed in section Properties, but we did not explain how to display custom properties in a panel. This script does exactly that. An RNA property is displayed with the syntax* 参数曾在部分参数中讨论过,但是我们仍不知道如何在面板显示自定义参数。这份代码会恰好实现这个。一个RNA属性将会被带语法的显示 ````` layout.prop(ob, 'myRnaInt') ````` *An ID property is displayed with* 一个ID参数将会显示为 `````python layout.prop(ob, '["myRnaInt"]') ````` *Note that the panel is registered explicitly with bpy.utils.register_class(MyPropPanel) instead of using register_module to register everything. Which method is used does not matter in this example, because MyPropPanel is the only thing that needs to be registred.* 请注意面板使用bpy.utils.register_class(MyPropPanel)注册而非register_module注册。在这个例子里面使用什么方法并不重要,因为MyPropPanel是唯一需要注册的东西。 ![](https://box.kancloud.cn/ab714456823215af44f213c108162f16_1182x464.png) `````python #---------------------------------------------------------- # File panel_props.py #---------------------------------------------------------- import bpy from bpy.props import * # Clean the scene and create some objects # 清除场景并创建一些对象 bpy.ops.object.select_by_type(type='MESH') bpy.ops.object.delete() bpy.ops.mesh.primitive_cube_add(location=(-3,0,0)) cube = bpy.context.object bpy.ops.mesh.primitive_cylinder_add(location=(0,0,0)) cyl = bpy.context.object bpy.ops.mesh.primitive_uv_sphere_add(location=(3,0,0)) sphere = bpy.context.object # Define an RNA prop for every object # 为每个对象定义一个RNA参数 bpy.types.Object.myRnaInt = IntProperty( name="RNA int", min = -100, max = 100, default = 33) # Define an RNA prop for every mesh # 为每个mesh定义一个RNA参数 bpy.types.Mesh.myRnaFloat = FloatProperty( name="RNA float", default = 12.345) # Set the cube's RNA props # 设置Cube的RNA参数 cube.myRnaInt = -99 cube.data.myRnaFloat = -1 # Create ID props by setting them. # 创建他们的ID参数 cube["MyIdString"] = "I am an ID prop" cube.data["MyIdBool"] = True # Property panel # 参数面板 class MyPropPanel(bpy.types.Panel): bl_label = "My properties" bl_space_type = "VIEW_3D" bl_region_type = "UI" def draw(self, context): ob = context.object if not ob: return layout = self.layout layout.prop(ob, 'myRnaInt') try: ob["MyIdString"] layout.prop(ob, '["MyIdString"]') except: pass if ob.type == 'MESH': me = ob.data layout.prop(me, 'myRnaFloat') try: me["MyIdBool"] layout.prop(me, '["MyIdBool"]') except: pass # Registration # 注册 bpy.utils.register_class(MyPropPanel) ````` ## **Using scene properties to store information** *This program lets the user input various kind of information, which is then sent from the panel to the buttons. The mechanism is to use user-defined RNA properties, which can be set by the panel and read by the buttons. All kind of Blender data can have properties. Global properties which are not directly associated with any specific object can conveniently be stored in the current scene. Note however that they will be lost if you switch to a new scene.* 这个程序允许用户输入各种信息,然后从面板发送到按钮。这是实现了让用户在面板定义RNA参数并通过按钮读取。所有Blender数据都包含参数。不与特定对象关联的全局参数将可以非常方便的保存在当前常见。但是请注意,切换到新的场景他们将会消失。 ![](https://box.kancloud.cn/9dc2c9125e0737765a218d0710b5e28a_322x260.png) `````python #---------------------------------------------------------- # File scene_props.py #---------------------------------------------------------- import bpy from bpy.props import * # # Store properties in the active scene # 储存参数到激活的场景 # def initSceneProperties(scn): bpy.types.Scene.MyInt = IntProperty( name = "Integer", description = "Enter an integer") scn['MyInt'] = 17 bpy.types.Scene.MyFloat = FloatProperty( name = "Float", description = "Enter a float", default = 33.33, min = -100, max = 100) bpy.types.Scene.MyBool = BoolProperty( name = "Boolean", description = "True or False?") scn['MyBool'] = True bpy.types.Scene.MyEnum = EnumProperty( items = [('Eine', 'Un', 'One'), ('Zwei', 'Deux', 'Two'), ('Drei', 'Trois', 'Three')], name = "Ziffer") scn['MyEnum'] = 2 bpy.types.Scene.MyString = StringProperty( name = "String") scn['MyString'] = "Lorem ipsum dolor sit amet" return initSceneProperties(bpy.context.scene) # # Menu in UI region # UI菜单区域 # class UIPanel(bpy.types.Panel): bl_label = "Property panel" bl_space_type = "VIEW_3D" bl_region_type = "UI" def draw(self, context): layout = self.layout scn = context.scene layout.prop(scn, 'MyInt', icon='BLENDER', toggle=True) layout.prop(scn, 'MyFloat') layout.prop(scn, 'MyBool') layout.prop(scn, 'MyEnum') layout.prop(scn, 'MyString') layout.operator("idname_must.be_all_lowercase_and_contain_one_dot") # # The button prints the values of the properites in the console. # 这个按钮在控制台输出参数值 # class OBJECT_OT_PrintPropsButton(bpy.types.Operator): bl_idname = "idname_must.be_all_lowercase_and_contain_one_dot" bl_label = "Print props" def execute(self, context): scn = context.scene printProp("Int: ", 'MyInt', scn) printProp("Float: ", 'MyFloat', scn) printProp("Bool: ", 'MyBool', scn) printProp("Enum: ", 'MyEnum', scn) printProp("String: ", 'MyString', scn) return{'FINISHED'} def printProp(label, key, scn): try: val = scn[key] except: val = 'Undefined' print("%s %s" % (key, val)) # Registration bpy.utils.register_module(__name__) ````` ## **Polling</br>查询** *A script often only works in some specific context, e.g. when an object of the right kind is active. E.g., a script that manipulates mesh vertices can not do anything meaningful if the active object is an armature.* 一个脚本总是在特定的前提下出现,例如,当选中一个正确的对象激活的时候,例如一个操作网格顶点的脚本对选中激活的是一个骨骼对象的时候毫无意义。 *This program adds a panel which modifies the active object's material. The panel resides in the user interface section (open with N), but it is only visible if the active object is a mesh with at least one material. Checking how many materials the active object has is done by poll(). This is not a function but rather a class method, indicated by the command @classmethod above the definition. So what is the difference between a function and a class method? Don't ask me! All I know is that the code works with the @classmethod line in place, but not without. * 这个程序添加了一个面板并修改激活对象的材质。这个面板位于UI部分(按N键打开),但是当选中对象有至少一个材质的时候才可见。通过poll()来检查激活对象有多少个残值。这不是一个函数而是一个类方法,由其上方的命令@classmethod所定义。那么函数和类方法有什么不同吗?别问我!我所知道的是只有被@classmethod 定义的时候才可以使用,没有例外。 ![](https://box.kancloud.cn/b045766d694f9f8c718769f8f6ae6a15_850x305.png) ~~~python #---------------------------------------------------------- # File poll.py #---------------------------------------------------------- import bpy, random # # Menu in UI region # UI菜单面板 # class ColorPanel(bpy.types.Panel): bl_label = "Modify colors" bl_space_type = "VIEW_3D" bl_region_type = "UI" @classmethod def poll(self, context): if context.object and context.object.type == 'MESH': return len(context.object.data.materials) def draw(self, context): layout = self.layout scn = context.scene layout.operator("random.button") layout.operator("darken_random.button") layout.operator("invert.button") # # The three buttons # 三个按钮 # class RandomButton(bpy.types.Operator): bl_idname = "random.button" bl_label = "Randomize" def execute(self, context): mat = context.object.data.materials[0] for i in range(3): mat.diffuse_color[i] = random.random() return{'FINISHED'} class DarkenRandomButton(bpy.types.Operator): bl_idname = "darken_random.button" bl_label = "Darken Randomly" def execute(self, context): mat = context.object.data.materials[0] for i in range(3): mat.diffuse_color[i] *= random.random() return{'FINISHED'} class InvertButton(bpy.types.Operator): bl_idname = "invert.button" bl_label = "Invert" def execute(self, context): mat = context.object.data.materials[0] for i in range(3): mat.diffuse_color[i] = 1 - mat.diffuse_color[i] return{'FINISHED'} # Registration bpy.utils.register_module(__name__) ~~~ ## **Dynamic drop-down menus</br>动态下拉菜单** *This program adds a panel with a drop-down menu to the User interface panel. In the beginning the menu contains three items: red, green and blue. There are two buttons labelled Set color. The upper one changes the color of the active object to the color selected in the drop-down menu, and the lower one sets it to the color specified by the three sliders. Colors can be added to and deleted from the drop-down menu.* 这个程序会为在UI菜单区域面板添加一个下拉菜单。开始时,菜单会有三个项目,red,green和blue。有两个标记设置颜色按钮。上面一个将激活对象设置成下拉菜单选中颜色,下面一个将设置成下方滑块设置颜色。可以从下拉菜单中增删颜色。 *Also note that polling works for buttons as well; the Set color button is greyed out unless the active object is a mesh with at least one material. * 要注意查询也对按钮起作用;如果选择的对象没有至少一种材质,则设置颜色按钮将会变灰。 ![](https://box.kancloud.cn/a426b5cfde232ccd351c84b9a365a470_1011x451.png) ~~~python #---------------------------------------------------------- # File swatches.py #---------------------------------------------------------- import bpy from bpy.props import * theSwatches = [ ("1 0 0" , "Red" , "1 0 0"), ("0 1 0" , "Green" , "0 1 0"), ("0 0 1" , "Blue" , "0 0 1")] def setSwatches(): bpy.types.Object.my_swatch = EnumProperty( items = theSwatches, name = "Swatch") setSwatches() bpy.types.Object.my_red = FloatProperty( name = "Red", default = 0.5, min = 0, max = 1) bpy.types.Object.my_green = FloatProperty( name = "Green", default = 0.5, min = 0, max = 1) bpy.types.Object.my_blue = FloatProperty( name = "Blue", default = 0.5, min = 0, max = 1) def findSwatch(key): for n,swatch in enumerate(theSwatches): (key1, name, colors) = swatch if key == key1: return n raise NameError("Unrecognized key %s" % key) # Swatch Panel class SwatchPanel(bpy.types.Panel): bl_label = "Swatches" #bl_idname = "myPanelID" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "material" def draw(self , context): layout = self.layout ob = context.active_object layout.prop_menu_enum(ob, "my_swatch") layout.operator("swatches.set").swatch=True layout.separator() layout.prop(ob, "my_red") layout.prop(ob, "my_green") layout.prop(ob, "my_blue") layout.operator("swatches.set").swatch=False layout.operator("swatches.add") layout.operator("swatches.delete") # Set button class OBJECT_OT_SetButton(bpy.types.Operator): bl_idname = "swatches.set" bl_label = "Set color" swatch = bpy.props.BoolProperty() @classmethod def poll(self, context): if context.object and context.object.type == 'MESH': return len(context.object.data.materials) def execute(self, context): ob = context.object if self.swatch: n = findSwatch(ob.my_swatch) (key, name, colors) = theSwatches[n] words = colors.split() color = (float(words[0]), float(words[1]), float(words[2])) else: color = (ob.my_red, ob.my_green, ob.my_blue) ob.data.materials[0].diffuse_color = color return{'FINISHED'} # Add button class OBJECT_OT_AddButton(bpy.types.Operator): bl_idname = "swatches.add" bl_label = "Add swatch" def execute(self, context): ob = context.object colors = "%.2f %.2f %.2f" % (ob.my_red, ob.my_green, ob.my_blue) theSwatches.append((colors, colors, colors)) setSwatches() return{'FINISHED'} # Delete button class OBJECT_OT_DeleteButton(bpy.types.Operator): bl_idname = "swatches.delete" bl_label = "Delete swatch" def execute(self, context): n = findSwatch(context.object.my_swatch) theSwatches.pop(n) setSwatches() return{'FINISHED'} # Registration bpy.utils.register_module(__name__) ~~~ ## **Adding an operator and appending it to a menu</br>添加操作并将其附加到菜单中** *The only operators encountered so far were simple buttons. In this program we make a more complicated operator, which creates a twisted cylinder.* 到目前为止我们都只是添加了简单的操作按钮。在这个程序里,我们将创建更复杂的操作,创建一个扭曲的圆柱。 *To invoke the operator, press **【Space】** and type in "Add twisted cylinder"; Blender suggests matching operator names while you type. The cylinder have several options, which appear in the Tool props area (below the Tools section) once the cylinder has been created. These can be modified interactively and the result is immediately displayed in the viewport.* 通过按下**空格键**,并输入"Add twisted cylinder"调用这个操作。Blender会在输入的时候匹配操作名称提示。圆柱一旦被创建,将会有几个工具参数出现在工具参数栏(在工具区域下面)。这可以在视口可视化交互修改参数。 *The last part of the script registers the script. Instead of pressing **【Space】**, you can now invoke the script more conveniently from the Add » Mesh submenu. If we had used append instead of prepend in register(), the entry had appeared at the bottom instead of at the top of the menu.* 脚本的最后一部分是注册脚本。替换按下**空格键**,你现在可以通过 Add » Mesh子菜单方便的调用这个脚本。如果我们使用在注册中使用了append而不是prepend,则条目出现在菜单底部而不是顶部。 ![](https://box.kancloud.cn/a0d0c66711058ec20148dd94ce87c118_968x672.png) ~~~ #---------------------------------------------------------- # File twisted.py #---------------------------------------------------------- import bpy, math def addTwistedCylinder(context, r, nseg, vstep, nplanes, twist): verts = [] faces = [] w = 2*math.pi/nseg a = 0 da = twist*math.pi/180 for j in range(nplanes+1): z = j*vstep a += da for i in range(nseg): verts.append((r*math.cos(w*i+a), r*math.sin(w*i+a), z)) if j > 0: i0 = (j-1)*nseg i1 = j*nseg for i in range(1, nseg): faces.append((i0+i-1, i0+i, i1+i, i1+i-1)) faces.append((i0+nseg-1, i0, i1, i1+nseg-1)) me = bpy.data.meshes.new("TwistedCylinder") me.from_pydata(verts, [], faces) ob = bpy.data.objects.new("TwistedCylinder", me) context.scene.objects.link(ob) context.scene.objects.active = ob return ob # # User interface # from bpy.props import * class MESH_OT_primitive_twisted_cylinder_add(bpy.types.Operator): '''Add a twisted cylinder''' bl_idname = "mesh.primitive_twisted_cylinder_add" bl_label = "Add twisted cylinder" bl_options = {'REGISTER', 'UNDO'} radius = FloatProperty(name="Radius", default=1.0, min=0.01, max=100.0) nseg = IntProperty(name="Major Segments", description="Number of segments for one layer", default=12, min=3, max=256) vstep = FloatProperty(name="Vertical step", description="Distance between subsequent planes", default=1.0, min=0.01, max=100.0) nplanes = IntProperty(name="Planes", description="Number of vertical planes", default=4, min=2, max=256) twist = FloatProperty(name="Twist angle", description="Angle between subsequent planes (degrees)", default=15, min=0, max=90) location = FloatVectorProperty(name="Location") rotation = FloatVectorProperty(name="Rotation") # Note: rotation in radians! def execute(self, context): ob = addTwistedCylinder(context, self.radius, self.nseg, self.vstep, self.nplanes, self.twist) ob.location = self.location ob.rotation_euler = self.rotation #context.scene.objects.link(ob) #context.scene.objects.active = ob return {'FINISHED'} # # Registration # Makes it possible to access the script from the Add > Mesh menu # 注册 # 使之可以在Add > Mesh 菜单中调用脚本 # def menu_func(self, context): self.layout.operator("mesh.primitive_twisted_cylinder_add", text="Twisted cylinder", icon='MESH_TORUS') def register(): bpy.utils.register_module(__name__) bpy.types.INFO_MT_mesh_add.prepend(menu_func) def unregister(): bpy.utils.unregister_module(__name__) bpy.types.INFO_MT_mesh_add.remove(menu_func) if __name__ == "__main__": register() ~~~ ## **A modal operator** *The following example is taken directly from the API documentation, as are the next few examples.* 接下来的例子直接来源于API文档,下面几个例子也是如此。 *A modal operator defines a Operator.modal function which running, handling events until it returns {'FINISHED'} or {'CANCELLED'}. Grab, Rotate, Scale and Fly-Mode are examples of modal operators. They are especially useful for interactive tools, your operator can have its own state where keys toggle options as the operator runs.* 一个模态算子定义一个运算。模态函数将会持续运行直到返回 {'FINISHED'} 或者{'CANCELLED'}。抓起,旋转,缩放和飞行模式是模态算子的范例。他们对于交互工具特别有用,你的操作可以拥有自己的状态,按键可以在操作运行中切换状态。 *When the operator in this example is invoked, it adds a modal handler to itself with the call context.window_manager.modal_handler_add(self). After that, the active object keeps moving in the XY-plane as long as we move the mouse. To quit, press either mouse button or the **【Esc】** key. The modal method triggers on three kinds of events:* 当示例操作被调用的时候,会调用一个名叫context.window_manager.modal_handler_add(self)为自己身添加一个模态处理程序。之后,只要移动鼠标被激活对象就会在XY平面移动。按下ESC键或者鼠标按钮将会结束。 模态方法触发有三种事件: 1. *A mouse move moves the active object.*</br>鼠标移动激活对象。 2. *Press the **【LMB![](https://box.kancloud.cn/c1164f0b0c3272190b79803361b54ca4_12x15.png) 】** to confirm and exit to normal mode. The object is left at its new position.*</br>按下鼠标左键确认操作并退回到普通模式。对象会被放在新的位置。 3. *Press the **【RMB![](https://box.kancloud.cn/df5d65fce128fe77a0dfcdc282260826_12x15.png)】** or the **【Esc】** key to cancel and exit to normal mode. The object reverts to its original position.*</br>按下鼠标右键或者ESC键取消并退回普通模式。对象会被放回原位。 *It is important that there is some way to exit to normal mode. If the modal() function always returns 'RUNNING_MODAL', the script will be stuck in an infinite loop and you have to restart Blender.* 重要的是有一些方法可以推出到普通模式。如果modal()总是返回'RUNNING_MODAL',代码将会被阻塞无限循环而你必须重启Blender。 *A modal operator defines two special methods called __init__() and __del__(), which are called when modal operation starts and stops, respectively.* 一个模态算子定义了两个特殊的烦恼方法名为__init__() 和 __del__(),分别在模态算子启动和终止的时候调用。 *Run the script. The active object moves in the XY-plane when you move the mouse. The script also create a panel with a button, from which you can also invoke the modal operator.* 运行这个脚本,激活的对象就会在你移动鼠标的时候在XY平面移动。这代码会创建一个包含按钮的面板,你可以从中调用模态算子。 ~~~python #---------------------------------------------------------- # File modal.py # from API documentation #---------------------------------------------------------- import bpy class MyModalOperator(bpy.types.Operator): bl_idname = "mine.modal_op" bl_label = "Move in XY plane" def __init__(self): print("Start moving") def __del__(self): print("Moved from (%d %d) to (%d %d)" % (self.init_x, self.init_y, self.x, self.y)) def execute(self, context): context.object.location.x = self.x / 100.0 context.object.location.y = self.y / 100.0 def modal(self, context, event): if event.type == 'MOUSEMOVE': # Apply self.x = event.mouse_x self.y = event.mouse_y self.execute(context) elif event.type == 'LEFTMOUSE': # Confirm return {'FINISHED'} elif event.type in ('RIGHTMOUSE', 'ESC'): # Cancel return {'CANCELLED'} return {'RUNNING_MODAL'} def invoke(self, context, event): self.x = event.mouse_x self.y = event.mouse_y self.init_x = self.x self.init_y = self.y self.execute(context) print(context.window_manager.modal_handler_add(self)) return {'RUNNING_MODAL'} # # Panel in tools region # class MyModalPanel(bpy.types.Panel): bl_label = "My modal operator" bl_space_type = "VIEW_3D" bl_region_type = "TOOLS" def draw(self, context): self.layout.operator("mine.modal_op") # Registration bpy.utils.register_module(__name__) # Automatically move active object on startup bpy.ops.mine.modal_op('INVOKE_DEFAULT') ~~~ ## **Invoke versus execute** *This script illustrates the difference between invoke and execute. The invoking event is an argument of the Operator.invoke function, which sets the two integer properties x and y to the mouse location and calls the Operator.execute function. Alternatively, we can execture the operator and explicitly set x and y: * 这个代码说明了调用和执行的区别。调用事件是Operator.invoke函数的一个参数,他设置两个整数参数x和y为鼠标的位置,并且调用Operator.execute函数。另外,我们可以执行操作明确设置x和y: ~~~ bpy.ops.wm.mouse_position(’EXEC_DEFAULT’, x=20, y=66) ~~~ *Instead of printing the mouse coordinates in the terminal window, the information is sent to the info panel in the upper right corner. This is a good place to display short notification, because the user does not have to look in another window, especially since the terminal/DOS window is not visible in all versions of Blender. However, long messages are difficult to fit into the limited space of the info panel. * 而不是在终端窗口中打印鼠标坐标,消息将被送到顶部右上角的信息面板。这是一个展示短通知的好地方,因为用户并不总是在看另外一个窗口,特别是终端/dos窗口在所有版本的Blender中都不可见。顶部面板窗口有限的空间难以适应长信息。 ![](https://box.kancloud.cn/44ef669c646521ba114d749eae661efb_935x429.png) ~~~ #---------------------------------------------------------- # File invoke.py # from API documentation #---------------------------------------------------------- import bpy class SimpleMouseOperator(bpy.types.Operator): """ This operator shows the mouse location, this string is used for the tooltip and API docs """ bl_idname = "wm.mouse_position" bl_label = "Mouse location" x = bpy.props.IntProperty() y = bpy.props.IntProperty() def execute(self, context): # rather then printing, use the report function, # this way the message appears in the header, self.report({'INFO'}, "Mouse coords are %d %d" % (self.x, self.y)) return {'FINISHED'} def invoke(self, context, event): self.x = event.mouse_x self.y = event.mouse_y return self.execute(context) # # Panel in tools region # class MousePanel(bpy.types.Panel): bl_label = "Mouse" bl_space_type = "VIEW_3D" bl_region_type = "TOOL_PROPS" def draw(self, context): self.layout.operator("wm.mouse_position") # # Registration # Not really necessary to register the class, because this happens # automatically when the module is registered. OTOH, it does not hurt either. bpy.utils.register_class(SimpleMouseOperator) bpy.utils.register_module(__name__) # Automatically display mouse position on startup bpy.ops.wm.mouse_position('INVOKE_DEFAULT') # Another test call, this time call execute() directly with pre-defined settings. #bpy.ops.wm.mouse_position('EXEC_DEFAULT', x=20, y=66) ~~~ ## **A popup dialog</br>弹出消息** *When this script is run a popup window appears, where you can set some properties. After you quit the popup window by moving the mouse outside of it, the properties are written both to the info window and to the console.* 当代码执行的时候会弹出一个消息窗口,你可以在其中设置一些参数。当你鼠标移出弹出窗口的范围的时候,弹出窗口将会退出,这些属性都会被写入信息窗口和控制台。 *In subsection [Panel layout and several arguments](https://wiki.blender.org/index.php/Dev:Py/Scripts/Cookbook/Code_snippets/Interface#Panel_layout_and_several_arguments) we used a single string to send several arguments to an operator. Here we use global variables for the same purpose. * 在小组面板布局和几个参数中,我们使用单个字符串向操作发送多个参数。这里我们使用全局变量来实现相同的目的。 ![](https://box.kancloud.cn/85a624c757e68e0cc1e6a8eb31ad923c_934x434.png) ~~~ #---------------------------------------------------------- # File popup.py # from API documentation #---------------------------------------------------------- import bpy from bpy.props import * theFloat = 9.8765 theBool = False theString = "Lorem ..." theEnum = 'one' class DialogOperator(bpy.types.Operator): bl_idname = "object.dialog_operator" bl_label = "Simple Dialog Operator" my_float = FloatProperty(name="Some Floating Point", min=0.0, max=100.0) my_bool = BoolProperty(name="Toggle Option") my_string = StringProperty(name="String Value") my_enum = EnumProperty(name="Enum value", items = [('one', 'eins', 'un'), ('two', 'zwei', 'deux'), ('three', 'drei', 'trois')]) def execute(self, context): message = "%.3f, %d, '%s' %s" % (self.my_float, self.my_bool, self.my_string, self.my_enum) self.report({'INFO'}, message) print(message) return {'FINISHED'} def invoke(self, context, event): global theFloat, theBool, theString, theEnum self.my_float = theFloat self.my_bool = theBool self.my_string = theString self.my_enum = theEnum return context.window_manager.invoke_props_dialog(self) bpy.utils.register_class(DialogOperator) # Invoke the dialog when loading bpy.ops.object.dialog_operator('INVOKE_DEFAULT') # # Panel in tools region # class DialogPanel(bpy.types.Panel): bl_label = "Dialog" bl_space_type = "VIEW_3D" bl_region_type = "UI" def draw(self, context): global theFloat, theBool, theString, theEnum theFloat = 12.345 theBool = True theString = "Code snippets" theEnum = 'two' self.layout.operator("object.dialog_operator") # # Registration bpy.utils.register_module(__name__) ~~~ ## **An error dialog</br>错误对话框** *As far as I know, Blender does not have any elegant means to notify the user if something has gone wrong. One can print a message in the terminal window or info panel, and then raise an exception. Most modern application would instead open a message box and display the error message. The following script uses the Blender API to create a popup dialog to notify the user.* 据我所知,Blender并没有优雅的方式来通知用户出现了问题。可以在终端窗口输出一条错误信息,并抛出异常。大多数现代应用程序都会打开一个消息窗口并向展示错误信息。在下面的脚本中将会使用Blender的API创建一个弹出对话框去通知用户。 *The script scans a file. If the word return is found, the script opens a popup window to tell the user that an error has occurred and on which line. If there no such word in the entire file, a popup window displays the number of scanned lines.* 这个脚本扫描一个文件。如果找到单词返回,则弹出窗口并告诉用户错误再哪一行。如果在整个文件中没有找到这个词,则弹出窗口展示扫描的行数。 *At the time of writing, this script causes a memory leak which makes Blender unstable. This bug will hopefully be fixed soon.* 在编写此文的时候,这个脚本导致内存泄漏使得Blender不稳定,这个bug会尽快修复(此文写于2.6时代,理应早已修复) ![](https://box.kancloud.cn/396e4a70a8305666675d70df9edf5eec_444x214.png) ~~~ #---------------------------------------------------------- # File error.py # Simple error dialog #---------------------------------------------------------- import bpy from bpy.props import * # # The error message operator. When invoked, pops up a dialog # window with the given message. # class MessageOperator(bpy.types.Operator): bl_idname = "error.message" bl_label = "Message" type = StringProperty() message = StringProperty() def execute(self, context): self.report({'INFO'}, self.message) print(self.message) return {'FINISHED'} def invoke(self, context, event): wm = context.window_manager return wm.invoke_popup(self, width=400, height=200) def draw(self, context): self.layout.label("A message has arrived") row = self.layout.split(0.25) row.prop(self, "type") row.prop(self, "message") row = self.layout.split(0.80) row.label("") row.operator("error.ok") # # The OK button in the error dialog # class OkOperator(bpy.types.Operator): bl_idname = "error.ok" bl_label = "OK" def execute(self, context): return {'FINISHED'} # # Opens a file select dialog and starts scanning the selected file. # class ScanFileOperator(bpy.types.Operator): bl_idname = "error.scan_file" bl_label = "Scan file for return" filepath = bpy.props.StringProperty(subtype="FILE_PATH") def execute(self, context): scanFile(self.filepath) return {'FINISHED'} def invoke(self, context, event): context.window_manager.fileselect_add(self) return {'RUNNING_MODAL'} # # Scan the file. If a line contains the word "return", invoke error # dialog and quit. If reached end of file, display another message. # def scanFile(filepath): fp = open(filepath, "rU") n = 1 for line in fp: words = line.split() if "return" in words: bpy.ops.error.message('INVOKE_DEFAULT', type = "Error", message = 'Found "return" on line %d' % n) return n += 1 fp.close() bpy.ops.error.message('INVOKE_DEFAULT', type = "Message", message = "No errors found in %d lines" % n) return # Register classes and start scan automatically bpy.utils.register_class(OkOperator) bpy.utils.register_class(MessageOperator) bpy.utils.register_class(ScanFileOperator) bpy.ops.error.scan_file('INVOKE_DEFAULT') ~~~ 注:本文原文所有权归原作者所有,翻译文本所有权归本人所有。 转载请附原文链接。