├── .gitignore
├── .vscode
└── launch.json
├── .vscodeignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── helpers
└── documentation_table.js
├── images
└── icon.png
├── package.json
├── snippets
└── snippets.json
└── vsc-extension-quickstart.md
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .vsix
3 | TABLE.md
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that launches the extension inside a new window
2 | {
3 | "version": "0.1.0",
4 | "configurations": [
5 | {
6 | "name": "Extension",
7 | "type": "extensionHost",
8 | "request": "launch",
9 | "runtimeExecutable": "${execPath}",
10 | "args": ["--extensionDevelopmentPath=${workspaceRoot}" ]
11 | }
12 | ]
13 | }
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
1 | .vscode/**
2 | .vscode-test/**
3 | .gitignore
4 | vsc-extension-quickstart.md
5 | TABLE.md
6 | helpers/**
7 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 | All notable changes to the "blender-python-code-templates" extension will be documented in this file.
3 |
4 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
5 |
6 | ## [Unreleased]
7 | - Initial release
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 nikorummukainen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # blender-python-code-templates
2 |
3 | Python snippets for Blender python code.
4 |
5 | > [GitHub repository link](https://github.com/nikorummukainen/blender-python-code-templates)
6 |
7 | > [Addon at VScode Marketplace](https://marketplace.visualstudio.com/items?itemName=blenderfreetimeprojects.blender-python-code-templates)
8 |
9 | > [Blender addon for turning files/blender texteditor content into .json snippets](https://github.com/nikorummukainen/blender-snippet-generator)
10 |
11 | ## Snippets
12 |
13 | |Prefixes|Description|
14 | |--------|-----------|
15 | |info addon|Blender addon info|
16 | |license gnu|GNU license|
17 | |license mit|MIT license|
18 | |keymap item|keymap item [More Info](https://docs.blender.org/api/current/bpy.types.KeyMapItem.html#bpy.types.KeyMapItem)|
19 | |keymaps|Register and unregister keymaps|
20 | |menu|Menu|
21 | |operator|Operator function class, without any imports or added functions|
22 | |operator modal|Modal operator function, without any imports or added functions|
23 | |operator modal draw|Modal operator draw function, without any imports or added functions|
24 | |panel|Panel|
25 | |pie menu|Pie Menu|
26 | |register|Register and Unregister Module|
27 | |template addon|Example of addon for adding object|
28 | |template background job|Example of script that shows how you can run blender from the command line (in background mode with no interface) to automate tasks, in this example it creates a text object, camera and light, then renders and/or saves it. This example also shows how you can parse command line options to scripts.|
29 | |template batch|Example of exporting each selected object into its own file|
30 | |template bmesh|Example of to get mesh representation for bmesh from edit-mode and updating it back after bmesh operation.|
31 | |template bmesh|Example of to get mesh representation for bmesh from active object and updating it back after bmesh operation.|
32 | |template keyingset|Example of generating Keying Set|
33 | |template nodes|Example of Implementation of custom nodes from Python|
34 | |template driver|Example of script defining functions to be used directly in drivers expressions to extend the builtin set of python functions.|
35 | |template script|Example of loading script relative to current blend file. This stub runs a python script relative to the currently open blend file, useful when editing scripts externally.|
36 | |template gamelogic|Example of gamelogic module. This module can be accessed by a python controller with its execution method set to 'Module'|
37 | |template gamelogic|Example of Simple gamelogic python script.|
38 | |template gamelogic|Example of gamelogic script this must be assigned to a python controller where it can access the object that owns it and the sensors/actuators that it connects to.|
39 | |template operator|Example of Template for file export operator, operator exports data from blender to .txt file|
40 | |template operator|Example of Template for file import operator, operator imports data from .txt to blender data|
41 | |template operator|Example of operator involving bmesh for creating and adding object to scene|
42 | |template operator|Example of Operator template for editing mesh UV's with bmesh|
43 | |template operator|Example of Blender modal operator function with imports, main function, register, unregister and testcall|
44 | |template ui list|Example of ui list template with adding it to blender with example panel|
45 | |template ui list|Example of simple ui list class with some filtering and bpy import|
46 | |template ui menu|Example of ui menu|
47 | |template ui simple panel|Example of ui panel class with import and register|
48 | |template ui panel|Example of ui panel class ui panel is created with examples of columns, buttons, rows, properties, with import and register, |
49 | |template ui pie menu|Example of 3d viewport pie menu|
50 | |template dynamic enum|This example script demonstrates a dynamic EnumProperty with custom icons.|
51 | |template ui previews|This example script demonstrates how to place a custom icon on a button menu entry.|
52 |
53 | ## Contribution Notes
54 |
55 | If adding or editing a snippet's prefix or description, to keep from having to edit the above table, you can generate it using the documentation_helper script, just `npm run table` and it will write a `TABLE.md` file with the above table so you can just copy and paste it to here. The `TABLE.md` file has been added to the `.gitignore` so it won't get accidently committed.
56 |
57 | ## Release Notes
58 |
59 | ### 0.9.0 beta
60 | now snippets include templates for Blender python api
61 |
62 | ### 0.9.1
63 | removed unnecessary newlines from templates.
64 |
65 | ### 0.9.4
66 | added link to marketplace page to README.md
67 | added github repository to package.json
68 | added link to github repository to README.md
69 | added link to snippet generator addon github repository to README.md
70 |
71 | new snippets:
72 | mit license
73 |
74 | -----------------------------------------------------------------------------------------------------------
75 |
76 | # Thanks
77 | Alan for his [Blender VS Code Debugger](https://github.com/alanscodelog/blender-debugger-for-vscode) addon, it's awesome!
78 |
79 | Ideasman42 and others who have contributed to [the Blender Wiki](https://wiki.blender.org) for providing instructions on [how to build Blender as python module](https://wiki.blender.org/index.php/User:Ideasman42/BlenderAsPyModule). Now my linter understands Blender.
80 |
81 | Jacques Lucke's awesome Blender addon: [Code Autocomplete](https://www.blendermarket.com/products/code-autocomplete).
82 |
83 | Blender developers! for providing the awesome package known as Blender and the python code templates.
--------------------------------------------------------------------------------
/helpers/documentation_table.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs')
2 |
3 | fs.readFile("./snippets/snippets.json", 'utf8', function(err, data) {
4 | if (err) throw err;
5 | sort(data)
6 | });
7 |
8 |
9 | function sort(data) {
10 | data = JSON.parse(data)
11 | let table = []
12 | table.push("|Prefixes|Description|")
13 | table.push("|--------|-----------|")
14 | for (i in data) {
15 | var snippet = data[i]
16 | var prefix = ""
17 | var description = ""
18 | prefix = snippet["prefix"]
19 | description = snippet["description"]
20 | line = "|" + prefix.replace(/\s/g, " ") + "|" + description +"|"
21 |
22 | table.push(line)
23 | }
24 | table = table.join("\n")
25 | fs.writeFile("./TABLE.md", table)
26 | }
--------------------------------------------------------------------------------
/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikorummukainen/blender-python-code-templates/97c8bfd4324673adf9bbbdfa2f9db4a0bd2406d6/images/icon.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "blender-python-code-templates",
3 | "displayName": "Blender Python Code Templates",
4 | "description": "Templates for Blender Addon development. Templates translated are from Blender python templates and from Jacques Lucke's Code Autocomplete addon with my own additions.",
5 | "version": "0.9.4",
6 | "publisher": "blenderfreetimeprojects",
7 | "license": "MIT",
8 | "icon": "images/icon.png",
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/nikorummukainen/blender-python-code-templates"
12 | },
13 | "bugs": {
14 | "url": "https://github.com/nikorummukainen/blender-python-code-templates/issues"
15 | },
16 | "engines": {
17 | "vscode": "^1.19.0"
18 | },
19 | "categories": [
20 | "Snippets"
21 | ],
22 | "contributes": {
23 | "snippets": [
24 | {
25 | "language": "python",
26 | "path": "./snippets/snippets.json"
27 | }
28 | ]
29 | },
30 | "scripts": {
31 | "table": "node helpers/documentation_table.js"
32 | }
33 | }
--------------------------------------------------------------------------------
/snippets/snippets.json:
--------------------------------------------------------------------------------
1 | {
2 | "addon_info": {
3 | "prefix": "info addon",
4 | "body": [
5 | "bl_info = {",
6 | "\t\"name\": \"${1:My Addon Name}\",",
7 | "\t\"description\": \"${2:Description of this addon}\",",
8 | "\t\"author\": \"${3:Authors name}\",",
9 | "\t\"version\": (${4:0, 0, 1}),",
10 | "\t\"blender\": (${5:2, 79, 0}),",
11 | "\t\"location\": \"${6:View3D}\",",
12 | "\t\"warning\": \"This addon is still in development.\",",
13 | "\t\"wiki_url\": \"\",",
14 | "\t\"category\": \"${7:Object}\" }",
15 | "\t"
16 | ],
17 | "description": "Blender addon info"
18 | },
19 | "GNU_license": {
20 | "prefix": "license gnu",
21 | "body": [
22 | "'''",
23 | "Copyright (C) ${1:2018} ${2:ADDONCREATORNAME}",
24 | "${3:ADDONCREATORNAME@MAIL.COM}",
25 | "",
26 | "Created by ${2:ADDONCREATORNAME}",
27 | "",
28 | "\tThis program is free software: you can redistribute it and/or modify",
29 | "\tit under the terms of the GNU General Public License as published by",
30 | "\tthe Free Software Foundation, either version 3 of the License, or",
31 | "\t(at your option) any later version.",
32 | "",
33 | "\tThis program is distributed in the hope that it will be useful,",
34 | "\tbut WITHOUT ANY WARRANTY; without even the implied warranty of",
35 | "\tMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the",
36 | "\tGNU General Public License for more details.",
37 | "",
38 | "\tYou should have received a copy of the GNU General Public License",
39 | "\talong with this program. If not, see .",
40 | "'''"
41 | ],
42 | "description": "GNU license"
43 | },
44 | "license_MIT": {
45 | "prefix": "license mit",
46 | "body": [
47 | "MIT License",
48 | "",
49 | "Copyright (c) ${1:2018} ${2:AUTHORSNAME}",
50 | "",
51 | "Permission is hereby granted, free of charge, to any person obtaining a copy",
52 | "of this software and associated documentation files (the \"Software\"), to deal",
53 | "in the Software without restriction, including without limitation the rights",
54 | "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell",
55 | "copies of the Software, and to permit persons to whom the Software is",
56 | "furnished to do so, subject to the following conditions:",
57 | "",
58 | "The above copyright notice and this permission notice shall be included in all",
59 | "copies or substantial portions of the Software.",
60 | "",
61 | "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR",
62 | "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,",
63 | "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE",
64 | "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER",
65 | "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,",
66 | "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE",
67 | "SOFTWARE."
68 | ],
69 | "description": "MIT license"
70 | },
71 | "keymap_item": {
72 | "prefix": "keymap item",
73 | "body": [
74 | "kmi = km.keymap_items.new(",
75 | "\tname=\"${1:keymapname}\",",
76 | "\tidname=\"${2:Operator.bl_idname}\",",
77 | "\ttype=\"${3:KEYMAP}\",",
78 | "\tvalue=\"${4:PRESS}\",",
79 | "\tshift=${5:False},",
80 | "\tctrl=${6:False},",
81 | "\talt = ${7:False},",
82 | "\toskey=${8:False}",
83 | "\t$0)"
84 | ],
85 | "description": "keymap item [more info](https://docs.blender.org/api/current/bpy.types.KeyMapItem.html#bpy.types.KeyMapItem)"
86 | },
87 | "keymap": {
88 | "prefix": "keymaps",
89 | "body": [
90 | "addon_keymaps = []",
91 | "def register_keymaps():",
92 | "\taddon = bpy.context.window_manager.keyconfigs.addon",
93 | "\tkm = addon.keymaps.new(name = \"3D View\", space_type = \"VIEW_3D\")",
94 | "\t# insert keymap items here",
95 | "\taddon_keymaps.append(km)",
96 | "",
97 | "def unregister_keymaps():",
98 | "\twm = bpy.context.window_manager",
99 | "\tfor km in addon_keymaps:",
100 | "\t\tfor kmi in km.keymap_items:",
101 | "\t\t\tkm.keymap_items.remove(kmi)",
102 | "\t\twm.keyconfigs.addon.keymaps.remove(km)",
103 | "\taddon_keymaps.clear()"
104 | ],
105 | "description": "Register and unregister keymaps"
106 | },
107 | "menu": {
108 | "prefix": "menu",
109 | "body": [
110 | "class ${1:MenuClassName}(bpy.types.Menu):",
111 | "\tbl_idname = \"${2:view3d.menuname}\"",
112 | "\tbl_label = \"${3:Menu name}\"",
113 | "",
114 | "\tdef draw(self, context):",
115 | "\t\tlayout = self.layout",
116 | "\t\t$0"
117 | ],
118 | "description": "Menu"
119 | },
120 |
121 | "operator_simple":{
122 | "prefix": "operator",
123 | "body": [
124 | "class ${1:MyClassName}(bpy.types.Operator):",
125 | "\tbl_idname = \"${2:my_operator.my_class_name}\"",
126 | "\tbl_label = \"${3:My Class Name}\"",
127 | "\tbl_description = \"${4:Description that shows in blender tooltips}\"",
128 | "\tbl_options = {\"REGISTER\"}",
129 | "",
130 | "\t@classmethod",
131 | "\tdef poll(cls, context):",
132 | "\t\treturn True",
133 | "",
134 | "\tdef execute(self, context):",
135 | "\t\t$0",
136 | "\t\treturn {\"FINISHED\"}",
137 | ""
138 | ],
139 | "description": "Operator function class, without any imports or added functions"
140 | },
141 | "modal_operator":{
142 | "prefix": "operator modal",
143 | "body": [
144 | "class ${1:MyClassName}(bpy.types.Operator):",
145 | "\tbl_idname = \"${2:my_operator.my_class_name}\"",
146 | "\tbl_label = \"${3:My Class Name}\"",
147 | "\tbl_description = \"${4:Description that shows in blender tooltips}\"",
148 | "\tbl_options = {'REGISTER'}",
149 | "",
150 | "\t@classmethod",
151 | "\tdef poll(cls, context):",
152 | "\t\treturn True",
153 | "",
154 | "\tdef invoke(self, context, event):",
155 | "\t\tcontext.window_manager.modal_handler_add(self)",
156 | "\t\treturn {\"RUNNING_MODAL\"}",
157 | "",
158 | "\tdef modal(self, context, event):",
159 | "\t\t",
160 | "\t\tif event.type == \"LEFTMOUSE\":",
161 | "\t\t\treturn {\"FINISHED\"}",
162 | "\t\t",
163 | "\t\tif event.type in {\"RIGHTMOUSE\", \"ESC\"}:",
164 | "\t\t\treturn {\"CANCELLED\"}",
165 | "\t\t$0",
166 | "\t\treturn {\"RUNNING_MODAL\"}",
167 | ""
168 | ],
169 | "description": "Modal operator function, without any imports or added functions"
170 | },
171 | "modal_operator_draw":{
172 | "prefix": "operator modal draw",
173 | "body": [
174 | "class ${1:MyClassName}(bpy.types.Operator):",
175 | "\tbl_idname = \"${2:my_operator.my_class_name}\"",
176 | "\tbl_label = \"${3:My Class Name}\"",
177 | "\tbl_description = \"${4:Description that shows in blender tooltips}\"",
178 | "\tbl_options = {'REGISTER'}",
179 | "",
180 | "\t@classmethod",
181 | "\tdef poll(cls, context):",
182 | "\t\treturn True",
183 | "",
184 | "\tdef invoke(self, context, event):",
185 | "\t\tself._handle = bpy.types.SpaceView3D.draw_handler_add(",
186 | "\t\t\tself.draw_callback_px, args, \"WINDOW\", \"POST_PIXEL\")",
187 | "\t\tcontext.window_manager.modal_handler_add(self)",
188 | "\t\treturn {\"RUNNING_MODAL\"}",
189 | "",
190 | "\tdef modal(self, context, event):",
191 | "\t\t",
192 | "\t\tif event.type == \"LEFTMOUSE\":",
193 | "\t\t\treturn self.finish()",
194 | "\t\t",
195 | "\t\tif event.type in {\"RIGHTMOUSE\", \"ESC\"}:",
196 | "\t\t\treturn self.cancelled()",
197 | "\t\t$0",
198 | "\t\treturn {\"RUNNING_MODAL\"}",
199 | "",
200 | "\tdef finish(self):",
201 | "\t\tbpy.types.SpaceView3D.draw_handler_remove(self._handle, \"WINDOW\")",
202 | "\t\treturn {\"FINISHED\"}",
203 | "",
204 | "\tdef cancelled(self):",
205 | "\t\tbpy.types.SpaceView3D.draw_handler_remove(self._handle, \"WINDOW\")",
206 | "\t\treturn {\"CANCELLED\"}",
207 | "",
208 | "\tdef draw_callback_px(tmp, self, context):",
209 | "\t\tpass",
210 | ""
211 | ],
212 | "description": "Modal operator draw function, without any imports or added functions"
213 | },
214 | "panel": {
215 | "prefix": "panel",
216 | "body": [
217 | "class ${1:PanelClassName}(bpy.types.Panel):",
218 | "\tbl_idname = \"${2:panelname}\"",
219 | "\tbl_label = \"${3:Panelname}\"",
220 | "\tbl_space_type = \"${4:VIEW_3D}\"",
221 | "\tbl_region_type = \"${5:TOOLS}\"",
222 | "\tbl_category = \"${6:category}\"",
223 | "",
224 | "\tdef draw(self, context):",
225 | "\t\tlayout = self.layout",
226 | "\t\t$0"
227 | ],
228 | "description": "Panel"
229 | },
230 | "pie_menu": {
231 | "prefix": "pie menu",
232 | "body": [
233 | "class ${1:ClassMenuName}(bpy.types.Menu):",
234 | "\tbl_idname = \"${2:view3d.menuname}\"",
235 | "\tbl_label = \"${3:Menuname}\"",
236 | "",
237 | "\tdef draw(self, context):",
238 | "\t\tpie = self.layout.menu_pie()",
239 | "\t\t$0"
240 | ],
241 | "description": "Pie Menu"
242 | },
243 | "register_functions": {
244 | "prefix": "register",
245 | "body": [
246 | "def register():",
247 | "\tbpy.utils.register_module(__name__)",
248 | "",
249 | "def unregister():",
250 | "\tbpy.utils.unregister_module(__name__)",
251 | "",
252 | "if __name__ == \"__main__\":",
253 | "\tregister()"
254 | ],
255 | "description": "Register and Unregister Module"
256 | },
257 | "template_addon_add_object":{
258 | "prefix": "template addon",
259 | "body": [
260 | "bl_info = {",
261 | "\t\"name\": \"New Object\",",
262 | "\t\"author\": \"Your Name Here\",",
263 | "\t\"version\": (1, 0),",
264 | "\t\"blender\": (2, 75, 0),",
265 | "\t\"location\": \"View3D > Add > Mesh > New Object\",",
266 | "\t\"description\": \"Adds a new Mesh Object\",",
267 | "\t\"warning\": \"\",",
268 | "\t\"wiki_url\": \"\",",
269 | "\t\"category\": \"Add Mesh\",",
270 | "\t}",
271 | "",
272 | "",
273 | "import bpy",
274 | "from bpy.types import Operator",
275 | "from bpy.props import FloatVectorProperty",
276 | "from bpy_extras.object_utils import AddObjectHelper, object_data_add",
277 | "from mathutils import Vector",
278 | "",
279 | "",
280 | "def add_object(self, context):",
281 | "\tscale_x = self.scale.x",
282 | "\tscale_y = self.scale.y",
283 | "",
284 | "\tverts = [Vector((-1 * scale_x, 1 * scale_y, 0)),",
285 | "\t Vector((1 * scale_x, 1 * scale_y, 0)),",
286 | "\t Vector((1 * scale_x, -1 * scale_y, 0)),",
287 | "\t Vector((-1 * scale_x, -1 * scale_y, 0)),",
288 | "\t ]",
289 | "",
290 | "\tedges = []",
291 | "\tfaces = [[0, 1, 2, 3]]",
292 | "",
293 | "\tmesh = bpy.data.meshes.new(name=\"New Object Mesh\")",
294 | "\tmesh.from_pydata(verts, edges, faces)",
295 | "\t# useful for development when the mesh may be invalid.",
296 | "\t# mesh.validate(verbose=True)",
297 | "\tobject_data_add(context, mesh, operator=self)",
298 | "",
299 | "",
300 | "class OBJECT_OT_add_object(Operator, AddObjectHelper):",
301 | "\t\"\"\"Create a new Mesh Object\"\"\"",
302 | "\tbl_idname = \"mesh.add_object\"",
303 | "\tbl_label = \"Add Mesh Object\"",
304 | "\tbl_options = {'REGISTER', 'UNDO'}",
305 | "",
306 | "\tscale = FloatVectorProperty(",
307 | "\t\t\tname=\"scale\",",
308 | "\t\t\tdefault=(1.0, 1.0, 1.0),",
309 | "\t\t\tsubtype='TRANSLATION',",
310 | "\t\t\tdescription=\"scaling\",",
311 | "\t\t\t)",
312 | "",
313 | "\tdef execute(self, context):",
314 | "",
315 | "\t\tadd_object(self, context)",
316 | "",
317 | "\t\treturn {'FINISHED'}",
318 | "",
319 | "",
320 | "# Registration",
321 | "",
322 | "def add_object_button(self, context):",
323 | "\tself.layout.operator(",
324 | "\t\tOBJECT_OT_add_object.bl_idname,",
325 | "\t\ttext=\"Add Object\",",
326 | "\t\ticon='PLUGIN')",
327 | "",
328 | "",
329 | "# This allows you to right click on a button and link to the manual",
330 | "def add_object_manual_map():",
331 | "\turl_manual_prefix = \"https://docs.blender.org/manual/en/dev/\"",
332 | "\turl_manual_mapping = (",
333 | "\t\t(\"bpy.ops.mesh.add_object\", \"editors/3dview/object\"),",
334 | "\t\t)",
335 | "\treturn url_manual_prefix, url_manual_mapping",
336 | "",
337 | "",
338 | "def register():",
339 | "\tbpy.utils.register_class(OBJECT_OT_add_object)",
340 | "\tbpy.utils.register_manual_map(add_object_manual_map)",
341 | "\tbpy.types.INFO_MT_mesh_add.append(add_object_button)",
342 | "",
343 | "",
344 | "def unregister():",
345 | "\tbpy.utils.unregister_class(OBJECT_OT_add_object)",
346 | "\tbpy.utils.unregister_manual_map(add_object_manual_map)",
347 | "\tbpy.types.INFO_MT_mesh_add.remove(add_object_button)",
348 | "",
349 | "",
350 | "if __name__ == \"__main__\":",
351 | "\tregister()"
352 | ],
353 | "description": "Example of addon for adding object"
354 | },
355 | "template_background_job":{
356 | "prefix": "template background job",
357 | "body": [
358 | "# This script is an example of how you can run blender from the command line",
359 | "# (in background mode with no interface) to automate tasks, in this example it",
360 | "# creates a text object, camera and light, then renders and/or saves it.",
361 | "# This example also shows how you can parse command line options to scripts.",
362 | "#",
363 | "# Example usage for this test.",
364 | "# blender --background --factory-startup --python \\$HOME/background_job.py -- \\",
365 | "# --text=\"Hello World\" \\",
366 | "# --render=\"/tmp/hello\" \\",
367 | "# --save=\"/tmp/hello.blend\"",
368 | "#",
369 | "# Notice:",
370 | "# '--factory-startup' is used to avoid the user default settings from",
371 | "# interfering with automated scene generation.",
372 | "#",
373 | "# '--' causes blender to ignore all following arguments so python can use them.",
374 | "#",
375 | "# See blender --help for details.",
376 | "",
377 | "import bpy",
378 | "",
379 | "",
380 | "def example_function(text, save_path, render_path):",
381 | "",
382 | "\tscene = bpy.context.scene",
383 | "",
384 | "\t# Clear existing objects.",
385 | "\tscene.camera = None",
386 | "\tfor obj in scene.objects:",
387 | "\t\tscene.objects.unlink(obj)",
388 | "",
389 | "\ttxt_data = bpy.data.curves.new(name=\"MyText\", type='FONT')",
390 | "",
391 | "\t# Text Object",
392 | "\ttxt_ob = bpy.data.objects.new(name=\"MyText\", object_data=txt_data)",
393 | "\tscene.objects.link(txt_ob) # add the data to the scene as an object",
394 | "\ttxt_data.body = text # the body text to the command line arg given",
395 | "\ttxt_data.align = 'CENTER' # center text",
396 | "",
397 | "\t# Camera",
398 | "\tcam_data = bpy.data.cameras.new(\"MyCam\")",
399 | "\tcam_ob = bpy.data.objects.new(name=\"MyCam\", object_data=cam_data)",
400 | "\tscene.objects.link(cam_ob) # instance the camera object in the scene",
401 | "\tscene.camera = cam_ob # set the active camera",
402 | "\tcam_ob.location = 0.0, 0.0, 10.0",
403 | "",
404 | "\t# Lamp",
405 | "\tlamp_data = bpy.data.lamps.new(\"MyLamp\", 'POINT')",
406 | "\tlamp_ob = bpy.data.objects.new(name=\"MyCam\", object_data=lamp_data)",
407 | "\tscene.objects.link(lamp_ob)",
408 | "\tlamp_ob.location = 2.0, 2.0, 5.0",
409 | "",
410 | "\tif save_path:",
411 | "\t\tbpy.ops.wm.save_as_mainfile(filepath=save_path)",
412 | "",
413 | "\tif render_path:",
414 | "\t\trender = scene.render",
415 | "\t\trender.use_file_extension = True",
416 | "\t\trender.filepath = render_path",
417 | "\t\tbpy.ops.render.render(write_still=True)",
418 | "",
419 | "",
420 | "def main():",
421 | "\timport sys # to get command line args",
422 | "\timport argparse # to parse options for us and print a nice help message",
423 | "",
424 | "\t# get the args passed to blender after \"--\", all of which are ignored by",
425 | "\t# blender so scripts may receive their own arguments",
426 | "\targv = sys.argv",
427 | "",
428 | "\tif \"--\" not in argv:",
429 | "\t\targv = [] # as if no args are passed",
430 | "\telse:",
431 | "\t\targv = argv[argv.index(\"--\") + 1:] # get all args after \"--\"",
432 | "",
433 | "\t# When --help or no args are given, print this help",
434 | "\tusage_text = (",
435 | "\t\t\t\"Run blender in background mode with this script:\"",
436 | "\t\t\t\" blender --background --python \" + __file__ + \" -- [options]\"",
437 | "\t\t\t)",
438 | "",
439 | "\tparser = argparse.ArgumentParser(description=usage_text)",
440 | "",
441 | "\t# Example utility, add some text and renders or saves it (with options)",
442 | "\t# Possible types are: string, int, long, choice, float and complex.",
443 | "\tparser.add_argument(\"-t\", \"--text\", dest=\"text\", type=str, required=True,",
444 | "\t\t\thelp=\"This text will be used to render an image\")",
445 | "",
446 | "\tparser.add_argument(\"-s\", \"--save\", dest=\"save_path\", metavar='FILE',",
447 | "\t\t\thelp=\"Save the generated file to the specified path\")",
448 | "\tparser.add_argument(\"-r\", \"--render\", dest=\"render_path\", metavar='FILE',",
449 | "\t\t\thelp=\"Render an image to the specified path\")",
450 | "",
451 | "\targs = parser.parse_args(argv) # In this example we wont use the args",
452 | "",
453 | "\tif not argv:",
454 | "\t\tparser.print_help()",
455 | "\t\treturn",
456 | "",
457 | "\tif not args.text:",
458 | "\t\tprint(\"Error: --text=\\\"some string\\\" argument not given, aborting.\")",
459 | "\t\tparser.print_help()",
460 | "\t\treturn",
461 | "",
462 | "\t# Run the example function",
463 | "\texample_function(args.text, args.save_path, args.render_path)",
464 | "",
465 | "\tprint(\"batch job finished, exiting\")",
466 | "",
467 | "",
468 | "if __name__ == \"__main__\":",
469 | "\tmain()"
470 | ],
471 | "description": "Example of script that shows how you can run blender from the command line (in background mode with no interface) to automate tasks, in this example it creates a text object, camera and light, then renders and/or saves it. This example also shows how you can parse command line options to scripts."
472 | },
473 | "template_batch_export":{
474 | "prefix": "template batch",
475 | "body": [
476 | "# exports each selected object into its own file",
477 | "",
478 | "import bpy",
479 | "import os",
480 | "",
481 | "# export to blend file location",
482 | "basedir = os.path.dirname(bpy.data.filepath)",
483 | "",
484 | "if not basedir:",
485 | "\traise Exception(\"Blend file is not saved\")",
486 | "",
487 | "scene = bpy.context.scene",
488 | "",
489 | "obj_active = scene.objects.active",
490 | "selection = bpy.context.selected_objects",
491 | "",
492 | "bpy.ops.object.select_all(action='DESELECT')",
493 | "",
494 | "for obj in selection:",
495 | "",
496 | "\tobj.select = True",
497 | "",
498 | "\t# some exporters only use the active object",
499 | "\tscene.objects.active = obj",
500 | "",
501 | "\tname = bpy.path.clean_name(obj.name)",
502 | "\tfn = os.path.join(basedir, name)",
503 | "",
504 | "\tbpy.ops.export_scene.fbx(filepath=fn + \".fbx\", use_selection=True)",
505 | "",
506 | "\t## Can be used for multiple formats",
507 | "\t# bpy.ops.export_scene.x3d(filepath=fn + \".x3d\", use_selection=True)",
508 | "",
509 | "\tobj.select = False",
510 | "",
511 | "\tprint(\"written:\", fn)",
512 | "",
513 | "",
514 | "scene.objects.active = obj_active",
515 | "",
516 | "for obj in selection:",
517 | "\tobj.select = True"
518 | ],
519 | "description": "Example of exporting each selected object into its own file"
520 | },
521 | "template_bmesh_simple_editmode":{
522 | "prefix": "template bmesh",
523 | "body": [
524 | "# This example assumes we have a mesh object in edit-mode",
525 | "",
526 | "import bpy",
527 | "import bmesh",
528 | "",
529 | "# Get the active mesh",
530 | "obj = bpy.context.edit_object",
531 | "me = obj.data",
532 | "",
533 | "",
534 | "# Get a BMesh representation",
535 | "bm = bmesh.from_edit_mesh(me)",
536 | "",
537 | "bm.faces.active = None",
538 | "",
539 | "# Modify the BMesh, can do anything here...",
540 | "for v in bm.verts:",
541 | "\tv.co.x += 1.0",
542 | "",
543 | "",
544 | "# Show the updates in the viewport",
545 | "# and recalculate n-gon tessellation.",
546 | "bmesh.update_edit_mesh(me, True)"
547 | ],
548 | "description": "Example of to get mesh representation for bmesh from edit-mode and updating it back after bmesh operation."
549 | },
550 | "template_bmesh_simple_active_object":{
551 | "prefix": "template bmesh",
552 | "body": [
553 | "# This example assumes we have a mesh object selected",
554 | "",
555 | "import bpy",
556 | "import bmesh",
557 | "",
558 | "# Get the active mesh",
559 | "me = bpy.context.object.data",
560 | "",
561 | "",
562 | "# Get a BMesh representation",
563 | "bm = bmesh.new() # create an empty BMesh",
564 | "bm.from_mesh(me) # fill it in from a Mesh",
565 | "",
566 | "",
567 | "# Modify the BMesh, can do anything here...",
568 | "for v in bm.verts:",
569 | "\tv.co.x += 1.0",
570 | "",
571 | "",
572 | "# Finish up, write the bmesh back to the mesh",
573 | "bm.to_mesh(me)",
574 | "bm.free() # free and prevent further access"
575 | ],
576 | "description": "Example of to get mesh representation for bmesh from active object and updating it back after bmesh operation."
577 | },
578 | "template_builtin_keyingset":{
579 | "prefix": "template keyingset",
580 | "body": [
581 | "import bpy",
582 | "",
583 | "",
584 | "class BUILTIN_KSI_hello(bpy.types.KeyingSetInfo):",
585 | "\tbl_label = \"Hello World KeyingSet\"",
586 | "",
587 | "\t# poll - test for whether Keying Set can be used at all",
588 | "\tdef poll(ksi, context):",
589 | "\t\treturn context.active_object or context.selected_objects",
590 | "",
591 | "\t# iterator - go over all relevant data, calling generate()",
592 | "\tdef iterator(ksi, context, ks):",
593 | "\t\tfor ob in context.selected_objects:",
594 | "\t\t\tksi.generate(context, ks, ob)",
595 | "",
596 | "\t# generator - populate Keying Set with property paths to use",
597 | "\tdef generate(ksi, context, ks, data):",
598 | "\t\tid_block = data.id_data",
599 | "",
600 | "\t\tks.paths.add(id_block, \"location\")",
601 | "",
602 | "\t\tfor i in range(5):",
603 | "\t\t\tks.paths.add(id_block, \"layers\", i, group_method='NAMED', group_name=\"5x Hello Layers\")",
604 | "",
605 | "\t\tks.paths.add(id_block, \"show_x_ray\", group_method='NONE')",
606 | "",
607 | "",
608 | "def register():",
609 | "\tbpy.utils.register_class(BUILTIN_KSI_hello)",
610 | "",
611 | "",
612 | "def unregister():",
613 | "\tbpy.utils.unregister_class(BUILTIN_KSI_hello)",
614 | "",
615 | "",
616 | "if __name__ == '__main__':",
617 | "\tregister()"
618 | ],
619 | "description": "Example of generating Keying Set"
620 | },
621 | "template_custom_nodes":{
622 | "prefix": "template nodes",
623 | "body": [
624 | "import bpy",
625 | "from bpy.types import NodeTree, Node, NodeSocket",
626 | "",
627 | "# Implementation of custom nodes from Python",
628 | "",
629 | "",
630 | "# Derived from the NodeTree base type, similar to Menu, Operator, Panel, etc.",
631 | "class MyCustomTree(NodeTree):",
632 | "\t# Description string",
633 | "\t'''A custom node tree type that will show up in the node editor header'''",
634 | "\t# Optional identifier string. If not explicitly defined, the python class name is used.",
635 | "\tbl_idname = 'CustomTreeType'",
636 | "\t# Label for nice name display",
637 | "\tbl_label = 'Custom Node Tree'",
638 | "\t# Icon identifier",
639 | "\tbl_icon = 'NODETREE'",
640 | "",
641 | "",
642 | "# Custom socket type",
643 | "class MyCustomSocket(NodeSocket):",
644 | "\t# Description string",
645 | "\t'''Custom node socket type'''",
646 | "\t# Optional identifier string. If not explicitly defined, the python class name is used.",
647 | "\tbl_idname = 'CustomSocketType'",
648 | "\t# Label for nice name display",
649 | "\tbl_label = 'Custom Node Socket'",
650 | "",
651 | "\t# Enum items list",
652 | "\tmy_items = [",
653 | "\t\t(\"DOWN\", \"Down\", \"Where your feet are\"),",
654 | "\t\t(\"UP\", \"Up\", \"Where your head should be\"),",
655 | "\t\t(\"LEFT\", \"Left\", \"Not right\"),",
656 | "\t\t(\"RIGHT\", \"Right\", \"Not left\")",
657 | "\t]",
658 | "",
659 | "\tmyEnumProperty = bpy.props.EnumProperty(name=\"Direction\", description=\"Just an example\", items=my_items, default='UP')",
660 | "",
661 | "\t# Optional function for drawing the socket input value",
662 | "\tdef draw(self, context, layout, node, text):",
663 | "\t\tif self.is_output or self.is_linked:",
664 | "\t\t\tlayout.label(text)",
665 | "\t\telse:",
666 | "\t\t\tlayout.prop(self, \"myEnumProperty\", text=text)",
667 | "",
668 | "\t# Socket color",
669 | "\tdef draw_color(self, context, node):",
670 | "\t\treturn (1.0, 0.4, 0.216, 0.5)",
671 | "",
672 | "",
673 | "# Mix-in class for all custom nodes in this tree type.",
674 | "# Defines a poll function to enable instantiation.",
675 | "class MyCustomTreeNode:",
676 | "\t@classmethod",
677 | "\tdef poll(cls, ntree):",
678 | "\t\treturn ntree.bl_idname == 'CustomTreeType'",
679 | "",
680 | "",
681 | "# Derived from the Node base type.",
682 | "class MyCustomNode(Node, MyCustomTreeNode):",
683 | "\t# === Basics ===",
684 | "\t# Description string",
685 | "\t'''A custom node'''",
686 | "\t# Optional identifier string. If not explicitly defined, the python class name is used.",
687 | "\tbl_idname = 'CustomNodeType'",
688 | "\t# Label for nice name display",
689 | "\tbl_label = 'Custom Node'",
690 | "\t# Icon identifier",
691 | "\tbl_icon = 'SOUND'",
692 | "",
693 | "\t# === Custom Properties ===",
694 | "\t# These work just like custom properties in ID data blocks",
695 | "\t# Extensive information can be found under",
696 | "\t# http://wiki.blender.org/index.php/Doc:2.6/Manual/Extensions/Python/Properties",
697 | "\tmyStringProperty = bpy.props.StringProperty()",
698 | "\tmyFloatProperty = bpy.props.FloatProperty(default=3.1415926)",
699 | "",
700 | "\t# === Optional Functions ===",
701 | "\t# Initialization function, called when a new node is created.",
702 | "\t# This is the most common place to create the sockets for a node, as shown below.",
703 | "\t# NOTE: this is not the same as the standard __init__ function in Python, which is",
704 | "\t# a purely internal Python method and unknown to the node system!",
705 | "\tdef init(self, context):",
706 | "\t\tself.inputs.new('CustomSocketType', \"Hello\")",
707 | "\t\tself.inputs.new('NodeSocketFloat', \"World\")",
708 | "\t\tself.inputs.new('NodeSocketVector', \"!\")",
709 | "",
710 | "\t\tself.outputs.new('NodeSocketColor', \"How\")",
711 | "\t\tself.outputs.new('NodeSocketColor', \"are\")",
712 | "\t\tself.outputs.new('NodeSocketFloat', \"you\")",
713 | "",
714 | "\t# Copy function to initialize a copied node from an existing one.",
715 | "\tdef copy(self, node):",
716 | "\t\tprint(\"Copying from node \", node)",
717 | "",
718 | "\t# Free function to clean up on removal.",
719 | "\tdef free(self):",
720 | "\t\tprint(\"Removing node \", self, \", Goodbye!\")",
721 | "",
722 | "\t# Additional buttons displayed on the node.",
723 | "\tdef draw_buttons(self, context, layout):",
724 | "\t\tlayout.label(\"Node settings\")",
725 | "\t\tlayout.prop(self, \"myFloatProperty\")",
726 | "",
727 | "\t# Detail buttons in the sidebar.",
728 | "\t# If this function is not defined, the draw_buttons function is used instead",
729 | "\tdef draw_buttons_ext(self, context, layout):",
730 | "\t\tlayout.prop(self, \"myFloatProperty\")",
731 | "\t\t# myStringProperty button will only be visible in the sidebar",
732 | "\t\tlayout.prop(self, \"myStringProperty\")",
733 | "",
734 | "\t# Optional: custom label",
735 | "\t# Explicit user label overrides this, but here we can define a label dynamically",
736 | "\tdef draw_label(self):",
737 | "\t\treturn \"I am a custom node\"",
738 | "",
739 | "",
740 | "### Node Categories ###",
741 | "# Node categories are a python system for automatically",
742 | "# extending the Add menu, toolbar panels and search operator.",
743 | "# For more examples see release/scripts/startup/nodeitems_builtins.py",
744 | "",
745 | "import nodeitems_utils",
746 | "from nodeitems_utils import NodeCategory, NodeItem",
747 | "",
748 | "",
749 | "# our own base class with an appropriate poll function,",
750 | "# so the categories only show up in our own tree type",
751 | "class MyNodeCategory(NodeCategory):",
752 | "\t@classmethod",
753 | "\tdef poll(cls, context):",
754 | "\t\treturn context.space_data.tree_type == 'CustomTreeType'",
755 | "",
756 | "# all categories in a list",
757 | "node_categories = [",
758 | "\t# identifier, label, items list",
759 | "\tMyNodeCategory(\"SOMENODES\", \"Some Nodes\", items=[",
760 | "\t\t# our basic node",
761 | "\t\tNodeItem(\"CustomNodeType\"),",
762 | "\t\t]),",
763 | "\tMyNodeCategory(\"OTHERNODES\", \"Other Nodes\", items=[",
764 | "\t\t# the node item can have additional settings,",
765 | "\t\t# which are applied to new nodes",
766 | "\t\t# NB: settings values are stored as string expressions,",
767 | "\t\t# for this reason they should be converted to strings using repr()",
768 | "\t\tNodeItem(\"CustomNodeType\", label=\"Node A\", settings={",
769 | "\t\t\t\"myStringProperty\": repr(\"Lorem ipsum dolor sit amet\"),",
770 | "\t\t\t\"myFloatProperty\": repr(1.0),",
771 | "\t\t\t}),",
772 | "\t\tNodeItem(\"CustomNodeType\", label=\"Node B\", settings={",
773 | "\t\t\t\"myStringProperty\": repr(\"consectetur adipisicing elit\"),",
774 | "\t\t\t\"myFloatProperty\": repr(2.0),",
775 | "\t\t\t}),",
776 | "\t\t]),",
777 | "\t]",
778 | "",
779 | "",
780 | "def register():",
781 | "\tbpy.utils.register_class(MyCustomTree)",
782 | "\tbpy.utils.register_class(MyCustomSocket)",
783 | "\tbpy.utils.register_class(MyCustomNode)",
784 | "",
785 | "\tnodeitems_utils.register_node_categories(\"CUSTOM_NODES\", node_categories)",
786 | "",
787 | "",
788 | "def unregister():",
789 | "\tnodeitems_utils.unregister_node_categories(\"CUSTOM_NODES\")",
790 | "",
791 | "\tbpy.utils.unregister_class(MyCustomTree)",
792 | "\tbpy.utils.unregister_class(MyCustomSocket)",
793 | "\tbpy.utils.unregister_class(MyCustomNode)",
794 | "",
795 | "",
796 | "if __name__ == \"__main__\":",
797 | "\tregister()"
798 | ],
799 | "description": "Example of Implementation of custom nodes from Python"
800 | },
801 | "template_driver_functions":{
802 | "prefix": "template driver",
803 | "body": [
804 | "# This script defines functions to be used directly in drivers expressions to",
805 | "# extend the builtin set of python functions.",
806 | "#",
807 | "# This can be executed on manually or set to 'Register' to",
808 | "# initialize thefunctions on file load.",
809 | "",
810 | "",
811 | "# two sample functions",
812 | "def invert(f):",
813 | "\t\"\"\" Simple function call:",
814 | "",
815 | "\t invert(val)",
816 | "\t\"\"\"",
817 | "\treturn 1.0 - f",
818 | "",
819 | "",
820 | "uuid_store = {}",
821 | "",
822 | "",
823 | "def slow_value(value, fac, uuid):",
824 | "\t\"\"\" Delay the value by a factor, use a unique string to allow",
825 | "\t use in multiple drivers without conflict:",
826 | "",
827 | "\t slow_value(val, 0.5, \"my_value\")",
828 | "\t\"\"\"",
829 | "\tvalue_prev = uuid_store.get(uuid, value)",
830 | "\tuuid_store[uuid] = value_new = (value_prev * fac) + (value * (1.0 - fac))",
831 | "\treturn value_new",
832 | "",
833 | "",
834 | "import bpy",
835 | "",
836 | "# Add variable defined in this script into the drivers namespace.",
837 | "bpy.app.driver_namespace[\"invert\"] = invert",
838 | "bpy.app.driver_namespace[\"slow_value\"] = slow_value"
839 | ],
840 | "description": "Example of script defining functions to be used directly in drivers expressions to extend the builtin set of python functions."
841 | },
842 | "template_external_script_stub":{
843 | "prefix": "template script",
844 | "body": [
845 | "# This stub runs a python script relative to the currently open",
846 | "# blend file, useful when editing scripts externally.",
847 | "",
848 | "import bpy",
849 | "import os",
850 | "",
851 | "# Use your own script name here:",
852 | "filename = \"my_script.py\"",
853 | "",
854 | "filepath = os.path.join(os.path.dirname(bpy.data.filepath), filename)",
855 | "global_namespace = {\"__file__\": filepath, \"__name__\": \"__main__\"}",
856 | "with open(filepath, 'rb') as file:",
857 | "\texec(compile(file.read(), filepath, 'exec'), global_namespace)"
858 | ],
859 | "description": "Example of loading script relative to current blend file. This stub runs a python script relative to the currently open blend file, useful when editing scripts externally."
860 | },
861 | "template_gamelogic_module":{
862 | "prefix": "template gamelogic",
863 | "body": [
864 | "# This module can be accessed by a python controller with",
865 | "# its execution method set to 'Module'",
866 | "# * Set the module string to \"gamelogic_module.main\" (without quotes)",
867 | "# * When renaming the script it MUST have a .py extension",
868 | "# * External text modules are supported as long as they are at",
869 | "# the same location as the blendfile or one of its libraries.",
870 | "",
871 | "import bge",
872 | "",
873 | "# variables defined here will only be set once when the",
874 | "# module is first imported. Set object specific vars",
875 | "# inside the function if you intend to use the module",
876 | "# with multiple objects.",
877 | "",
878 | "",
879 | "def main(cont):",
880 | "\town = cont.owner",
881 | "",
882 | "\tsens = cont.sensors['mySensor']",
883 | "\tactu = cont.actuators['myActuator']",
884 | "",
885 | "\tif sens.positive:",
886 | "\t\tcont.activate(actu)",
887 | "\telse:",
888 | "\t\tcont.deactivate(actu)",
889 | "",
890 | "# dont call main(bge.logic.getCurrentController()), the py controller will"
891 | ],
892 | "description": "Example of gamelogic module. This module can be accessed by a python controller with its execution method set to 'Module'"
893 | },
894 | "template_gamelogic_simple":{
895 | "prefix": "template gamelogic",
896 | "body": [
897 | "import bge",
898 | "",
899 | "",
900 | "def main():",
901 | "",
902 | "\tcont = bge.logic.getCurrentController()",
903 | "\town = cont.owner",
904 | "",
905 | "\tsens = cont.sensors['mySensor']",
906 | "\tactu = cont.actuators['myActuator']",
907 | "",
908 | "\tif sens.positive:",
909 | "\t\tcont.activate(actu)",
910 | "\telse:",
911 | "\t\tcont.deactivate(actu)",
912 | "",
913 | "main()"
914 | ],
915 | "description": "Example of Simple gamelogic python script."
916 | },
917 | "template_gamelogic":{
918 | "prefix": "template gamelogic",
919 | "body": [
920 | "# This script must be assigned to a python controller",
921 | "# where it can access the object that owns it and the sensors/actuators that it connects to.",
922 | "",
923 | "import bge",
924 | "",
925 | "# support for Vector(), Matrix() types and advanced functions like Matrix.Scale(...) and Matrix.Rotation(...)",
926 | "# import mathutils",
927 | "",
928 | "# for functions like getWindowWidth(), getWindowHeight()",
929 | "# import Rasterizer",
930 | "",
931 | "",
932 | "def main():",
933 | "\tcont = bge.logic.getCurrentController()",
934 | "",
935 | "\t# The KX_GameObject that owns this controller.",
936 | "\town = cont.owner",
937 | "",
938 | "\t# for scripts that deal with spacial logic",
939 | "\town_pos = own.worldPosition",
940 | "",
941 | "\t# Some example functions, remove to write your own script.",
942 | "\t# check for a positive sensor, will run on any object without errors.",
943 | "\tprint(\"Logic info for KX_GameObject\", own.name)",
944 | "\tinput = False",
945 | "",
946 | "\tfor sens in cont.sensors:",
947 | "\t\t# The sensor can be on another object, we may want to use it",
948 | "\t\town_sens = sens.owner",
949 | "\t\tprint(\" sensor:\", sens.name, end=\" \")",
950 | "\t\tif sens.positive:",
951 | "\t\t\tprint(\"(true)\")",
952 | "\t\t\tinput = True",
953 | "\t\telse:",
954 | "\t\t\tprint(\"(false)\")",
955 | "",
956 | "\tfor actu in cont.actuators:",
957 | "\t\t# The actuator can be on another object, we may want to use it",
958 | "\t\town_actu = actu.owner",
959 | "\t\tprint(\" actuator:\", actu.name)",
960 | "",
961 | "\t\t# This runs the actuator or turns it off",
962 | "\t\t# note that actuators will continue to run unless explicitly turned off.",
963 | "\t\tif input:",
964 | "\t\t\tcont.activate(actu)",
965 | "\t\telse:",
966 | "\t\t\tcont.deactivate(actu)",
967 | "",
968 | "\t# Its also good practice to get sensors and actuators by name",
969 | "\t# rather then index so any changes to their order wont break the script.",
970 | "",
971 | "\t# sens_key = cont.sensors[\"key_sensor\"]",
972 | "\t# actu_motion = cont.actuators[\"motion\"]",
973 | "",
974 | "\t# Loop through all other objects in the scene",
975 | "\tsce = bge.logic.getCurrentScene()",
976 | "\tprint(\"Scene Objects:\", sce.name)",
977 | "\tfor ob in sce.objects:",
978 | "\t\tprint(\" \", ob.name, ob.worldPosition)",
979 | "",
980 | "\t# Example where collision objects are checked for their properties",
981 | "\t# adding to our objects \"life\" property",
982 | "\t\"\"\"",
983 | "\tactu_collide = cont.sensors[\"collision_sens\"]",
984 | "\tfor ob in actu_collide.hitObjectList:",
985 | "\t\t# Check to see the object has this property",
986 | "\t\tif \"life\" in ob:",
987 | "\t\t\town[\"life\"] += ob[\"life\"]",
988 | "\t\t\tob[\"life\"] = 0",
989 | "\tprint(own[\"life\"])",
990 | "\t\"\"\"",
991 | "",
992 | "main()"
993 | ],
994 | "description": "Example of gamelogic script this must be assigned to a python controller where it can access the object that owns it and the sensors/actuators that it connects to."
995 | },
996 | "template_operator_file_export":{
997 | "prefix": "template operator",
998 | "body": [
999 | "import bpy",
1000 | "",
1001 | "",
1002 | "def write_some_data(context, filepath, use_some_setting):",
1003 | "\tprint(\"running write_some_data...\")",
1004 | "\tf = open(filepath, 'w', encoding='utf-8')",
1005 | "\tf.write(\"Hello World %s\" % use_some_setting)",
1006 | "\tf.close()",
1007 | "",
1008 | "\treturn {'FINISHED'}",
1009 | "",
1010 | "",
1011 | "# ExportHelper is a helper class, defines filename and",
1012 | "# invoke() function which calls the file selector.",
1013 | "from bpy_extras.io_utils import ExportHelper",
1014 | "from bpy.props import StringProperty, BoolProperty, EnumProperty",
1015 | "from bpy.types import Operator",
1016 | "",
1017 | "",
1018 | "class ExportSomeData(Operator, ExportHelper):",
1019 | "\t\"\"\"This appears in the tooltip of the operator and in the generated docs\"\"\"",
1020 | "\tbl_idname = \"export_test.some_data\" # important since its how bpy.ops.import_test.some_data is constructed",
1021 | "\tbl_label = \"Export Some Data\"",
1022 | "",
1023 | "\t# ExportHelper mixin class uses this",
1024 | "\tfilename_ext = \".txt\"",
1025 | "",
1026 | "\tfilter_glob = StringProperty(",
1027 | "\t\t\tdefault=\"*.txt\",",
1028 | "\t\t\toptions={'HIDDEN'},",
1029 | "\t\t\tmaxlen=255, # Max internal buffer length, longer would be clamped.",
1030 | "\t\t\t)",
1031 | "",
1032 | "\t# List of operator properties, the attributes will be assigned",
1033 | "\t# to the class instance from the operator settings before calling.",
1034 | "\tuse_setting = BoolProperty(",
1035 | "\t\t\tname=\"Example Boolean\",",
1036 | "\t\t\tdescription=\"Example Tooltip\",",
1037 | "\t\t\tdefault=True,",
1038 | "\t\t\t)",
1039 | "",
1040 | "\ttype = EnumProperty(",
1041 | "\t\t\tname=\"Example Enum\",",
1042 | "\t\t\tdescription=\"Choose between two items\",",
1043 | "\t\t\titems=(('OPT_A', \"First Option\", \"Description one\"),",
1044 | "\t\t\t ('OPT_B', \"Second Option\", \"Description two\")),",
1045 | "\t\t\tdefault='OPT_A',",
1046 | "\t\t\t)",
1047 | "",
1048 | "\tdef execute(self, context):",
1049 | "\t\treturn write_some_data(context, self.filepath, self.use_setting)",
1050 | "",
1051 | "",
1052 | "# Only needed if you want to add into a dynamic menu",
1053 | "def menu_func_export(self, context):",
1054 | "\tself.layout.operator(ExportSomeData.bl_idname, text=\"Text Export Operator\")",
1055 | "",
1056 | "",
1057 | "def register():",
1058 | "\tbpy.utils.register_class(ExportSomeData)",
1059 | "\tbpy.types.INFO_MT_file_export.append(menu_func_export)",
1060 | "",
1061 | "",
1062 | "def unregister():",
1063 | "\tbpy.utils.unregister_class(ExportSomeData)",
1064 | "\tbpy.types.INFO_MT_file_export.remove(menu_func_export)",
1065 | "",
1066 | "",
1067 | "if __name__ == \"__main__\":",
1068 | "\tregister()",
1069 | "",
1070 | "\t# test call",
1071 | "\tbpy.ops.export_test.some_data('INVOKE_DEFAULT')"
1072 | ],
1073 | "description": "Example of Template for file export operator, operator exports data from blender to .txt file"
1074 | },
1075 | "template_operator_file_import":{
1076 | "prefix": "template operator",
1077 | "body": [
1078 | "import bpy",
1079 | "",
1080 | "",
1081 | "def read_some_data(context, filepath, use_some_setting):",
1082 | "\tprint(\"running read_some_data...\")",
1083 | "\tf = open(filepath, 'r', encoding='utf-8')",
1084 | "\tdata = f.read()",
1085 | "\tf.close()",
1086 | "",
1087 | "\t# would normally load the data here",
1088 | "\tprint(data)",
1089 | "",
1090 | "\treturn {'FINISHED'}",
1091 | "",
1092 | "",
1093 | "# ImportHelper is a helper class, defines filename and",
1094 | "# invoke() function which calls the file selector.",
1095 | "from bpy_extras.io_utils import ImportHelper",
1096 | "from bpy.props import StringProperty, BoolProperty, EnumProperty",
1097 | "from bpy.types import Operator",
1098 | "",
1099 | "",
1100 | "class ImportSomeData(Operator, ImportHelper):",
1101 | "\t\"\"\"This appears in the tooltip of the operator and in the generated docs\"\"\"",
1102 | "\tbl_idname = \"import_test.some_data\" # important since its how bpy.ops.import_test.some_data is constructed",
1103 | "\tbl_label = \"Import Some Data\"",
1104 | "",
1105 | "\t# ImportHelper mixin class uses this",
1106 | "\tfilename_ext = \".txt\"",
1107 | "",
1108 | "\tfilter_glob = StringProperty(",
1109 | "\t\t\tdefault=\"*.txt\",",
1110 | "\t\t\toptions={'HIDDEN'},",
1111 | "\t\t\tmaxlen=255, # Max internal buffer length, longer would be clamped.",
1112 | "\t\t\t)",
1113 | "",
1114 | "\t# List of operator properties, the attributes will be assigned",
1115 | "\t# to the class instance from the operator settings before calling.",
1116 | "\tuse_setting = BoolProperty(",
1117 | "\t\t\tname=\"Example Boolean\",",
1118 | "\t\t\tdescription=\"Example Tooltip\",",
1119 | "\t\t\tdefault=True,",
1120 | "\t\t\t)",
1121 | "",
1122 | "\ttype = EnumProperty(",
1123 | "\t\t\tname=\"Example Enum\",",
1124 | "\t\t\tdescription=\"Choose between two items\",",
1125 | "\t\t\titems=(('OPT_A', \"First Option\", \"Description one\"),",
1126 | "\t\t\t ('OPT_B', \"Second Option\", \"Description two\")),",
1127 | "\t\t\tdefault='OPT_A',",
1128 | "\t\t\t)",
1129 | "",
1130 | "\tdef execute(self, context):",
1131 | "\t\treturn read_some_data(context, self.filepath, self.use_setting)",
1132 | "",
1133 | "",
1134 | "# Only needed if you want to add into a dynamic menu",
1135 | "def menu_func_import(self, context):",
1136 | "\tself.layout.operator(ImportSomeData.bl_idname, text=\"Text Import Operator\")",
1137 | "",
1138 | "",
1139 | "def register():",
1140 | "\tbpy.utils.register_class(ImportSomeData)",
1141 | "\tbpy.types.INFO_MT_file_import.append(menu_func_import)",
1142 | "",
1143 | "",
1144 | "def unregister():",
1145 | "\tbpy.utils.unregister_class(ImportSomeData)",
1146 | "\tbpy.types.INFO_MT_file_import.remove(menu_func_import)",
1147 | "",
1148 | "",
1149 | "if __name__ == \"__main__\":",
1150 | "\tregister()",
1151 | "",
1152 | "\t# test call",
1153 | "\tbpy.ops.import_test.some_data('INVOKE_DEFAULT')"
1154 | ],
1155 | "description": "Example of Template for file import operator, operator imports data from .txt to blender data"
1156 | },
1157 | "template_opertator_mesh_add":{
1158 | "prefix": "template operator",
1159 | "body": [
1160 | "import bpy",
1161 | "import bmesh",
1162 | "",
1163 | "",
1164 | "def add_box(width, height, depth):",
1165 | "\t\"\"\"",
1166 | "\tThis function takes inputs and returns vertex and face arrays.",
1167 | "\tno actual mesh data creation is done here.",
1168 | "\t\"\"\"",
1169 | "",
1170 | "\tverts = [(+1.0, +1.0, -1.0),",
1171 | "\t (+1.0, -1.0, -1.0),",
1172 | "\t (-1.0, -1.0, -1.0),",
1173 | "\t (-1.0, +1.0, -1.0),",
1174 | "\t (+1.0, +1.0, +1.0),",
1175 | "\t (+1.0, -1.0, +1.0),",
1176 | "\t (-1.0, -1.0, +1.0),",
1177 | "\t (-1.0, +1.0, +1.0),",
1178 | "\t ]",
1179 | "",
1180 | "\tfaces = [(0, 1, 2, 3),",
1181 | "\t (4, 7, 6, 5),",
1182 | "\t (0, 4, 5, 1),",
1183 | "\t (1, 5, 6, 2),",
1184 | "\t (2, 6, 7, 3),",
1185 | "\t (4, 0, 3, 7),",
1186 | "\t ]",
1187 | "",
1188 | "\t# apply size",
1189 | "\tfor i, v in enumerate(verts):",
1190 | "\t\tverts[i] = v[0] * width, v[1] * depth, v[2] * height",
1191 | "",
1192 | "\treturn verts, faces",
1193 | "",
1194 | "",
1195 | "from bpy.props import (",
1196 | "\t\tBoolProperty,",
1197 | "\t\tBoolVectorProperty,",
1198 | "\t\tFloatProperty,",
1199 | "\t\tFloatVectorProperty,",
1200 | "\t\t)",
1201 | "",
1202 | "",
1203 | "class AddBox(bpy.types.Operator):",
1204 | "\t\"\"\"Add a simple box mesh\"\"\"",
1205 | "\tbl_idname = \"mesh.primitive_box_add\"",
1206 | "\tbl_label = \"Add Box\"",
1207 | "\tbl_options = {'REGISTER', 'UNDO'}",
1208 | "",
1209 | "\twidth = FloatProperty(",
1210 | "\t\t\tname=\"Width\",",
1211 | "\t\t\tdescription=\"Box Width\",",
1212 | "\t\t\tmin=0.01, max=100.0,",
1213 | "\t\t\tdefault=1.0,",
1214 | "\t\t\t)",
1215 | "\theight = FloatProperty(",
1216 | "\t\t\tname=\"Height\",",
1217 | "\t\t\tdescription=\"Box Height\",",
1218 | "\t\t\tmin=0.01, max=100.0,",
1219 | "\t\t\tdefault=1.0,",
1220 | "\t\t\t)",
1221 | "\tdepth = FloatProperty(",
1222 | "\t\t\tname=\"Depth\",",
1223 | "\t\t\tdescription=\"Box Depth\",",
1224 | "\t\t\tmin=0.01, max=100.0,",
1225 | "\t\t\tdefault=1.0,",
1226 | "\t\t\t)",
1227 | "\tlayers = BoolVectorProperty(",
1228 | "\t\t\tname=\"Layers\",",
1229 | "\t\t\tdescription=\"Object Layers\",",
1230 | "\t\t\tsize=20,",
1231 | "\t\t\toptions={'HIDDEN', 'SKIP_SAVE'},",
1232 | "\t\t\t)",
1233 | "",
1234 | "\t# generic transform props",
1235 | "\tview_align = BoolProperty(",
1236 | "\t\t\tname=\"Align to View\",",
1237 | "\t\t\tdefault=False,",
1238 | "\t\t\t)",
1239 | "\tlocation = FloatVectorProperty(",
1240 | "\t\t\tname=\"Location\",",
1241 | "\t\t\tsubtype='TRANSLATION',",
1242 | "\t\t\t)",
1243 | "\trotation = FloatVectorProperty(",
1244 | "\t\t\tname=\"Rotation\",",
1245 | "\t\t\tsubtype='EULER',",
1246 | "\t\t\t)",
1247 | "",
1248 | "\tdef execute(self, context):",
1249 | "",
1250 | "\t\tverts_loc, faces = add_box(self.width,",
1251 | "\t\t self.height,",
1252 | "\t\t self.depth,",
1253 | "\t\t )",
1254 | "",
1255 | "\t\tmesh = bpy.data.meshes.new(\"Box\")",
1256 | "",
1257 | "\t\tbm = bmesh.new()",
1258 | "",
1259 | "\t\tfor v_co in verts_loc:",
1260 | "\t\t\tbm.verts.new(v_co)",
1261 | "",
1262 | "\t\tbm.verts.ensure_lookup_table()",
1263 | "\t\tfor f_idx in faces:",
1264 | "\t\t\tbm.faces.new([bm.verts[i] for i in f_idx])",
1265 | "",
1266 | "\t\tbm.to_mesh(mesh)",
1267 | "\t\tmesh.update()",
1268 | "",
1269 | "\t\t# add the mesh as an object into the scene with this utility module",
1270 | "\t\tfrom bpy_extras import object_utils",
1271 | "\t\tobject_utils.object_data_add(context, mesh, operator=self)",
1272 | "",
1273 | "\t\treturn {'FINISHED'}",
1274 | "",
1275 | "",
1276 | "def menu_func(self, context):",
1277 | "\tself.layout.operator(AddBox.bl_idname, icon='MESH_CUBE')",
1278 | "",
1279 | "",
1280 | "def register():",
1281 | "\tbpy.utils.register_class(AddBox)",
1282 | "\tbpy.types.INFO_MT_mesh_add.append(menu_func)",
1283 | "",
1284 | "",
1285 | "def unregister():",
1286 | "\tbpy.utils.unregister_class(AddBox)",
1287 | "\tbpy.types.INFO_MT_mesh_add.remove(menu_func)",
1288 | "",
1289 | "if __name__ == \"__main__\":",
1290 | "\tregister()",
1291 | "",
1292 | "\t# test call",
1293 | "\tbpy.ops.mesh.primitive_box_add()"
1294 | ],
1295 | "description": "Example of operator involving bmesh for creating and adding object to scene"
1296 | },
1297 | "template_operator_mesh_uv":{
1298 | "prefix": "template operator",
1299 | "body": [
1300 | "import bpy",
1301 | "import bmesh",
1302 | "",
1303 | "",
1304 | "def main(context):",
1305 | "\tobj = context.active_object",
1306 | "\tme = obj.data",
1307 | "\tbm = bmesh.from_edit_mesh(me)",
1308 | "",
1309 | "\tuv_layer = bm.loops.layers.uv.verify()",
1310 | "\tbm.faces.layers.tex.verify() # currently blender needs both layers.",
1311 | "",
1312 | "\t# adjust UVs",
1313 | "\tfor f in bm.faces:",
1314 | "\t\tfor l in f.loops:",
1315 | "\t\t\tluv = l[uv_layer]",
1316 | "\t\t\tif luv.select:",
1317 | "\t\t\t\t# apply the location of the vertex as a UV",
1318 | "\t\t\t\tluv.uv = l.vert.co.xy",
1319 | "",
1320 | "\tbmesh.update_edit_mesh(me)",
1321 | "",
1322 | "",
1323 | "class UvOperator(bpy.types.Operator):",
1324 | "\t\"\"\"UV Operator description\"\"\"",
1325 | "\tbl_idname = \"uv.simple_operator\"",
1326 | "\tbl_label = \"Simple UV Operator\"",
1327 | "",
1328 | "\t@classmethod",
1329 | "\tdef poll(cls, context):",
1330 | "\t\treturn (context.mode == 'EDIT_MESH')",
1331 | "",
1332 | "\tdef execute(self, context):",
1333 | "\t\tmain(context)",
1334 | "\t\treturn {'FINISHED'}",
1335 | "",
1336 | "",
1337 | "def register():",
1338 | "\tbpy.utils.register_class(UvOperator)",
1339 | "",
1340 | "",
1341 | "def unregister():",
1342 | "\tbpy.utils.unregister_class(UvOperator)",
1343 | "",
1344 | "",
1345 | "if __name__ == \"__main__\":",
1346 | "\tregister()",
1347 | "",
1348 | "\t# test call",
1349 | "\tbpy.ops.uv.simple_operator()"
1350 | ],
1351 | "description": "Example of Operator template for editing mesh UV's with bmesh"
1352 | },
1353 | "template_operator_simple":{
1354 | "prefix": "template operator",
1355 | "body": [
1356 | "import bpy",
1357 | "",
1358 | "",
1359 | "def main(context):",
1360 | "\tfor ob in context.scene.objects:",
1361 | "\t\tprint(ob)",
1362 | "",
1363 | "",
1364 | "class SimpleOperator(bpy.types.Operator):",
1365 | "\t\"\"\"Tooltip\"\"\"",
1366 | "\tbl_idname = \"object.simple_operator\"",
1367 | "\tbl_label = \"Simple Object Operator\"",
1368 | "",
1369 | "\t@classmethod",
1370 | "\tdef poll(cls, context):",
1371 | "\t\treturn context.active_object is not None",
1372 | "",
1373 | "\tdef execute(self, context):",
1374 | "\t\tmain(context)",
1375 | "\t\treturn {'FINISHED'}",
1376 | "",
1377 | "",
1378 | "def register():",
1379 | "\tbpy.utils.register_class(SimpleOperator)",
1380 | "",
1381 | "",
1382 | "def unregister():",
1383 | "\tbpy.utils.unregister_class(SimpleOperator)",
1384 | "",
1385 | "",
1386 | "if __name__ == \"__main__\":",
1387 | "\tregister()",
1388 | "",
1389 | "\t# test call",
1390 | "\tbpy.ops.object.simple_operator()"
1391 | ],
1392 | "description": "Example of Blender modal operator function with imports, main function, register, unregister and testcall"
1393 | },
1394 | "template_ui_list_w_panel":{
1395 | "prefix": "template ui list",
1396 | "body": [
1397 | "import bpy",
1398 | "",
1399 | "",
1400 | "class MATERIAL_UL_matslots_example(bpy.types.UIList):",
1401 | "\t# The draw_item function is called for each item of the collection that is visible in the list.",
1402 | "\t# data is the RNA object containing the collection,",
1403 | "\t# item is the current drawn item of the collection,",
1404 | "\t# icon is the \"computed\" icon for the item (as an integer, because some objects like materials or textures",
1405 | "\t# have custom icons ID, which are not available as enum items).",
1406 | "\t# active_data is the RNA object containing the active property for the collection (i.e. integer pointing to the",
1407 | "\t# active item of the collection).",
1408 | "\t# active_propname is the name of the active property (use 'getattr(active_data, active_propname)').",
1409 | "\t# index is index of the current item in the collection.",
1410 | "\t# flt_flag is the result of the filtering process for this item.",
1411 | "\t# Note: as index and flt_flag are optional arguments, you do not have to use/declare them here if you don't",
1412 | "\t# need them.",
1413 | "\tdef draw_item(self, context, layout, data, item, icon, active_data, active_propname):",
1414 | "\t\tob = data",
1415 | "\t\tslot = item",
1416 | "\t\tma = slot.material",
1417 | "\t\t# draw_item must handle the three layout types... Usually 'DEFAULT' and 'COMPACT' can share the same code.",
1418 | "\t\tif self.layout_type in {'DEFAULT', 'COMPACT'}:",
1419 | "\t\t\t# You should always start your row layout by a label (icon + text), or a non-embossed text field,",
1420 | "\t\t\t# this will also make the row easily selectable in the list! The later also enables ctrl-click rename.",
1421 | "\t\t\t# We use icon_value of label, as our given icon is an integer value, not an enum ID.",
1422 | "\t\t\t# Note \"data\" names should never be translated!",
1423 | "\t\t\tif ma:",
1424 | "\t\t\t\tlayout.prop(ma, \"name\", text=\"\", emboss=False, icon_value=icon)",
1425 | "\t\t\telse:",
1426 | "\t\t\t\tlayout.label(text=\"\", translate=False, icon_value=icon)",
1427 | "\t\t\t# And now we can add other UI stuff...",
1428 | "\t\t\t# Here, we add nodes info if this material uses (old!) shading nodes.",
1429 | "\t\t\tif ma and not context.scene.render.use_shading_nodes:",
1430 | "\t\t\t\tmanode = ma.active_node_material",
1431 | "\t\t\t\tif manode:",
1432 | "\t\t\t\t\t# The static method UILayout.icon returns the integer value of the icon ID \"computed\" for the given",
1433 | "\t\t\t\t\t# RNA object.",
1434 | "\t\t\t\t\tlayout.label(text=\"Node %s\" % manode.name, translate=False, icon_value=layout.icon(manode))",
1435 | "\t\t\t\telif ma.use_nodes:",
1436 | "\t\t\t\t\tlayout.label(text=\"Node \", translate=False)",
1437 | "\t\t\t\telse:",
1438 | "\t\t\t\t\tlayout.label(text=\"\")",
1439 | "\t\t# 'GRID' layout type should be as compact as possible (typically a single icon!).",
1440 | "\t\telif self.layout_type in {'GRID'}:",
1441 | "\t\t\tlayout.alignment = 'CENTER'",
1442 | "\t\t\tlayout.label(text=\"\", icon_value=icon)",
1443 | "",
1444 | "",
1445 | "# And now we can use this list everywhere in Blender. Here is a small example panel.",
1446 | "class UIListPanelExample(bpy.types.Panel):",
1447 | "\t\"\"\"Creates a Panel in the Object properties window\"\"\"",
1448 | "\tbl_label = \"UIList Panel\"",
1449 | "\tbl_idname = \"OBJECT_PT_ui_list_example\"",
1450 | "\tbl_space_type = 'PROPERTIES'",
1451 | "\tbl_region_type = 'WINDOW'",
1452 | "\tbl_context = \"object\"",
1453 | "",
1454 | "\tdef draw(self, context):",
1455 | "\t\tlayout = self.layout",
1456 | "",
1457 | "\t\tobj = context.object",
1458 | "",
1459 | "\t\t# template_list now takes two new args.",
1460 | "\t\t# The first one is the identifier of the registered UIList to use (if you want only the default list,",
1461 | "\t\t# with no custom draw code, use \"UI_UL_list\").",
1462 | "\t\tlayout.template_list(\"MATERIAL_UL_matslots_example\", \"\", obj, \"material_slots\", obj, \"active_material_index\")",
1463 | "",
1464 | "\t\t# The second one can usually be left as an empty string. It's an additional ID used to distinguish lists in case you",
1465 | "\t\t# use the same list several times in a given area.",
1466 | "\t\tlayout.template_list(\"MATERIAL_UL_matslots_example\", \"compact\", obj, \"material_slots\",",
1467 | "\t\t obj, \"active_material_index\", type='COMPACT')",
1468 | "",
1469 | "",
1470 | "def register():",
1471 | "\tbpy.utils.register_class(MATERIAL_UL_matslots_example)",
1472 | "\tbpy.utils.register_class(UIListPanelExample)",
1473 | "",
1474 | "",
1475 | "def unregister():",
1476 | "\tbpy.utils.unregister_class(MATERIAL_UL_matslots_example)",
1477 | "\tbpy.utils.unregister_class(UIListPanelExample)",
1478 | "",
1479 | "",
1480 | "if __name__ == \"__main__\":",
1481 | "\tregister()"
1482 | ],
1483 | "description": "Example of ui list template with adding it to blender with example panel"
1484 | },
1485 | "template_ui_list":{
1486 | "prefix": "template ui list",
1487 | "body": [
1488 | "import bpy",
1489 | "",
1490 | "",
1491 | "class MESH_UL_mylist(bpy.types.UIList):",
1492 | "\t# Constants (flags)",
1493 | "\t# Be careful not to shadow FILTER_ITEM (i.e. UIList().bitflag_filter_item)!",
1494 | "\t# E.g. VGROUP_EMPTY = 1 << 0",
1495 | "",
1496 | "\t# Custom properties, saved with .blend file. E.g.",
1497 | "\t# use_filter_empty = bpy.props.BoolProperty(name=\"Filter Empty\", default=False, options=set(),",
1498 | "\t# description=\"Whether to filter empty vertex groups\")",
1499 | "",
1500 | "\t# Called for each drawn item.",
1501 | "\tdef draw_item(self, context, layout, data, item, icon, active_data, active_propname, index, flt_flag):",
1502 | "\t\t# 'DEFAULT' and 'COMPACT' layout types should usually use the same draw code.",
1503 | "\t\tif self.layout_type in {'DEFAULT', 'COMPACT'}:",
1504 | "\t\t\tpass",
1505 | "\t\t# 'GRID' layout type should be as compact as possible (typically a single icon!).",
1506 | "\t\telif self.layout_type in {'GRID'}:",
1507 | "\t\t\tpass",
1508 | "",
1509 | "\t# Called once to draw filtering/reordering options.",
1510 | "\tdef draw_filter(self, context, layout):",
1511 | "\t\t# Nothing much to say here, it's usual UI code...",
1512 | "\t\tpass",
1513 | "",
1514 | "\t# Called once to filter/reorder items.",
1515 | "\tdef filter_items(self, context, data, propname):",
1516 | "\t\t# This function gets the collection property (as the usual tuple (data, propname)), and must return two lists:",
1517 | "\t\t# * The first one is for filtering, it must contain 32bit integers were self.bitflag_filter_item marks the",
1518 | "\t\t# matching item as filtered (i.e. to be shown), and 31 other bits are free for custom needs. Here we use the",
1519 | "\t\t# first one to mark VGROUP_EMPTY.",
1520 | "\t\t# * The second one is for reordering, it must return a list containing the new indices of the items (which",
1521 | "\t\t# gives us a mapping org_idx -> new_idx).",
1522 | "\t\t# Please note that the default UI_UL_list defines helper functions for common tasks (see its doc for more info).",
1523 | "\t\t# If you do not make filtering and/or ordering, return empty list(s) (this will be more efficient than",
1524 | "\t\t# returning full lists doing nothing!).",
1525 | "",
1526 | "\t\t# Default return values.",
1527 | "\t\tflt_flags = []",
1528 | "\t\tflt_neworder = []",
1529 | "",
1530 | "\t\t# Do filtering/reordering here...",
1531 | "",
1532 | "\t\treturn flt_flags, flt_neworder"
1533 | ],
1534 | "description": "Example of simple ui list class with some filtering and bpy import"
1535 | },
1536 | "template_ui_menu_simple":{
1537 | "prefix": "template ui menu",
1538 | "body": [
1539 | "import bpy",
1540 | "",
1541 | "",
1542 | "class SimpleCustomMenu(bpy.types.Menu):",
1543 | "\tbl_label = \"Simple Custom Menu\"",
1544 | "\tbl_idname = \"OBJECT_MT_simple_custom_menu\"",
1545 | "",
1546 | "\tdef draw(self, context):",
1547 | "\t\tlayout = self.layout",
1548 | "",
1549 | "\t\tlayout.operator(\"wm.open_mainfile\")",
1550 | "\t\tlayout.operator(\"wm.save_as_mainfile\")",
1551 | "",
1552 | "",
1553 | "def register():",
1554 | "\tbpy.utils.register_class(SimpleCustomMenu)",
1555 | "",
1556 | "",
1557 | "def unregister():",
1558 | "\tbpy.utils.unregister_class(SimpleCustomMenu)",
1559 | "",
1560 | "if __name__ == \"__main__\":",
1561 | "\tregister()",
1562 | "",
1563 | "\t# The menu can also be called from scripts",
1564 | "\tbpy.ops.wm.call_menu(name=SimpleCustomMenu.bl_idname)"
1565 | ],
1566 | "description": "Example of ui menu"
1567 | },
1568 | "template_ui_panel_simple":{
1569 | "prefix": "template ui simple panel",
1570 | "body": [
1571 | "import bpy",
1572 | "",
1573 | "",
1574 | "class HelloWorldPanel(bpy.types.Panel):",
1575 | "\t\"\"\"Creates a Panel in the Object properties window\"\"\"",
1576 | "\tbl_label = \"Hello World Panel\"",
1577 | "\tbl_idname = \"OBJECT_PT_hello\"",
1578 | "\tbl_space_type = 'PROPERTIES'",
1579 | "\tbl_region_type = 'WINDOW'",
1580 | "\tbl_context = \"object\"",
1581 | "",
1582 | "\tdef draw(self, context):",
1583 | "\t\tlayout = self.layout",
1584 | "",
1585 | "\t\tobj = context.object",
1586 | "",
1587 | "\t\trow = layout.row()",
1588 | "\t\trow.label(text=\"Hello world!\", icon='WORLD_DATA')",
1589 | "",
1590 | "\t\trow = layout.row()",
1591 | "\t\trow.label(text=\"Active object is: \" + obj.name)",
1592 | "\t\trow = layout.row()",
1593 | "\t\trow.prop(obj, \"name\")",
1594 | "",
1595 | "\t\trow = layout.row()",
1596 | "\t\trow.operator(\"mesh.primitive_cube_add\")",
1597 | "",
1598 | "",
1599 | "def register():",
1600 | "\tbpy.utils.register_class(HelloWorldPanel)",
1601 | "",
1602 | "",
1603 | "def unregister():",
1604 | "\tbpy.utils.unregister_class(HelloWorldPanel)",
1605 | "",
1606 | "",
1607 | "if __name__ == \"__main__\":",
1608 | "\tregister()"
1609 | ],
1610 | "description": "Example of ui panel class with import and register"
1611 | },
1612 | "template_ui_panel":{
1613 | "prefix": "template ui panel",
1614 | "body": [
1615 | "import bpy",
1616 | "",
1617 | "",
1618 | "class LayoutDemoPanel(bpy.types.Panel):",
1619 | "\t\"\"\"Creates a Panel in the scene context of the properties editor\"\"\"",
1620 | "\tbl_label = \"Layout Demo\"",
1621 | "\tbl_idname = \"SCENE_PT_layout\"",
1622 | "\tbl_space_type = 'PROPERTIES'",
1623 | "\tbl_region_type = 'WINDOW'",
1624 | "\tbl_context = \"scene\"",
1625 | "",
1626 | "\tdef draw(self, context):",
1627 | "\t\tlayout = self.layout",
1628 | "",
1629 | "\t\tscene = context.scene",
1630 | "",
1631 | "\t\t# Create a simple row.",
1632 | "\t\tlayout.label(text=\" Simple Row:\")",
1633 | "",
1634 | "\t\trow = layout.row()",
1635 | "\t\trow.prop(scene, \"frame_start\")",
1636 | "\t\trow.prop(scene, \"frame_end\")",
1637 | "",
1638 | "\t\t# Create an row where the buttons are aligned to each other.",
1639 | "\t\tlayout.label(text=\" Aligned Row:\")",
1640 | "",
1641 | "\t\trow = layout.row(align=True)",
1642 | "\t\trow.prop(scene, \"frame_start\")",
1643 | "\t\trow.prop(scene, \"frame_end\")",
1644 | "",
1645 | "\t\t# Create two columns, by using a split layout.",
1646 | "\t\tsplit = layout.split()",
1647 | "",
1648 | "\t\t# First column",
1649 | "\t\tcol = split.column()",
1650 | "\t\tcol.label(text=\"Column One:\")",
1651 | "\t\tcol.prop(scene, \"frame_end\")",
1652 | "\t\tcol.prop(scene, \"frame_start\")",
1653 | "",
1654 | "\t\t# Second column, aligned",
1655 | "\t\tcol = split.column(align=True)",
1656 | "\t\tcol.label(text=\"Column Two:\")",
1657 | "\t\tcol.prop(scene, \"frame_start\")",
1658 | "\t\tcol.prop(scene, \"frame_end\")",
1659 | "",
1660 | "\t\t# Big render button",
1661 | "\t\tlayout.label(text=\"Big Button:\")",
1662 | "\t\trow = layout.row()",
1663 | "\t\trow.scale_y = 3.0",
1664 | "\t\trow.operator(\"render.render\")",
1665 | "",
1666 | "\t\t# Different sizes in a row",
1667 | "\t\tlayout.label(text=\"Different button sizes:\")",
1668 | "\t\trow = layout.row(align=True)",
1669 | "\t\trow.operator(\"render.render\")",
1670 | "",
1671 | "\t\tsub = row.row()",
1672 | "\t\tsub.scale_x = 2.0",
1673 | "\t\tsub.operator(\"render.render\")",
1674 | "",
1675 | "\t\trow.operator(\"render.render\")",
1676 | "",
1677 | "",
1678 | "def register():",
1679 | "\tbpy.utils.register_class(LayoutDemoPanel)",
1680 | "",
1681 | "",
1682 | "def unregister():",
1683 | "\tbpy.utils.unregister_class(LayoutDemoPanel)",
1684 | "",
1685 | "",
1686 | "if __name__ == \"__main__\":",
1687 | "\tregister()"
1688 | ],
1689 | "description": "Example of ui panel class ui panel is created with examples of columns, buttons, rows, properties, with import and register, "
1690 | },
1691 | "template_ui_pie_menu":{
1692 | "prefix": "template ui pie menu",
1693 | "body": [
1694 | "import bpy",
1695 | "from bpy.types import Menu",
1696 | "",
1697 | "# spawn an edit mode selection pie (run while object is in edit mode to get a valid output)",
1698 | "",
1699 | "",
1700 | "class VIEW3D_PIE_template(Menu):",
1701 | "\t# label is displayed at the center of the pie menu.",
1702 | "\tbl_label = \"Select Mode\"",
1703 | "",
1704 | "\tdef draw(self, context):",
1705 | "\t\tlayout = self.layout",
1706 | "",
1707 | "\t\tpie = layout.menu_pie()",
1708 | "\t\t# operator_enum will just spread all available options",
1709 | "\t\t# for the type enum of the operator on the pie",
1710 | "\t\tpie.operator_enum(\"mesh.select_mode\", \"type\")",
1711 | "",
1712 | "",
1713 | "def register():",
1714 | "\tbpy.utils.register_class(VIEW3D_PIE_template)",
1715 | "",
1716 | "",
1717 | "def unregister():",
1718 | "\tbpy.utils.unregister_class(VIEW3D_PIE_template)",
1719 | "",
1720 | "",
1721 | "if __name__ == \"__main__\":",
1722 | "\tregister()",
1723 | "",
1724 | "\tbpy.ops.wm.call_menu_pie(name=\"VIEW3D_PIE_template\")"
1725 | ],
1726 | "description": "Example of 3d viewport pie menu"
1727 | },
1728 | "template_dynamic_enum":{
1729 | "prefix": "template dynamic enum",
1730 | "body": [
1731 | "# This sample script demonstrates a dynamic EnumProperty with custom icons.",
1732 | "# The EnumProperty is populated dynamically with thumbnails of the contents of",
1733 | "# a chosen directory in 'enum_previews_from_directory_items'.",
1734 | "# Then, the same enum is displayed with different interfaces. Note that the",
1735 | "# generated icon previews do not have Blender IDs, which means that they can",
1736 | "# not be used with UILayout templates that require IDs,",
1737 | "# such as template_list and template_ID_preview.",
1738 | "#",
1739 | "# Other use cases:",
1740 | "# - make a fixed list of enum_items instead of calculating them in a function",
1741 | "# - generate isolated thumbnails to use as custom icons in buttons",
1742 | "# and menu items",
1743 | "#",
1744 | "# For custom icons, see the template \"ui_previews_custom_icon.py\".",
1745 | "#",
1746 | "# For distributable scripts, it is recommended to place the icons inside the",
1747 | "# script directory and access it relative to the py script file for portability:",
1748 | "#",
1749 | "#\tos.path.join(os.path.dirname(__file__), \"images\")",
1750 | "",
1751 | "",
1752 | "import os",
1753 | "import bpy",
1754 | "",
1755 | "",
1756 | "def enum_previews_from_directory_items(self, context):",
1757 | "\t\"\"\"EnumProperty callback\"\"\"",
1758 | "\tenum_items = []",
1759 | "",
1760 | "\tif context is None:",
1761 | "\t\treturn enum_items",
1762 | "",
1763 | "\twm = context.window_manager",
1764 | "\tdirectory = wm.my_previews_dir",
1765 | "",
1766 | "\t# Get the preview collection (defined in register func).",
1767 | "\tpcoll = preview_collections[\"main\"]",
1768 | "",
1769 | "\tif directory == pcoll.my_previews_dir:",
1770 | "\t\treturn pcoll.my_previews",
1771 | "",
1772 | "\tprint(\"Scanning directory: %s\" % directory)",
1773 | "",
1774 | "\tif directory and os.path.exists(directory):",
1775 | "\t\t# Scan the directory for png files",
1776 | "\t\timage_paths = []",
1777 | "\t\tfor fn in os.listdir(directory):",
1778 | "\t\t\tif fn.lower().endswith(\".png\"):",
1779 | "\t\t\t\timage_paths.append(fn)",
1780 | "",
1781 | "\t\tfor i, name in enumerate(image_paths):",
1782 | "\t\t\t# generates a thumbnail preview for a file.",
1783 | "\t\t\tfilepath = os.path.join(directory, name)",
1784 | "\t\t\tthumb = pcoll.load(filepath, filepath, 'IMAGE')",
1785 | "\t\t\tenum_items.append((name, name, \"\", thumb.icon_id, i))",
1786 | "",
1787 | "\tpcoll.my_previews = enum_items",
1788 | "\tpcoll.my_previews_dir = directory",
1789 | "\treturn pcoll.my_previews",
1790 | "",
1791 | "",
1792 | "class PreviewsExamplePanel(bpy.types.Panel):",
1793 | "\t\"\"\"Creates a Panel in the Object properties window\"\"\"",
1794 | "\tbl_label = \"Previews Example Panel\"",
1795 | "\tbl_idname = \"OBJECT_PT_previews\"",
1796 | "\tbl_space_type = 'PROPERTIES'",
1797 | "\tbl_region_type = 'WINDOW'",
1798 | "\tbl_context = \"object\"",
1799 | "",
1800 | "\tdef draw(self, context):",
1801 | "\t\tlayout = self.layout",
1802 | "\t\twm = context.window_manager",
1803 | "",
1804 | "\t\trow = layout.row()",
1805 | "\t\trow.prop(wm, \"my_previews_dir\")",
1806 | "",
1807 | "\t\trow = layout.row()",
1808 | "\t\trow.template_icon_view(wm, \"my_previews\")",
1809 | "",
1810 | "\t\trow = layout.row()",
1811 | "\t\trow.prop(wm, \"my_previews\")",
1812 | "",
1813 | "",
1814 | "# We can store multiple preview collections here,",
1815 | "# however in this example we only store \"main\"",
1816 | "preview_collections = {}",
1817 | "",
1818 | "",
1819 | "def register():",
1820 | "\tfrom bpy.types import WindowManager",
1821 | "\tfrom bpy.props import (",
1822 | "\t\t\tStringProperty,",
1823 | "\t\t\tEnumProperty,",
1824 | "\t\t\t)",
1825 | "",
1826 | "\tWindowManager.my_previews_dir = StringProperty(",
1827 | "\t\t\tname=\"Folder Path\",",
1828 | "\t\t\tsubtype='DIR_PATH',",
1829 | "\t\t\tdefault=\"\"",
1830 | "\t\t\t)",
1831 | "",
1832 | "\tWindowManager.my_previews = EnumProperty(",
1833 | "\t\t\titems=enum_previews_from_directory_items,",
1834 | "\t\t\t)",
1835 | "",
1836 | "\t# Note that preview collections returned by bpy.utils.previews",
1837 | "\t# are regular Python objects - you can use them to store custom data.",
1838 | "\t#",
1839 | "\t# This is especially useful here, since:",
1840 | "\t# - It avoids us regenerating the whole enum over and over.",
1841 | "\t# - It can store enum_items' strings",
1842 | "\t# (remember you have to keep those strings somewhere in py,",
1843 | "\t# else they get freed and Blender references invalid memory!).",
1844 | "\timport bpy.utils.previews",
1845 | "\tpcoll = bpy.utils.previews.new()",
1846 | "\tpcoll.my_previews_dir = \"\"",
1847 | "\tpcoll.my_previews = ()",
1848 | "",
1849 | "\tpreview_collections[\"main\"] = pcoll",
1850 | "",
1851 | "\tbpy.utils.register_class(PreviewsExamplePanel)",
1852 | "",
1853 | "",
1854 | "def unregister():",
1855 | "\tfrom bpy.types import WindowManager",
1856 | "",
1857 | "\tdel WindowManager.my_previews",
1858 | "",
1859 | "\tfor pcoll in preview_collections.values():",
1860 | "\t\tbpy.utils.previews.remove(pcoll)",
1861 | "\tpreview_collections.clear()",
1862 | "",
1863 | "\tbpy.utils.unregister_class(PreviewsExamplePanel)",
1864 | "",
1865 | "",
1866 | "if __name__ == \"__main__\":",
1867 | "\tregister()"
1868 | ],
1869 | "description": "This example script demonstrates a dynamic EnumProperty with custom icons."
1870 | },
1871 | "template_ui_previews_custom_icon":{
1872 | "prefix": "template ui previews",
1873 | "body": [
1874 | "# This sample script demonstrates how to place a custom icon on a button or",
1875 | "# menu entry.",
1876 | "#",
1877 | "# IMPORTANT NOTE: if you run this sample, there will be no icon in the button",
1878 | "# You need to replace the image path with a real existing one.",
1879 | "# For distributable scripts, it is recommended to place the icons inside the",
1880 | "# addon folder and access it relative to the py script file for portability",
1881 | "#",
1882 | "#",
1883 | "# Other use cases for UI-previews:",
1884 | "# - provide a fixed list of previews to select from",
1885 | "# - provide a dynamic list of preview (eg. calculated from reading a directory)",
1886 | "#",
1887 | "# For the above use cases, see the template 'ui_previews_dynamic_enum.py\"",
1888 | "",
1889 | "",
1890 | "import os",
1891 | "import bpy",
1892 | "",
1893 | "",
1894 | "class PreviewsExamplePanel(bpy.types.Panel):",
1895 | "\t\"\"\"Creates a Panel in the Object properties window\"\"\"",
1896 | "\tbl_label = \"Previews Example Panel\"",
1897 | "\tbl_idname = \"OBJECT_PT_previews\"",
1898 | "\tbl_space_type = 'PROPERTIES'",
1899 | "\tbl_region_type = 'WINDOW'",
1900 | "\tbl_context = \"object\"",
1901 | "",
1902 | "\tdef draw(self, context):",
1903 | "\t\tlayout = self.layout",
1904 | "\t\tpcoll = preview_collections[\"main\"]",
1905 | "",
1906 | "\t\trow = layout.row()",
1907 | "\t\tmy_icon = pcoll[\"my_icon\"]",
1908 | "\t\trow.operator(\"render.render\", icon_value=my_icon.icon_id)",
1909 | "",
1910 | "\t\t# my_icon.icon_id can be used in any UI function that accepts",
1911 | "\t\t# icon_value # try also setting text=\"\"",
1912 | "\t\t# to get an icon only operator button",
1913 | "",
1914 | "",
1915 | "# We can store multiple preview collections here,",
1916 | "# however in this example we only store \"main\"",
1917 | "preview_collections = {}",
1918 | "",
1919 | "",
1920 | "def register():",
1921 | "",
1922 | "\t# Note that preview collections returned by bpy.utils.previews",
1923 | "\t# are regular py objects - you can use them to store custom data.",
1924 | "\timport bpy.utils.previews",
1925 | "\tpcoll = bpy.utils.previews.new()",
1926 | "",
1927 | "\t# path to the folder where the icon is",
1928 | "\t# the path is calculated relative to this py file inside the addon folder",
1929 | "\tmy_icons_dir = os.path.join(os.path.dirname(__file__), \"icons\")",
1930 | "",
1931 | "\t# load a preview thumbnail of a file and store in the previews collection",
1932 | "\tpcoll.load(\"my_icon\", os.path.join(my_icons_dir, \"icon-image.png\"), 'IMAGE')",
1933 | "",
1934 | "\tpreview_collections[\"main\"] = pcoll",
1935 | "",
1936 | "\tbpy.utils.register_class(PreviewsExamplePanel)",
1937 | "",
1938 | "",
1939 | "def unregister():",
1940 | "",
1941 | "\tfor pcoll in preview_collections.values():",
1942 | "\t\tbpy.utils.previews.remove(pcoll)",
1943 | "\tpreview_collections.clear()",
1944 | "",
1945 | "\tbpy.utils.unregister_class(PreviewsExamplePanel)",
1946 | "",
1947 | "",
1948 | "if __name__ == \"__main__\":",
1949 | "\tregister()"
1950 | ],
1951 | "description": "This example script demonstrates how to place a custom icon on a button menu entry."
1952 | }
1953 |
1954 | }
1955 |
--------------------------------------------------------------------------------
/vsc-extension-quickstart.md:
--------------------------------------------------------------------------------
1 | # Welcome to your VS Code Extension
2 |
3 | ## What's in the folder
4 | * This folder contains all of the files necessary for your extension.
5 | * `package.json` - this is the manifest file that defines the location of the snippet file
6 | and specifies the language of the snippets.
7 | * `snippets/snippets.json` - the file containing all snippets.
8 |
9 | ## Get up and running straight away
10 | * Press `F5` to open a new window with your extension loaded.
11 | * Create a new file with a file name suffix matching your language.
12 | * Verify that your snippets are proposed on intellisense.
13 |
14 | ## Make changes
15 | * You can relaunch the extension from the debug toolbar after making changes to the files listed above.
16 | * You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes.
17 |
18 | ## Install your extension
19 | * To start using your extension with Visual Studio Code copy it into the `/.vscode/extensions` folder and restart Code.
20 | * To share your extension with the world, read on https://code.visualstudio.com/docs about publishing an extension.
21 |
--------------------------------------------------------------------------------