# ##### BEGIN GPL LICENSE BLOCK ##### # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ##### END GPL LICENSE BLOCK ##### # bl_info = { 'name': 'Display Keys Status for Screencasting', 'author': 'Paulo Gomes, Bartius Crouch, John E. Herrenyo', 'version': (1, 0), 'blender': (2, 5, 6), 'api': 35457, 'location': 'View3D > Properties panel > Display tab', 'warning': '', 'description': 'Display keys pressed in the 3d-view, '\ 'useful for screencasts.', 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/'\ 'Py/Scripts/3D_interaction/Screencast_Key_Status_Tool', 'tracker_url': 'http://projects.blender.org/tracker/index.php?'\ 'func=detail&aid=21612', 'category': '3D View'} import bgl import blf import bpy import math import time def getIconShape(iconName): data = [] if iconName == "mouse": data = [[[0.264, 0.002, 0.0], [0.096, 0.002, 0.0], [0.059, 0.226, 0.0], [0.04, 0.313, 0.0]], [[0.04, 0.313, 0.0], [-0.015, 0.565, 0.0], [-0.005, 0.664, 0.0], [0.032, 0.87, 0.0]], [[0.032, 0.87, 0.0], [0.05, 0.973, 0.0], [0.16, 1.002, 0.0], [0.264, 1.002, 0.0]], [[0.264, 1.002, 0.0], [0.369, 1.002, 0.0], [0.478, 0.973, 0.0], [0.497, 0.87, 0.0]], [[0.497, 0.87, 0.0], [0.533, 0.664, 0.0], [0.544, 0.565, 0.0], [0.489, 0.313, 0.0]], [[0.489, 0.313, 0.0], [0.47, 0.226, 0.0], [0.432, 0.002, 0.0], [0.264, 0.002, 0.0]]] elif iconName == "left_button": data = [[[0.154, 0.763, 0.0], [0.126, 0.755, 0.0], [0.12, 0.754, 0.0], [0.066, 0.751, 0.0]], [[0.066, 0.751, 0.0], [0.043, 0.75, 0.0], [0.039, 0.757, 0.0], [0.039, 0.767, 0.0]], [[0.039, 0.767, 0.0], [0.047, 0.908, 0.0], [0.078, 0.943, 0.0], [0.155, 0.97, 0.0]], [[0.155, 0.97, 0.0], [0.174, 0.977, 0.0], [0.187, 0.975, 0.0], [0.191, 0.972, 0.0]], [[0.191, 0.972, 0.0], [0.203, 0.958, 0.0], [0.205, 0.949, 0.0], [0.199, 0.852, 0.0]], [[0.199, 0.852, 0.0], [0.195, 0.77, 0.0], [0.18, 0.771, 0.0], [0.154, 0.763, 0.0]]] elif iconName == "right_button": data = [[[0.375, 0.763, 0.0], [0.402, 0.755, 0.0], [0.408, 0.754, 0.0], [0.462, 0.751, 0.0]], [[0.462, 0.751, 0.0], [0.486, 0.75, 0.0], [0.49, 0.757, 0.0], [0.489, 0.767, 0.0]], [[0.489, 0.767, 0.0], [0.481, 0.908, 0.0], [0.451, 0.943, 0.0], [0.374, 0.97, 0.0]], [[0.374, 0.97, 0.0], [0.354, 0.977, 0.0], [0.341, 0.975, 0.0], [0.338, 0.972, 0.0]], [[0.338, 0.972, 0.0], [0.325, 0.958, 0.0], [0.324, 0.949, 0.0], [0.329, 0.852, 0.0]], [[0.329, 0.852, 0.0], [0.334, 0.77, 0.0], [0.348, 0.771, 0.0], [0.375, 0.763, 0.0]]] elif iconName == "middle_button": data = [[[0.301, 0.8, 0.0], [0.298, 0.768, 0.0], [0.231, 0.768, 0.0], [0.228, 0.8, 0.0]], [[0.228, 0.8, 0.0], [0.226, 0.817, 0.0], [0.225, 0.833, 0.0], [0.224, 0.85, 0.0]], [[0.224, 0.85, 0.0], [0.222, 0.873, 0.0], [0.222, 0.877, 0.0], [0.224, 0.9, 0.0]], [[0.224, 0.9, 0.0], [0.225, 0.917, 0.0], [0.226, 0.933, 0.0], [0.228, 0.95, 0.0]], [[0.228, 0.95, 0.0], [0.231, 0.982, 0.0], [0.298, 0.982, 0.0], [0.301, 0.95, 0.0]], [[0.301, 0.95, 0.0], [0.302, 0.933, 0.0], [0.303, 0.917, 0.0], [0.305, 0.9, 0.0]], [[0.305, 0.9, 0.0], [0.307, 0.877, 0.0], [0.307, 0.873, 0.0], [0.305, 0.85, 0.0]], [[0.305, 0.85, 0.0], [0.303, 0.833, 0.0], [0.302, 0.817, 0.0], [0.301, 0.8, 0.0]]] return data def drawIconShape(iconShapeData, offsetX, offsetY, iconSize): innerShape = [] for i in iconShapeData: innerShape.append(i[0]) for i in iconShapeData: shapeSegment = i shapeSegment[0] = [iconSize * k for k in shapeSegment[0]] shapeSegment[1] = [iconSize * k for k in shapeSegment[1]] shapeSegment[2] = [iconSize * k for k in shapeSegment[2]] shapeSegment[3] = [iconSize * k for k in shapeSegment[3]] # Create the Buffer shapeBuf = bgl.Buffer(bgl.GL_FLOAT, [4, 3], shapeSegment) # Create the map and draw the triangle fan bgl.glMap1f(bgl.GL_MAP1_VERTEX_3, 0.0, 1.0, 3, 4, shapeBuf) bgl.glEnable(bgl.GL_MAP1_VERTEX_3) bgl.glTranslatef(offsetX, offsetY, 0) bgl.glBegin(bgl.GL_TRIANGLE_FAN) for j in range(30): bgl.glEvalCoord1f(float(j) / 30.0) x, y, z = shapeSegment[3] # Make sure the last vertex is the one that must be, to avoid gaps bgl.glVertex3f(x, y, z) bgl.glEnd() bgl.glDisable(bgl.GL_MAP1_VERTEX_3) bgl.glTranslatef(-offsetX, -offsetY, 0) # Now, fill in the interior bgl.glTranslatef(offsetX, offsetY, 0) bgl.glBegin(bgl.GL_TRIANGLE_FAN) for i in innerShape: j = [iconSize * k for k in i] x, y, z = j bgl.glVertex3f(x, y, z) bgl.glEnd() bgl.glTranslatef(-offsetX, -offsetY, 0) # Test code def drawCircle(xc, yc, radius, smoothness): ''' Draw a circle. Parameters - xc - x co-ordinate yc - y co-ordinate radius - radius of the circle smoothness - number of triangles to use''' bgl.glBegin(bgl.GL_TRIANGLE_FAN) bgl.glColor3f(1.0, 0.0, 0.0) for i in range(0, smoothness): angle = i * math.pi * 2.0 / smoothness bgl.glVertex2f(xc + radius * math.cos(angle), yc + radius * math.sin(angle)) bgl.glEnd() #drawMouse() # Test code def drawBezier(): cps = [[-40.0, -40.0, 0.0], [-20.0, 40.0, 0.0], [20.0, -40.0, 0.0], [40.0, 40.0, 0.0]] cpsBuf = bgl.Buffer(bgl.GL_FLOAT, [4, 3], cps) #print(cpsBuf) bgl.glMap1f(bgl.GL_MAP1_VERTEX_3, 0.0, 1.0, 3, 4, cpsBuf) bgl.glEnable(bgl.GL_MAP1_VERTEX_3) bgl.glTranslatef(50, 50, 0) bgl.glBegin(bgl.GL_LINE_STRIP) for i in range(30): bgl.glEvalCoord1f(float(i) / 30.0) bgl.glEnd() bgl.glTranslatef(-50, -50, 0) def drawMouse(context, state): # Load the shapes needed mouseShape = getIconShape("mouse") leftButtonShape = getIconShape("left_button") rightButtonShape = getIconShape("right_button") middleButtonShape = getIconShape("middle_button") wm = context.window_manager color = wm.display_color r, g, b = color # Basic constants X_OFFSET = wm.display_x_offset Y_OFFSET = wm.display_y_offset ICON_SIZE = 200 bgl.glEnable(bgl.GL_BLEND) bgl.glBlendFunc(bgl.GL_SRC_ALPHA, bgl.GL_ONE_MINUS_SRC_ALPHA) bgl.glColor4f(r, g, b, 0.2) drawIconShape(mouseShape, X_OFFSET, Y_OFFSET, ICON_SIZE) bgl.glColor4f(0.706, 0.239, 0.227, 1.0 if state == "LEFTMOUSE" else 0.2) drawIconShape(leftButtonShape, X_OFFSET, Y_OFFSET, ICON_SIZE) bgl.glColor4f(0.239, 0.435, 0.702, 1.0 if state == "RIGHTMOUSE" else 0.2) drawIconShape(rightButtonShape, X_OFFSET, Y_OFFSET, ICON_SIZE) bgl.glColor4f(0.333, 0.467, 0.204, 1.0 if state == "MIDDLEMOUSE" else 0.2) drawIconShape(middleButtonShape, X_OFFSET, Y_OFFSET, ICON_SIZE) return def draw_callback_px(self, context): wm = context.window_manager if wm.display_keys: # draw text in the 3d-view blf.size(0, wm.display_font_size, 72) r, g, b = wm.display_color bgl.glColor3f(r, g, b) final = 0 # only display key-presses of last 2 seconds for i in range(len(self.key)): label_age = time.time()-self.time[i] if label_age < 2: blf.position(0, wm.display_pos_x, wm.display_pos_y + wm.display_font_size*i, 0) if label_age > 1.5: label_alpha = (2.0-label_age)*2 bgl.glColor4f(r, g, b, label_alpha) blf.draw(0, self.key[i]) final = i else: break '''drawCircle(50, 50, 20, 5) drawCircle(70, 50, 20, 5) drawCircle(90, 50, 20, 5)''' mouseState = "" if self.mouse == "LEFTMOUSE" or self.mouse == "RIGHTMOUSE" or self.mouse == "MIDDLEMOUSE": mouseState = self.mouse if wm.display_mouse: drawMouse(context, mouseState) # get rid of statuses that aren't displayed anymore self.key = self.key[:final+1] self.time = self.time[:final+1] else: return class ScreencastKeysStatus(bpy.types.Operator): bl_idname = "view3d.screencast_keys" bl_label = "Screencast Key Status Tool" bl_description = "Display keys pressed in the 3D-view" def modal(self, context, event): if context.area: context.area.tag_redraw() # keys that shouldn't show up in the 3d-view mouse_keys = ['MOUSEMOVE','MIDDLEMOUSE','LEFTMOUSE', 'RIGHTMOUSE', 'WHEELDOWNMOUSE','WHEELUPMOUSE'] ignore_keys = ['LEFT_SHIFT', 'RIGHT_SHIFT', 'LEFT_ALT', 'RIGHT_ALT', 'LEFT_CTRL', 'RIGHT_CTRL', 'TIMER'] if not context.window_manager.display_mouse: ignore_keys.extend(mouse_keys) if event.value == 'PRESS': # add key-press to display-list sc_keys = [] if event.ctrl: sc_keys.append("Ctrl ") if event.alt: sc_keys.append("Alt ") if event.shift: sc_keys.append("Shift ") if event.type not in ignore_keys: sc_keys.append(event.type) self.key.insert(0, "+ ".join(map(str, sc_keys))) self.time.insert(0, time.time()) self.mouse = "" if event.type in mouse_keys: self.mouse = event.type if not context.window_manager.display_keys: # stop script context.region.callback_remove(self._handle) return {'CANCELLED'} return {'PASS_THROUGH'} def invoke(self, context, event): if context.area.type == 'VIEW_3D': if context.window_manager.display_keys == False: # operator is called for the first time, start everything context.window_manager.display_keys = True context.window_manager.modal_handler_add(self) self._handle = context.region.callback_add(draw_callback_px, (self, context), 'POST_PIXEL') self.key = [] self.time = [] self.mouse = "" return {'RUNNING_MODAL'} else: # operator is called again, stop displaying context.window_manager.display_keys = False self.key = [] self.time = [] self.mouse = "" return {'CANCELLED'} else: self.report({'WARNING'}, "View3D not found, can't run operator") return {'CANCELLED'} # properties used by the script def init_properties(): bpy.types.WindowManager.display_keys = bpy.props.BoolProperty( default=False) bpy.types.WindowManager.display_mouse = bpy.props.BoolProperty( name="Mouse", description="Display mouse events", default=False) bpy.types.WindowManager.display_font_size = bpy.props.IntProperty( name="Size", description="Fontsize", default=20) bpy.types.WindowManager.display_pos_x = bpy.props.IntProperty( name="Pos X", description="Position of the font in x axis", default=15) bpy.types.WindowManager.display_pos_y = bpy.props.IntProperty( name="Pos Y", description="Position of the font in y axis", default=60) bpy.types.WindowManager.display_color = bpy.props.FloatVectorProperty( name="Colour", description="Font colour", default=(0.054, 0.068, 0.071), min=0, max=1, subtype='COLOR') bpy.types.WindowManager.display_x_offset = bpy.props.IntProperty( name="X Offset", description="Offset in the X Axis", default=300) bpy.types.WindowManager.display_y_offset = bpy.props.IntProperty( name="Y Offset", description="Offset in the Y Axis", default=60) # removal of properties when script is disabled def clear_properties(): props = ["display_keys", "display_mouse", "display_font_size", "display_pos_x", "display_pos_y"] for p in props: if bpy.context.window_manager.get(p) != None: del bpy.context.window_manager[p] try: x = getattr(bpy.types.WindowManager, p) del x except: pass # defining the panel class OBJECT_PT_keys_status(bpy.types.Panel): bl_label = "Display Keys Status" bl_space_type = "VIEW_3D" bl_region_type = "UI" def draw(self, context): col = self.layout.column(align=False) if not context.window_manager.display_keys: col.operator("view3d.screencast_keys", text="Start display", icon='PLAY') else: col.operator("view3d.screencast_keys", text="Stop display", icon='PAUSE') col = self.layout.column(align=True) row = col.row(align=True) row.prop(context.window_manager, "display_pos_x") row.prop(context.window_manager, "display_pos_y") row = col.row(align=True) row.prop(context.window_manager, "display_font_size") row.prop(context.window_manager, "display_mouse") row = self.layout.row() row.prop(context.window_manager, "display_color") row = col.row(align=True) row.prop(context.window_manager, "display_x_offset") row.prop(context.window_manager, "display_y_offset") classes = [ScreencastKeysStatus, OBJECT_PT_keys_status] def register(): init_properties() for c in classes: bpy.utils.register_class(c) def unregister(): for c in classes: bpy.utils.unregister_class(c) clear_properties() if __name__ == "__main__": register()