├── README.md ├── __init__.py ├── backend.py ├── handlers.py ├── operators.py ├── overlays.py ├── panels.py ├── preferences.py ├── profiling.py ├── properties.py └── tools.py /README.md: -------------------------------------------------------------------------------- 1 | Advanced Blender Add-on 2 | ======================= 3 | 4 | Many great examples are provided as Python templates in Blender, but none of them illustrates of to organize files to write an add-on of more than a single file. 5 | 6 | This repository intends to give a **starter kit** as well as to illustrate and intensively comment **design patterns** commonly used in advanced Blender add-ons. It also gives in the comments pointers to related documentation. 7 | 8 | Feedback 9 | -------- 10 | 11 | **Feel free to suggest or request new examples.** I'll happily make this as exhaustive as possible so that it becomes a reference for add-on developers. 12 | 13 | Feedback can be given through [issues](https://github.com/eliemichel/AdvancedBlenderAddon/issues), the [BlenderArtists thread](https://blenderartists.org/t/wip-advanced-blender-addon-starter-kit/1253453) or on [twitter](https://twitter.com/exppad). 14 | 15 | Usage 16 | ----- 17 | 18 | You can download this repo as a zip and install it as a Blender add-on. Then it features **hot reloading** so you can go to where the add-on is installed (e.g. `C:\Users\Elie\AppData\Roaming\Blender Foundation\Blender\2.90\scripts\addons` or `.blender/2.90/scripts/addons`), modify the files and run "Reload Scripts" in Blender to update the add-on without restarting Blender. 19 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | # ##### BEGIN GPL LICENSE BLOCK ##### 2 | # 3 | # Copyright (c) 2020 Elie Michel 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software Foundation, 17 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | # 19 | # ##### END GPL LICENSE BLOCK ##### 20 | 21 | bl_info = { 22 | "name": "Advanced Blender Addon Demo", 23 | "author": "Élie Michel ", 24 | "version": (0, 1, 0), 25 | "blender": (2, 90, 0), 26 | "location": "Properties > Scene", 27 | "description": "An example of advanced add-on for Blender", 28 | "warning": "", 29 | "wiki_url": "", 30 | "tracker_url": "https://github.com/eliemichel/AdvancedBlenderAddon", 31 | "support": "COMMUNITY", 32 | "category": "3D view", 33 | } 34 | 35 | # ------------------------------------------------------------------- 36 | # This section is dedicated to make the "Reload Scripts" operator of 37 | # Blender truly work to be able to update the add-on while developping. 38 | # This feature only reloads this __init__ file, so we force reloading all 39 | # other files here. 40 | 41 | # When loaded is already in local, we know this is called by "Reload plugins" 42 | if locals().get('loaded') or True: 43 | loaded = False 44 | from importlib import reload 45 | from sys import modules 46 | import os 47 | 48 | for i in range(3): # Try up to three times, in case of ordering errors 49 | err = False 50 | modules[__name__] = reload(modules[__name__]) 51 | submodules = list(modules.items()) 52 | for name, module in submodules: 53 | if name.startswith(f"{__package__}."): 54 | if module.__file__ is None: 55 | # This is a namespace, no need to do anything 56 | continue 57 | elif not os.path.isfile(module.__file__): 58 | # File has been removed 59 | del modules[name] 60 | del globals()[name] 61 | else: 62 | print(f"Reloading: {module}") 63 | try: 64 | globals()[name] = reload(module) 65 | except Exception as e: 66 | print(e) 67 | err = True 68 | if not err: 69 | break 70 | #del reload, modules 71 | 72 | # ------------------------------------------------------------------- 73 | 74 | import bpy 75 | from . import preferences 76 | from . import properties 77 | from . import operators 78 | from . import panels 79 | from . import overlays 80 | from . import tools 81 | from . import handlers 82 | 83 | def register(): 84 | preferences.register() 85 | properties.register() 86 | operators.register() 87 | panels.register() 88 | overlays.register() 89 | tools.register() 90 | handlers.register() 91 | 92 | def unregister(): 93 | preferences.unregister() 94 | properties.unregister() 95 | operators.unregister() 96 | panels.unregister() 97 | overlays.unregister() 98 | tools.unregister() 99 | handlers.unregister() 100 | -------------------------------------------------------------------------------- /backend.py: -------------------------------------------------------------------------------- 1 | # If this module does not use bpy nor any blender related module, it may 2 | # be licensed with something else than the GPL: 3 | 4 | # ##### BEGIN MIT LICENSE BLOCK ##### 5 | # 6 | # Copyright (c) 2020 Elie Michel 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the “Software”), to 9 | # deal in the Software without restriction, including without limitation the 10 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | # sell copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # The Software is provided “as is”, without warranty of any kind, express or 17 | # implied, including but not limited to the warranties of merchantability, 18 | # fitness for a particular purpose and noninfringement. In no event shall the 19 | # authors or copyright holders be liable for any claim, damages or other 20 | # liability, whether in an action of contract, tort or otherwise, arising 21 | # from, out of or in connection with the software or the use or other dealings 22 | # in the Software. 23 | # 24 | # ##### BEGIN MIT LICENSE BLOCK ##### 25 | 26 | import numpy as np 27 | 28 | # This shows how to efficiently access mesh vertex positions as numpy arrays 29 | def compute_centroid(obj): 30 | vertices = obj.data.vertices 31 | 32 | # Buffer of positions as a numpy array 33 | positions = np.zeros((len(vertices), 3), dtype=np.float32) 34 | 35 | # The foreach_get/foreach_set functions are available on many Blender's 36 | # object to efficiently copy internal data into python objects supporting 37 | # the buffer protocole, like for instance numpy arrays: 38 | # (it is important to use ravel() to present the array as single dimensional) 39 | vertices.foreach_get('co', positions.ravel()) 40 | 41 | # Convert to world space 42 | matrix = np.array(obj.matrix_world) 43 | homogenous_coords = np.vstack((positions.T, np.ones((1, len(vertices))))) 44 | world_positions = matrix.dot(homogenous_coords)[:-1,:].T 45 | 46 | return world_positions.mean(axis=0) 47 | -------------------------------------------------------------------------------- /handlers.py: -------------------------------------------------------------------------------- 1 | # ##### BEGIN GPL LICENSE BLOCK ##### 2 | # 3 | # Copyright (c) 2020 Elie Michel 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software Foundation, 17 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | # 19 | # ##### END GPL LICENSE BLOCK ##### 20 | 21 | import bpy 22 | from bpy.app.handlers import load_post, persistent 23 | 24 | # Handlers are callback functions "hooked" to some events of Blender's 25 | # internal loop. They are called whenever some event occurs. 26 | # The full list of available handlers can be found here: 27 | # https://docs.blender.org/api/current/bpy.app.handlers.html 28 | 29 | # ------------------------------------------------------------------- 30 | 31 | # Make sure to make this persistent otherwise handlers get reset prior 32 | # to loading new files. 33 | @persistent 34 | def advanced_blender_addon_on_load(scene): 35 | if scene is not None: 36 | # Typically, you'll initialize some stuff here, or reset any 37 | # global state. 38 | print("A scene has been loaded!") 39 | 40 | # ------------------------------------------------------------------- 41 | 42 | def remove_handler(handlers_list, cb): 43 | """Remove any handler with the same name from a given handlers list""" 44 | to_remove = [h for h in handlers_list if h.__name__ == cb.__name__] 45 | for h in to_remove: 46 | handlers_list.remove(h) 47 | 48 | def register(): 49 | unregister() # remove handlers if they were present already 50 | load_post.append(advanced_blender_addon_on_load) 51 | 52 | def unregister(): 53 | remove_handler(load_post, advanced_blender_addon_on_load) 54 | -------------------------------------------------------------------------------- /operators.py: -------------------------------------------------------------------------------- 1 | # ##### BEGIN GPL LICENSE BLOCK ##### 2 | # 3 | # Copyright (c) 2020 Elie Michel 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software Foundation, 17 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | # 19 | # ##### END GPL LICENSE BLOCK ##### 20 | 21 | import bpy 22 | from bpy.types import Operator 23 | from bpy.props import FloatProperty 24 | from bpy_extras import view3d_utils 25 | 26 | from . import profiling 27 | from . import backend 28 | 29 | # ------------------------------------------------------------------- 30 | 31 | class ResetProfiling(Operator): 32 | bl_idname = "advanced_blender_addon.reset_profiling" 33 | bl_label = "Reset Profiling Counters" 34 | 35 | def execute(self, context): 36 | scene = context.scene 37 | scene.compute_centroid_profiling.reset() 38 | return {'FINISHED'} 39 | 40 | # ------------------------------------------------------------------- 41 | 42 | class ComputeCentroid(Operator): 43 | """Compute the centroid of the object (this message is used as description in the UI)""" 44 | bl_idname = "advanced_blender_addon.compute_centroid" 45 | bl_label = "Compute Centroid" 46 | 47 | @classmethod 48 | def poll(cls, context): 49 | # Ensure that the operator will not be called when the context is 50 | # not compatible with it. Here for instance our operator only applies 51 | # to a mesh. 52 | # It is better to do these context checks here than directly in 53 | # execute() because it will also for instance deactivate buttons 54 | # pointing to this operator in the UI. 55 | return context.active_object is not None and context.active_object.type == 'MESH' 56 | 57 | def execute(self, context): 58 | timer = profiling.Timer() 59 | 60 | # The operator class defines the front-end to a function. Its core 61 | # logic will likely resides in a separate module (called 'backend' here) 62 | # as a regular python function. 63 | centroid = backend.compute_centroid(context.active_object) 64 | 65 | # Record in the profiling property the time spent in the core function 66 | context.scene.compute_centroid_profiling.add_sample(timer.ellapsed()) 67 | 68 | # We can report messages to the user, doc at: 69 | # https://docs.blender.org/api/current/bpy.types.Operator.html#bpy.types.Operator.Operator.report 70 | self.report({'INFO'}, f"Centoid is at {centroid}") 71 | 72 | return {'FINISHED'} 73 | 74 | # ------------------------------------------------------------------- 75 | 76 | class UpdateDemoOverlay(Operator): 77 | """Operator called by the DemoTool tool when the mouse moves""" 78 | bl_idname = "advanced_blender_addon.update_demo_overlay" 79 | bl_label = "Update Demo Overlay" 80 | 81 | radius: FloatProperty( 82 | name="Radius", 83 | description="The radius of the brush in the demo tool", 84 | default=10.0, 85 | ) 86 | 87 | # A tool calls the invoke() method of the operator, providing info 88 | # about e.g. the mouse position 89 | def invoke(self, context, event): 90 | scene = context.scene 91 | 92 | # Project the mouse onto the ground plane 93 | origin, direction = self.get_mouse_ray(context, event) 94 | ground_position = origin - direction * (origin.z / direction.z) 95 | 96 | # And save it in the scene for the overlay to use it 97 | scene.demo_tool_cursor = ground_position 98 | scene.demo_tool_radius = self.radius 99 | 100 | # Request a redraw of the area because the overlay changed 101 | context.area.tag_redraw() 102 | return {'FINISHED'} 103 | 104 | def get_mouse_ray(self, context, event): 105 | """get the ray from the viewport and mouse""" 106 | region, rv3d = context.region, context.region_data 107 | coord = event.mouse_region_x, event.mouse_region_y 108 | ray_direction = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord) 109 | ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord) 110 | return ray_origin, ray_direction 111 | 112 | # ------------------------------------------------------------------- 113 | 114 | class DemoTool(Operator): 115 | """Operator called by the DemoTool tool when clicking""" 116 | bl_idname = "advanced_blender_addon.demo_tool" 117 | bl_label = "Demo Tool Action" 118 | 119 | def invoke(self, context, event): 120 | x = event.mouse_region_x 121 | y = event.mouse_region_y 122 | self.report({'INFO'}, f"DemoTool at position ({x},{y})!") 123 | return {'FINISHED'} 124 | 125 | # ------------------------------------------------------------------- 126 | 127 | classes = ( 128 | ResetProfiling, 129 | ComputeCentroid, 130 | UpdateDemoOverlay, 131 | DemoTool, 132 | ) 133 | register, unregister = bpy.utils.register_classes_factory(classes) 134 | -------------------------------------------------------------------------------- /overlays.py: -------------------------------------------------------------------------------- 1 | # ##### BEGIN GPL LICENSE BLOCK ##### 2 | # 3 | # Copyright (c) 2020 Elie Michel 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software Foundation, 17 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | # 19 | # ##### END GPL LICENSE BLOCK ##### 20 | 21 | import bpy 22 | from bpy.types import Gizmo, GizmoGroup 23 | import bgl 24 | from gpu_extras.presets import draw_circle_2d 25 | 26 | # NB: It is not possible to subclass View3DOverlay so overlays are 27 | # mimicked using a gizmo+gizmogroup with no interaction. 28 | 29 | # ------------------------------------------------------------------- 30 | 31 | # A gizmo implementing only the draw() method is just an overlay, 32 | # but gizmos can actually get way more advanced (see gizmo templates) 33 | class DemoToolWidget(Gizmo): 34 | bl_idname = "VIEW3D_GT_demo_tool" 35 | 36 | def draw(self, context): 37 | # Many custom drawing examples can be found in the gpu module doc: 38 | # https://docs.blender.org/api/current/gpu.html 39 | matrix = context.region_data.perspective_matrix 40 | 41 | bgl.glEnable(bgl.GL_BLEND) 42 | bgl.glEnable(bgl.GL_DEPTH_TEST) 43 | bgl.glLineWidth(2) 44 | 45 | cur = context.scene.demo_tool_cursor 46 | rad = context.scene.demo_tool_radius 47 | draw_circle_2d((cur[0], cur[1]), (1, 1, 1, 1), rad, 200) 48 | 49 | # restore opengl defaults 50 | bgl.glLineWidth(1) 51 | bgl.glDisable(bgl.GL_BLEND) 52 | bgl.glDisable(bgl.GL_DEPTH_TEST) 53 | 54 | # ------------------------------------------------------------------- 55 | 56 | class DemoToolOverlay(GizmoGroup): 57 | bl_idname = "SCENE_GGT_demo_tool" 58 | bl_label = "Demo Tool" 59 | bl_space_type = 'VIEW_3D' 60 | bl_region_type = 'WINDOW' 61 | bl_options = {'3D', 'PERSISTENT'} 62 | 63 | def setup(self, context): 64 | self.gizmos.new(DemoToolWidget.bl_idname) 65 | 66 | # ------------------------------------------------------------------- 67 | 68 | classes = ( 69 | DemoToolWidget, 70 | DemoToolOverlay, 71 | ) 72 | register, unregister = bpy.utils.register_classes_factory(classes) 73 | -------------------------------------------------------------------------------- /panels.py: -------------------------------------------------------------------------------- 1 | # ##### BEGIN GPL LICENSE BLOCK ##### 2 | # 3 | # Copyright (c) 2020 Elie Michel 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software Foundation, 17 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | # 19 | # ##### END GPL LICENSE BLOCK ##### 20 | 21 | import bpy 22 | from bpy.types import Panel 23 | 24 | from . import operators as ops 25 | 26 | # for debug 27 | all_icons = ('NONE', 'QUESTION', 'ERROR', 'CANCEL', 'TRIA_RIGHT', 'TRIA_DOWN', 'TRIA_LEFT', 'TRIA_UP', 'ARROW_LEFTRIGHT', 'PLUS', 'DISCLOSURE_TRI_RIGHT', 'DISCLOSURE_TRI_DOWN', 'RADIOBUT_OFF', 'RADIOBUT_ON', 'MENU_PANEL', 'BLENDER', 'GRIP', 'DOT', 'COLLAPSEMENU', 'X', 'DUPLICATE', 'TRASH', 'COLLECTION_NEW', 'OPTIONS', 'NODE', 'NODE_SEL', 'WINDOW', 'WORKSPACE', 'RIGHTARROW_THIN', 'BORDERMOVE', 'VIEWZOOM', 'ADD', 'REMOVE', 'PANEL_CLOSE', 'COPY_ID', 'EYEDROPPER', 'CHECKMARK', 'AUTO', 'CHECKBOX_DEHLT', 'CHECKBOX_HLT', 'UNLOCKED', 'LOCKED', 'UNPINNED', 'PINNED', 'SCREEN_BACK', 'RIGHTARROW', 'DOWNARROW_HLT', 'FCURVE_SNAPSHOT', 'OBJECT_HIDDEN', 'TOPBAR', 'STATUSBAR', 'PLUGIN', 'HELP', 'GHOST_ENABLED', 'COLOR', 'UNLINKED', 'LINKED', 'HAND', 'ZOOM_ALL', 'ZOOM_SELECTED', 'ZOOM_PREVIOUS', 'ZOOM_IN', 'ZOOM_OUT', 'DRIVER_DISTANCE', 'DRIVER_ROTATIONAL_DIFFERENCE', 'DRIVER_TRANSFORM', 'FREEZE', 'STYLUS_PRESSURE', 'GHOST_DISABLED', 'FILE_NEW', 'FILE_TICK', 'QUIT', 'URL', 'RECOVER_LAST', 'THREE_DOTS', 'FULLSCREEN_ENTER', 'FULLSCREEN_EXIT', 'BRUSHES_ALL', 'LIGHT', 'MATERIAL', 'TEXTURE', 'ANIM', 'WORLD', 'SCENE', 'OUTPUT', 'SCRIPT', 'PARTICLES', 'PHYSICS', 'SPEAKER', 'TOOL_SETTINGS', 'SHADERFX', 'MODIFIER', 'BLANK1', 'FAKE_USER_OFF', 'FAKE_USER_ON', 'VIEW3D', 'GRAPH', 'OUTLINER', 'PROPERTIES', 'FILEBROWSER', 'IMAGE', 'INFO', 'SEQUENCE', 'TEXT', 'SOUND', 'ACTION', 'NLA', 'PREFERENCES', 'TIME', 'NODETREE', 'CONSOLE', 'TRACKER', 'ASSET_MANAGER', 'NODE_COMPOSITING', 'NODE_TEXTURE', 'NODE_MATERIAL', 'UV', 'OBJECT_DATAMODE', 'EDITMODE_HLT', 'UV_DATA', 'VPAINT_HLT', 'TPAINT_HLT', 'WPAINT_HLT', 'SCULPTMODE_HLT', 'POSE_HLT', 'PARTICLEMODE', 'TRACKING', 'TRACKING_BACKWARDS', 'TRACKING_FORWARDS', 'TRACKING_BACKWARDS_SINGLE', 'TRACKING_FORWARDS_SINGLE', 'TRACKING_CLEAR_BACKWARDS', 'TRACKING_CLEAR_FORWARDS', 'TRACKING_REFINE_BACKWARDS', 'TRACKING_REFINE_FORWARDS', 'SCENE_DATA', 'RENDERLAYERS', 'WORLD_DATA', 'OBJECT_DATA', 'MESH_DATA', 'CURVE_DATA', 'META_DATA', 'LATTICE_DATA', 'LIGHT_DATA', 'MATERIAL_DATA', 'TEXTURE_DATA', 'ANIM_DATA', 'CAMERA_DATA', 'PARTICLE_DATA', 'LIBRARY_DATA_DIRECT', 'GROUP', 'ARMATURE_DATA', 'COMMUNITY', 'BONE_DATA', 'CONSTRAINT', 'SHAPEKEY_DATA', 'CONSTRAINT_BONE', 'CAMERA_STEREO', 'PACKAGE', 'UGLYPACKAGE', 'EXPERIMENTAL', 'BRUSH_DATA', 'IMAGE_DATA', 'FILE', 'FCURVE', 'FONT_DATA', 'RENDER_RESULT', 'SURFACE_DATA', 'EMPTY_DATA', 'PRESET', 'RENDER_ANIMATION', 'RENDER_STILL', 'LIBRARY_DATA_BROKEN', 'BOIDS', 'STRANDS', 'LIBRARY_DATA_INDIRECT', 'GREASEPENCIL', 'LINE_DATA', 'LIBRARY_DATA_OVERRIDE', 'GROUP_BONE', 'GROUP_VERTEX', 'GROUP_VCOL', 'GROUP_UVS', 'FACE_MAPS', 'RNA', 'RNA_ADD', 'MOUSE_LMB', 'MOUSE_MMB', 'MOUSE_RMB', 'MOUSE_MOVE', 'MOUSE_LMB_DRAG', 'MOUSE_MMB_DRAG', 'MOUSE_RMB_DRAG', 'MEMORY', 'PRESET_NEW', 'DECORATE', 'DECORATE_KEYFRAME', 'DECORATE_ANIMATE', 'DECORATE_DRIVER', 'DECORATE_LINKED', 'DECORATE_LIBRARY_OVERRIDE', 'DECORATE_UNLOCKED', 'DECORATE_LOCKED', 'DECORATE_OVERRIDE', 'FUND', 'TRACKER_DATA', 'HEART', 'ORPHAN_DATA', 'USER', 'SYSTEM', 'SETTINGS', 'OUTLINER_OB_EMPTY', 'OUTLINER_OB_MESH', 'OUTLINER_OB_CURVE', 'OUTLINER_OB_LATTICE', 'OUTLINER_OB_META', 'OUTLINER_OB_LIGHT', 'OUTLINER_OB_CAMERA', 'OUTLINER_OB_ARMATURE', 'OUTLINER_OB_FONT', 'OUTLINER_OB_SURFACE', 'OUTLINER_OB_SPEAKER', 'OUTLINER_OB_FORCE_FIELD', 'OUTLINER_OB_GROUP_INSTANCE', 'OUTLINER_OB_GREASEPENCIL', 'OUTLINER_OB_LIGHTPROBE', 'OUTLINER_OB_IMAGE', 'RESTRICT_COLOR_OFF', 'RESTRICT_COLOR_ON', 'HIDE_ON', 'HIDE_OFF', 'RESTRICT_SELECT_ON', 'RESTRICT_SELECT_OFF', 'RESTRICT_RENDER_ON', 'RESTRICT_RENDER_OFF', 'RESTRICT_INSTANCED_OFF', 'OUTLINER_DATA_EMPTY', 'OUTLINER_DATA_MESH', 'OUTLINER_DATA_CURVE', 'OUTLINER_DATA_LATTICE', 'OUTLINER_DATA_META', 'OUTLINER_DATA_LIGHT', 'OUTLINER_DATA_CAMERA', 'OUTLINER_DATA_ARMATURE', 'OUTLINER_DATA_FONT', 'OUTLINER_DATA_SURFACE', 'OUTLINER_DATA_SPEAKER', 'OUTLINER_DATA_LIGHTPROBE', 'OUTLINER_DATA_GP_LAYER', 'OUTLINER_DATA_GREASEPENCIL', 'GP_SELECT_POINTS', 'GP_SELECT_STROKES', 'GP_MULTIFRAME_EDITING', 'GP_ONLY_SELECTED', 'GP_SELECT_BETWEEN_STROKES', 'MODIFIER_OFF', 'MODIFIER_ON', 'ONIONSKIN_OFF', 'ONIONSKIN_ON', 'RESTRICT_VIEW_ON', 'RESTRICT_VIEW_OFF', 'RESTRICT_INSTANCED_ON', 'MESH_PLANE', 'MESH_CUBE', 'MESH_CIRCLE', 'MESH_UVSPHERE', 'MESH_ICOSPHERE', 'MESH_GRID', 'MESH_MONKEY', 'MESH_CYLINDER', 'MESH_TORUS', 'MESH_CONE', 'MESH_CAPSULE', 'EMPTY_SINGLE_ARROW', 'LIGHT_POINT', 'LIGHT_SUN', 'LIGHT_SPOT', 'LIGHT_HEMI', 'LIGHT_AREA', 'CUBE', 'SPHERE', 'CONE', 'META_PLANE', 'META_CUBE', 'META_BALL', 'META_ELLIPSOID', 'META_CAPSULE', 'SURFACE_NCURVE', 'SURFACE_NCIRCLE', 'SURFACE_NSURFACE', 'SURFACE_NCYLINDER', 'SURFACE_NSPHERE', 'SURFACE_NTORUS', 'EMPTY_AXIS', 'STROKE', 'EMPTY_ARROWS', 'CURVE_BEZCURVE', 'CURVE_BEZCIRCLE', 'CURVE_NCURVE', 'CURVE_NCIRCLE', 'CURVE_PATH', 'LIGHTPROBE_CUBEMAP', 'LIGHTPROBE_PLANAR', 'LIGHTPROBE_GRID', 'COLOR_RED', 'COLOR_GREEN', 'COLOR_BLUE', 'TRIA_RIGHT_BAR', 'TRIA_DOWN_BAR', 'TRIA_LEFT_BAR', 'TRIA_UP_BAR', 'FORCE_FORCE', 'FORCE_WIND', 'FORCE_VORTEX', 'FORCE_MAGNETIC', 'FORCE_HARMONIC', 'FORCE_CHARGE', 'FORCE_LENNARDJONES', 'FORCE_TEXTURE', 'FORCE_CURVE', 'FORCE_BOID', 'FORCE_TURBULENCE', 'FORCE_DRAG', 'FORCE_FLUIDFLOW', 'RIGID_BODY', 'RIGID_BODY_CONSTRAINT', 'IMAGE_PLANE', 'IMAGE_BACKGROUND', 'IMAGE_REFERENCE', 'NODE_INSERT_ON', 'NODE_INSERT_OFF', 'NODE_TOP', 'NODE_SIDE', 'NODE_CORNER', 'ANCHOR_TOP', 'ANCHOR_BOTTOM', 'ANCHOR_LEFT', 'ANCHOR_RIGHT', 'ANCHOR_CENTER', 'SELECT_SET', 'SELECT_EXTEND', 'SELECT_SUBTRACT', 'SELECT_INTERSECT', 'SELECT_DIFFERENCE', 'ALIGN_LEFT', 'ALIGN_CENTER', 'ALIGN_RIGHT', 'ALIGN_JUSTIFY', 'ALIGN_FLUSH', 'ALIGN_TOP', 'ALIGN_MIDDLE', 'ALIGN_BOTTOM', 'BOLD', 'ITALIC', 'UNDERLINE', 'SMALL_CAPS', 'CON_ACTION', 'HOLDOUT_OFF', 'HOLDOUT_ON', 'INDIRECT_ONLY_OFF', 'INDIRECT_ONLY_ON', 'CON_CAMERASOLVER', 'CON_FOLLOWTRACK', 'CON_OBJECTSOLVER', 'CON_LOCLIKE', 'CON_ROTLIKE', 'CON_SIZELIKE', 'CON_TRANSLIKE', 'CON_DISTLIMIT', 'CON_LOCLIMIT', 'CON_ROTLIMIT', 'CON_SIZELIMIT', 'CON_SAMEVOL', 'CON_TRANSFORM', 'CON_TRANSFORM_CACHE', 'CON_CLAMPTO', 'CON_KINEMATIC', 'CON_LOCKTRACK', 'CON_SPLINEIK', 'CON_STRETCHTO', 'CON_TRACKTO', 'CON_ARMATURE', 'CON_CHILDOF', 'CON_FLOOR', 'CON_FOLLOWPATH', 'CON_PIVOT', 'CON_SHRINKWRAP', 'MODIFIER_DATA', 'MOD_WAVE', 'MOD_BUILD', 'MOD_DECIM', 'MOD_MIRROR', 'MOD_SOFT', 'MOD_SUBSURF', 'HOOK', 'MOD_PHYSICS', 'MOD_PARTICLES', 'MOD_BOOLEAN', 'MOD_EDGESPLIT', 'MOD_ARRAY', 'MOD_UVPROJECT', 'MOD_DISPLACE', 'MOD_CURVE', 'MOD_LATTICE', 'MOD_TINT', 'MOD_ARMATURE', 'MOD_SHRINKWRAP', 'MOD_CAST', 'MOD_MESHDEFORM', 'MOD_BEVEL', 'MOD_SMOOTH', 'MOD_SIMPLEDEFORM', 'MOD_MASK', 'MOD_CLOTH', 'MOD_EXPLODE', 'MOD_FLUIDSIM', 'MOD_MULTIRES', 'MOD_FLUID', 'MOD_SOLIDIFY', 'MOD_SCREW', 'MOD_VERTEX_WEIGHT', 'MOD_DYNAMICPAINT', 'MOD_REMESH', 'MOD_OCEAN', 'MOD_WARP', 'MOD_SKIN', 'MOD_TRIANGULATE', 'MOD_WIREFRAME', 'MOD_DATA_TRANSFER', 'MOD_NORMALEDIT', 'MOD_PARTICLE_INSTANCE', 'MOD_HUE_SATURATION', 'MOD_NOISE', 'MOD_OFFSET', 'MOD_SIMPLIFY', 'MOD_THICKNESS', 'MOD_INSTANCE', 'MOD_TIME', 'MOD_OPACITY', 'REC', 'PLAY', 'FF', 'REW', 'PAUSE', 'PREV_KEYFRAME', 'NEXT_KEYFRAME', 'PLAY_SOUND', 'PLAY_REVERSE', 'PREVIEW_RANGE', 'ACTION_TWEAK', 'PMARKER_ACT', 'PMARKER_SEL', 'PMARKER', 'MARKER_HLT', 'MARKER', 'KEYFRAME_HLT', 'KEYFRAME', 'KEYINGSET', 'KEY_DEHLT', 'KEY_HLT', 'MUTE_IPO_OFF', 'MUTE_IPO_ON', 'DRIVER', 'SOLO_OFF', 'SOLO_ON', 'FRAME_PREV', 'FRAME_NEXT', 'NLA_PUSHDOWN', 'IPO_CONSTANT', 'IPO_LINEAR', 'IPO_BEZIER', 'IPO_SINE', 'IPO_QUAD', 'IPO_CUBIC', 'IPO_QUART', 'IPO_QUINT', 'IPO_EXPO', 'IPO_CIRC', 'IPO_BOUNCE', 'IPO_ELASTIC', 'IPO_BACK', 'IPO_EASE_IN', 'IPO_EASE_OUT', 'IPO_EASE_IN_OUT', 'NORMALIZE_FCURVES', 'VERTEXSEL', 'EDGESEL', 'FACESEL', 'CURSOR', 'PIVOT_BOUNDBOX', 'PIVOT_CURSOR', 'PIVOT_INDIVIDUAL', 'PIVOT_MEDIAN', 'PIVOT_ACTIVE', 'CENTER_ONLY', 'ROOTCURVE', 'SMOOTHCURVE', 'SPHERECURVE', 'INVERSESQUARECURVE', 'SHARPCURVE', 'LINCURVE', 'NOCURVE', 'RNDCURVE', 'PROP_OFF', 'PROP_ON', 'PROP_CON', 'PROP_PROJECTED', 'PARTICLE_POINT', 'PARTICLE_TIP', 'PARTICLE_PATH', 'SNAP_FACE_CENTER', 'SNAP_PERPENDICULAR', 'SNAP_MIDPOINT', 'SNAP_OFF', 'SNAP_ON', 'SNAP_NORMAL', 'SNAP_GRID', 'SNAP_VERTEX', 'SNAP_EDGE', 'SNAP_FACE', 'SNAP_VOLUME', 'SNAP_INCREMENT', 'STICKY_UVS_LOC', 'STICKY_UVS_DISABLE', 'STICKY_UVS_VERT', 'CLIPUV_DEHLT', 'CLIPUV_HLT', 'SNAP_PEEL_OBJECT', 'GRID', 'OBJECT_ORIGIN', 'ORIENTATION_GLOBAL', 'ORIENTATION_GIMBAL', 'ORIENTATION_LOCAL', 'ORIENTATION_NORMAL', 'ORIENTATION_VIEW', 'COPYDOWN', 'PASTEDOWN', 'PASTEFLIPUP', 'PASTEFLIPDOWN', 'VIS_SEL_11', 'VIS_SEL_10', 'VIS_SEL_01', 'VIS_SEL_00', 'AUTOMERGE_OFF', 'AUTOMERGE_ON', 'UV_VERTEXSEL', 'UV_EDGESEL', 'UV_FACESEL', 'UV_ISLANDSEL', 'UV_SYNC_SELECT', 'TRANSFORM_ORIGINS', 'GIZMO', 'ORIENTATION_CURSOR', 'NORMALS_VERTEX', 'NORMALS_FACE', 'NORMALS_VERTEX_FACE', 'SHADING_BBOX', 'SHADING_WIRE', 'SHADING_SOLID', 'SHADING_RENDERED', 'SHADING_TEXTURE', 'OVERLAY', 'XRAY', 'LOCKVIEW_OFF', 'LOCKVIEW_ON', 'AXIS_SIDE', 'AXIS_FRONT', 'AXIS_TOP', 'LAYER_USED', 'LAYER_ACTIVE', 'OUTLINER_OB_HAIR', 'OUTLINER_DATA_HAIR', 'HAIR_DATA', 'OUTLINER_OB_POINTCLOUD', 'OUTLINER_DATA_POINTCLOUD', 'POINTCLOUD_DATA', 'OUTLINER_OB_VOLUME', 'OUTLINER_DATA_VOLUME', 'VOLUME_DATA', 'HOME', 'DOCUMENTS', 'TEMP', 'SORTALPHA', 'SORTBYEXT', 'SORTTIME', 'SORTSIZE', 'SHORTDISPLAY', 'LONGDISPLAY', 'IMGDISPLAY', 'BOOKMARKS', 'FONTPREVIEW', 'FILTER', 'NEWFOLDER', 'FOLDER_REDIRECT', 'FILE_PARENT', 'FILE_REFRESH', 'FILE_FOLDER', 'FILE_BLANK', 'FILE_BLEND', 'FILE_IMAGE', 'FILE_MOVIE', 'FILE_SCRIPT', 'FILE_SOUND', 'FILE_FONT', 'FILE_TEXT', 'SORT_DESC', 'SORT_ASC', 'LINK_BLEND', 'APPEND_BLEND', 'IMPORT', 'EXPORT', 'LOOP_BACK', 'LOOP_FORWARDS', 'BACK', 'FORWARD', 'FILE_ARCHIVE', 'FILE_CACHE', 'FILE_VOLUME', 'FILE_3D', 'FILE_HIDDEN', 'FILE_BACKUP', 'DISK_DRIVE', 'MATPLANE', 'MATSPHERE', 'MATCUBE', 'MONKEY', 'HAIR', 'ALIASED', 'ANTIALIASED', 'MAT_SPHERE_SKY', 'MATSHADERBALL', 'MATCLOTH', 'MATFLUID', 'WORDWRAP_OFF', 'WORDWRAP_ON', 'SYNTAX_OFF', 'SYNTAX_ON', 'LINENUMBERS_OFF', 'LINENUMBERS_ON', 'SCRIPTPLUGINS', 'DISC', 'DESKTOP', 'EXTERNAL_DRIVE', 'NETWORK_DRIVE', 'SEQ_SEQUENCER', 'SEQ_PREVIEW', 'SEQ_LUMA_WAVEFORM', 'SEQ_CHROMA_SCOPE', 'SEQ_HISTOGRAM', 'SEQ_SPLITVIEW', 'SEQ_STRIP_META', 'SEQ_STRIP_DUPLICATE', 'IMAGE_RGB', 'IMAGE_RGB_ALPHA', 'IMAGE_ALPHA', 'IMAGE_ZDEPTH', 'HANDLE_AUTOCLAMPED', 'HANDLE_AUTO', 'HANDLE_ALIGNED', 'HANDLE_VECTOR', 'HANDLE_FREE', 'VIEW_PERSPECTIVE', 'VIEW_ORTHO', 'VIEW_CAMERA', 'VIEW_PAN', 'VIEW_ZOOM', 'BRUSH_BLOB', 'BRUSH_BLUR', 'BRUSH_CLAY', 'BRUSH_CLAY_STRIPS', 'BRUSH_CLONE', 'BRUSH_CREASE', 'BRUSH_FILL', 'BRUSH_FLATTEN', 'BRUSH_GRAB', 'BRUSH_INFLATE', 'BRUSH_LAYER', 'BRUSH_MASK', 'BRUSH_MIX', 'BRUSH_NUDGE', 'BRUSH_PINCH', 'BRUSH_SCRAPE', 'BRUSH_SCULPT_DRAW', 'BRUSH_SMEAR', 'BRUSH_SMOOTH', 'BRUSH_SNAKE_HOOK', 'BRUSH_SOFTEN', 'BRUSH_TEXDRAW', 'BRUSH_TEXFILL', 'BRUSH_TEXMASK', 'BRUSH_THUMB', 'BRUSH_ROTATE', 'GPBRUSH_SMOOTH', 'GPBRUSH_THICKNESS', 'GPBRUSH_STRENGTH', 'GPBRUSH_GRAB', 'GPBRUSH_PUSH', 'GPBRUSH_TWIST', 'GPBRUSH_PINCH', 'GPBRUSH_RANDOMIZE', 'GPBRUSH_CLONE', 'GPBRUSH_WEIGHT', 'GPBRUSH_PENCIL', 'GPBRUSH_PEN', 'GPBRUSH_INK', 'GPBRUSH_INKNOISE', 'GPBRUSH_BLOCK', 'GPBRUSH_MARKER', 'GPBRUSH_FILL', 'GPBRUSH_AIRBRUSH', 'GPBRUSH_CHISEL', 'GPBRUSH_ERASE_SOFT', 'GPBRUSH_ERASE_HARD', 'GPBRUSH_ERASE_STROKE', 'SMALL_TRI_RIGHT_VEC', 'KEYTYPE_KEYFRAME_VEC', 'KEYTYPE_BREAKDOWN_VEC', 'KEYTYPE_EXTREME_VEC', 'KEYTYPE_JITTER_VEC', 'KEYTYPE_MOVING_HOLD_VEC', 'HANDLETYPE_FREE_VEC', 'HANDLETYPE_ALIGNED_VEC', 'HANDLETYPE_VECTOR_VEC', 'HANDLETYPE_AUTO_VEC', 'HANDLETYPE_AUTO_CLAMP_VEC', 'COLORSET_01_VEC', 'COLORSET_02_VEC', 'COLORSET_03_VEC', 'COLORSET_04_VEC', 'COLORSET_05_VEC', 'COLORSET_06_VEC', 'COLORSET_07_VEC', 'COLORSET_08_VEC', 'COLORSET_09_VEC', 'COLORSET_10_VEC', 'COLORSET_11_VEC', 'COLORSET_12_VEC', 'COLORSET_13_VEC', 'COLORSET_14_VEC', 'COLORSET_15_VEC', 'COLORSET_16_VEC', 'COLORSET_17_VEC', 'COLORSET_18_VEC', 'COLORSET_19_VEC', 'COLORSET_20_VEC', 'EVENT_A', 'EVENT_B', 'EVENT_C', 'EVENT_D', 'EVENT_E', 'EVENT_F', 'EVENT_G', 'EVENT_H', 'EVENT_I', 'EVENT_J', 'EVENT_K', 'EVENT_L', 'EVENT_M', 'EVENT_N', 'EVENT_O', 'EVENT_P', 'EVENT_Q', 'EVENT_R', 'EVENT_S', 'EVENT_T', 'EVENT_U', 'EVENT_V', 'EVENT_W', 'EVENT_X', 'EVENT_Y', 'EVENT_Z', 'EVENT_SHIFT', 'EVENT_CTRL', 'EVENT_ALT', 'EVENT_OS', 'EVENT_F1', 'EVENT_F2', 'EVENT_F3', 'EVENT_F4', 'EVENT_F5', 'EVENT_F6', 'EVENT_F7', 'EVENT_F8', 'EVENT_F9', 'EVENT_F10', 'EVENT_F11', 'EVENT_F12', 'EVENT_ESC', 'EVENT_TAB', 'EVENT_PAGEUP', 'EVENT_PAGEDOWN', 'EVENT_RETURN', 'EVENT_SPACEKEY') 28 | 29 | # ------------------------------------------------------------------- 30 | 31 | class AdvancedBlenderAddonPanel(Panel): 32 | bl_label = "Advanced Blender Addon" 33 | bl_idname = "SCENE_PT_AdvancedBlenderAddonPanel" 34 | bl_space_type = 'PROPERTIES' 35 | bl_region_type = 'WINDOW' 36 | bl_context = "scene" 37 | 38 | def draw(self, context): 39 | # The draw() method is called by Blender's UI system when 40 | # drawing the panel. It must use self.layout to add items. 41 | # The draw() function can be long, so feel free to 42 | # break it up in several methods. 43 | scene = context.scene 44 | layout = self.layout 45 | 46 | self.draw_ops(layout) 47 | 48 | self.draw_profiling(scene, layout) 49 | 50 | layout.prop(scene, "show_debug_icons") 51 | if scene.show_debug_icons: 52 | layout.separator() 53 | self.draw_icon_chart(layout) 54 | 55 | def draw_ops(self, layout): 56 | """Show profiling counters (average, stddev)""" 57 | layout.label(text="Operators:") 58 | 59 | # Use operator's bl_idname rather than explicitely writing 60 | # sth like "advanced_blender_addon.reset_profiling" 61 | layout.operator(ops.ResetProfiling.bl_idname) 62 | 63 | layout.operator(ops.ComputeCentroid.bl_idname) 64 | 65 | def draw_profiling(self, scene, layout): 66 | """Show profiling counters (average, stddev)""" 67 | layout.label(text="Profiling:") 68 | col = layout.column(align=True) 69 | 70 | prof = scene.compute_centroid_profiling 71 | col.label(text=f" - compute centroid: {prof.average()*1000.:.03}ms (±{prof.stddev()*1000.:.03}ms, {prof.sample_count} samples)") 72 | 73 | def draw_icon_chart(self, layout): 74 | # One can add icons to many ui elements, but it can be hard to 75 | # guess what they look like from their name, so here is a display 76 | # of all the available icons to pick up the right one from! 77 | for ic in all_icons: 78 | layout.label(icon=ic, text=ic) 79 | 80 | # ------------------------------------------------------------------- 81 | 82 | classes = ( 83 | AdvancedBlenderAddonPanel, 84 | ) 85 | register, unregister = bpy.utils.register_classes_factory(classes) 86 | -------------------------------------------------------------------------------- /preferences.py: -------------------------------------------------------------------------------- 1 | # ##### BEGIN GPL LICENSE BLOCK ##### 2 | # 3 | # Copyright (c) 2020 Elie Michel 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software Foundation, 17 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | # 19 | # ##### END GPL LICENSE BLOCK ##### 20 | 21 | import bpy 22 | from bpy.types import AddonPreferences 23 | from bpy.props import FloatProperty 24 | 25 | addon_idname = __package__.split(".")[0] 26 | 27 | # ------------------------------------------------------------------- 28 | 29 | def getPreferences(context=None): 30 | """Get the preferences of this add-on (guessed from the __package__ 31 | name, which should always be fine).""" 32 | if context is None: 33 | context = bpy.context 34 | return context.preferences.addons[addon_idname].preferences 35 | 36 | # ------------------------------------------------------------------- 37 | # See more examples at 38 | # https://docs.blender.org/api/current/bpy.types.AddonPreferences.html 39 | 40 | class AdvancedBlenderAddonPreferences(AddonPreferences): 41 | bl_idname = addon_idname 42 | 43 | some_preference: FloatProperty( 44 | name="Some Preference", 45 | description="This is a global setting, saved in user preferences rather than in .blend files.", 46 | default=3.14 47 | ) 48 | 49 | def draw(self, context): 50 | layout = self.layout 51 | layout.label(text="Add here some user settings.") 52 | layout.prop(self, "some_preference") 53 | 54 | # ------------------------------------------------------------------- 55 | 56 | classes = (AdvancedBlenderAddonPreferences,) 57 | register, unregister = bpy.utils.register_classes_factory(classes) 58 | -------------------------------------------------------------------------------- /profiling.py: -------------------------------------------------------------------------------- 1 | # If this module does not use bpy nor any blender related module, it may 2 | # be licensed with something else than the GPL: 3 | 4 | # ##### BEGIN MIT LICENSE BLOCK ##### 5 | # 6 | # Copyright (c) 2020 Elie Michel 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the “Software”), to 9 | # deal in the Software without restriction, including without limitation the 10 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | # sell copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # The Software is provided “as is”, without warranty of any kind, express or 17 | # implied, including but not limited to the warranties of merchantability, 18 | # fitness for a particular purpose and noninfringement. In no event shall the 19 | # authors or copyright holders be liable for any claim, damages or other 20 | # liability, whether in an action of contract, tort or otherwise, arising 21 | # from, out of or in connection with the software or the use or other dealings 22 | # in the Software. 23 | # 24 | # ##### BEGIN MIT LICENSE BLOCK ##### 25 | 26 | import time 27 | 28 | # ------------------------------------------------------------------- 29 | 30 | class Timer(): 31 | def __init__(self): 32 | self.start = time.perf_counter() 33 | 34 | def ellapsed(self): 35 | return time.perf_counter() - self.start 36 | 37 | # ------------------------------------------------------------------- 38 | -------------------------------------------------------------------------------- /properties.py: -------------------------------------------------------------------------------- 1 | # ##### BEGIN GPL LICENSE BLOCK ##### 2 | # 3 | # Copyright (c) 2020 Elie Michel 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software Foundation, 17 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | # 19 | # ##### END GPL LICENSE BLOCK ##### 20 | 21 | import bpy 22 | from bpy.types import ( 23 | Scene, PropertyGroup 24 | ) 25 | from bpy.props import ( 26 | FloatProperty, IntProperty, PointerProperty, BoolProperty, 27 | FloatVectorProperty, 28 | ) 29 | 30 | from math import sqrt 31 | 32 | # ------------------------------------------------------------------- 33 | # A property group can have custom methods attached to it for a more 34 | # convenient access. Members 'sample_count', 'accumulated' and 'accumulated_sq' 35 | # are what is stored in .blend files but average() is used for display in panels 36 | # and add_sample() in operators. A property group can hence contain logic. 37 | 38 | class ProfilingCounterProperty(PropertyGroup): 39 | """A profiling counters can accumulate profiling timings with add_sample 40 | and then provide their average and standard deviation (stddev()). 41 | This is an example of advanced PropertyGroup.""" 42 | 43 | sample_count: IntProperty( 44 | name="Sample Count", 45 | description="Number of sampled values accumulated in the total count", 46 | default=0, 47 | min=0, 48 | ) 49 | 50 | accumulated: FloatProperty( 51 | name="Accumulated Values", 52 | description="Sum of all samples", 53 | default=0.0, 54 | ) 55 | 56 | accumulated_sq: FloatProperty( 57 | name="Accumulated Squared Values", 58 | description="Sum of the square value of all samples (to compute standard deviation)", 59 | default=0.0, 60 | ) 61 | 62 | def average(self): 63 | if self.sample_count == 0: 64 | return 0 65 | else: 66 | return self.accumulated / self.sample_count 67 | 68 | def stddev(self): 69 | if self.sample_count == 0: 70 | return 0 71 | else: 72 | avg = self.average() 73 | var = self.accumulated_sq / self.sample_count - avg * avg 74 | return sqrt(max(0, var)) 75 | 76 | def add_sample(self, value): 77 | self.sample_count += 1 78 | self.accumulated += value 79 | self.accumulated_sq += value * value 80 | 81 | def reset(self): 82 | self.sample_count = 0 83 | self.accumulated = 0.0 84 | self.accumulated_sq = 0.0 85 | 86 | # ------------------------------------------------------------------- 87 | 88 | classes = ( 89 | ProfilingCounterProperty, 90 | ) 91 | register_cls, unregister_cls = bpy.utils.register_classes_factory(classes) 92 | 93 | 94 | def register(): 95 | register_cls() 96 | 97 | # Add a profiling property to all scenes 98 | Scene.compute_centroid_profiling = PointerProperty(type=ProfilingCounterProperty) 99 | 100 | Scene.show_debug_icons = BoolProperty( 101 | name="Show Debug Icons", 102 | description="Draw in the scene properties panel the list of all available icons", 103 | default=False, 104 | ) 105 | 106 | Scene.demo_tool_cursor = FloatVectorProperty( 107 | name="DemoTool's Cusor Position", 108 | description="Used internally by the Demo Tool operators to communicate with the overlay", 109 | options={'HIDDEN', 'SKIP_SAVE'}, 110 | ) 111 | 112 | Scene.demo_tool_radius = FloatProperty( 113 | name="DemoTool's Radius", 114 | description="Used internally by the Demo Tool operators to communicate with the overlay", 115 | default=10, 116 | options={'HIDDEN', 'SKIP_SAVE'}, 117 | ) 118 | 119 | 120 | def unregister(): 121 | unregister_cls() 122 | 123 | del Scene.compute_centroid_profiling 124 | del Scene.show_debug_icons 125 | del Scene.demo_tool_cursor 126 | del Scene.demo_tool_radius 127 | -------------------------------------------------------------------------------- /tools.py: -------------------------------------------------------------------------------- 1 | # ##### BEGIN GPL LICENSE BLOCK ##### 2 | # 3 | # Copyright (c) 2020 Elie Michel 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software Foundation, 17 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | # 19 | # ##### END GPL LICENSE BLOCK ##### 20 | 21 | import bpy 22 | from bpy.types import WorkSpaceTool 23 | 24 | from . import operators as ops 25 | from . import overlays 26 | 27 | # Tools are UI states that can be activated through the T panel 28 | # in the 3D view. They don't hold logic, they just set-up connections 29 | # between events/keys and operators which are enabled when the tool 30 | # gets activated. 31 | # They can draw a setting UI on the top bar of the 3d view, and enable 32 | # some overlay widget. 33 | 34 | # ------------------------------------------------------------------- 35 | 36 | class DemoTool(WorkSpaceTool): 37 | bl_space_type = 'VIEW_3D' 38 | bl_context_mode = 'OBJECT' 39 | 40 | bl_idname = "advanced_blender_addon.demo_tool" 41 | bl_label = "Demo Tool" 42 | bl_description = "Just a mock demo tool to show of it works" 43 | 44 | # The icon displayed in the T panel 45 | bl_icon = "ops.generic.select_circle" 46 | 47 | # This overlay will be activated only when the tool is turned on 48 | # while otherwise overlays stays activated all the time. 49 | bl_widget = overlays.DemoToolOverlay.bl_idname 50 | 51 | # The keymap is the core of the tool, it tells when to call which 52 | # operator. 53 | bl_keymap = ( 54 | (ops.DemoTool.bl_idname, 55 | {"type": 'LEFTMOUSE', "value": 'PRESS'}, 56 | {"properties": []}), 57 | 58 | (ops.UpdateDemoOverlay.bl_idname, 59 | {"type": 'MOUSEMOVE', "value": 'ANY'}, 60 | {"properties": []}), 61 | ) 62 | 63 | def draw_settings(context, layout, tool): 64 | # Settings are what is displayed in the top bar. They don't belong 65 | # to the tool itself but rather to the operators it calls. 66 | props = tool.operator_properties(ops.UpdateDemoOverlay.bl_idname) 67 | layout.prop(props, "radius") 68 | 69 | # ------------------------------------------------------------------- 70 | 71 | def register(): 72 | bpy.utils.register_tool(DemoTool) 73 | 74 | def unregister(): 75 | bpy.utils.unregister_tool(DemoTool) 76 | 77 | --------------------------------------------------------------------------------