[Blender Python] Set an event timer to refresh the screen 60 times per second

Blender basically updates the screen according to the input, but there are times when you want the screen to be updated automatically without inputting anything. I think there is. In such a case, use the event timer.

It's hard to understand just by updating the screen, so I moved the image randomly.

sample

gl_benchmark.py


# ##### 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 #####

# Blender2.77a

import time
import random

import bpy
import bgl
import blf


image_file_path = "C:/Works/blender_rogo.png "

class GL_Texture():
    def __init__(self, file_path):
        self.image = None
        self.width = 0
        self.height = 0
        
        self.load_image(file_path)
    
    def load_image(self, file_path):
        try:
            self.image = bpy.data.images.load(file_path)
        except Exception as e:
            print(e)
        
        if self.image:
            self.width, self.height = self.image.size
            self.image.gl_load(0, bgl.GL_NEAREST, bgl.GL_NEAREST)
    
    def remove(self):
        if self.image:
            try:
                self.image.user_clear()
                self.image.gl_free()
                self.image.buffers_free()
                bpy.data.images.remove(self.image)
            except Exception as e:
                print(e)
    
    def bind(self):
        if self.image.bindcode[0]:
            bgl.glBindTexture(bgl.GL_TEXTURE_2D, self.image.bindcode[0])
        else:
            self.image.gl_load(0, bgl.GL_NEAREST, bgl.GL_NEAREST)
            print("reload gl texture")


class Sprite():
    def __init__(self, x, y, move_x, move_y):
        self.x = x
        self.y = y
        self.move_x = move_x
        self.move_y = move_y


class GL_BenchMark():
    def __init__(self):
        self.texture = GL_Texture(image_file_path)
        
        self.fps = 0
        self.fps_count = 0
        self.fps_time = time.time()
        self.fps_one_second = self.fps_time
        
        self.view3d_width = 100.0
        self.view3d_height = 100.0
        
        self.sprite_list = []
        self.sprite_add(100)
    
    def draw(self, context):
        for region in context.area.regions:
            if region.type == "WINDOW":
                self.view3d_width = region.width
                self.view3d_height = region.height
            
        # calc
        self.fps_time = time.time()
        self.fps_count += 1
        if (self.fps_time-self.fps_one_second) >= 1.0:
            self.fps = self.fps_count
            self.fps_count = 0
            self.fps_one_second = self.fps_time
        
        for sp in self.sprite_list:
            sp.x += sp.move_x
            if sp.x < 0.0 or self.view3d_width < sp.x:
                sp.move_x *= -1
                sp.x += sp.move_x
        
            sp.y += sp.move_y
            if sp.y < 0.0 or self.view3d_height < sp.y:
                sp.move_y *= -1
                sp.y += sp.move_y
        
        # draw
        bgl.glColor4f(1.0, 1.0, 1.0, 1.0)
        bgl.glEnable(bgl.GL_BLEND)
        bgl.glEnable(bgl.GL_TEXTURE_2D)
        
        self.texture.bind()
        for sp in self.sprite_list:
            w = self.texture.width
            h = self.texture.height
            bgl.glBegin(bgl.GL_QUADS)
            bgl.glTexCoord2f(0.0, 0.0)
            bgl.glVertex2f(sp.x, sp.y)
            bgl.glTexCoord2f(1.0, 0.0)
            bgl.glVertex2f(sp.x+w, sp.y)
            bgl.glTexCoord2f(1.0, 1.0)
            bgl.glVertex2f(sp.x+w, sp.y+h)
            bgl.glTexCoord2f(0.0, 1.0)
            bgl.glVertex2f(sp.x, sp.y+h)
            bgl.glEnd()
        
        bgl.glDisable(bgl.GL_TEXTURE_2D)
        bgl.glDisable(bgl.GL_BLEND)
        
        # text draw
        font_id = 0
        blf.enable(font_id, blf.SHADOW)
        blf.shadow(font_id, 5, 0.0, 0.0, 0.0, 1.0)
        
        blf.position(font_id, 5, 5, 0)
        blf.size(font_id, 25, 72)
        blf.draw(font_id, "FPS:{}".format(self.fps))
        
        blf.position(font_id, 5, 30, 0)
        blf.size(font_id, 25, 72)
        blf.draw(font_id, "count:{}".format(len(self.sprite_list)))
        
        blf.disable(font_id, blf.SHADOW)
    
    def remove(self):
        self.texture.remove()
    
    def sprite_add(self, count):
        for i in range(count):
            x = random.uniform(1.0, self.view3d_width-1.0)
            y = random.uniform(1.0, self.view3d_height-1.0)
            vx = random.uniform(-2.0, 2.0)
            vy = random.uniform(-2.0, 2.0)
            
            self.sprite_list.append(Sprite(x, y, vx, vy))
    
    def sprite_remove(self, count):
        if len(self.sprite_list) > count:
            for i in range(count):
                self.sprite_list.pop()
    
    def event(self, context, event):
        if event.type == 'UP_ARROW' and event.value == 'PRESS':
            self.sprite_add(100)
            return {'RUNNING_MODAL'}
            
        if event.type == 'DOWN_ARROW' and event.value == 'PRESS':
            self.sprite_remove(100)
            return {'RUNNING_MODAL'}
        
        return {'PASS_THROUGH'}


class GL_BenchMark_Operator(bpy.types.Operator):
    bl_idname = "view3d.gl_bechmark_operator"
    bl_label = "View3D OpenGL Bechmark"
    
    _handle_draw = None
    is_enabled = False
    _timer = None
    _gl_benchmark = None
    
    @staticmethod
    def draw_callback_px(self, context):
        GL_BenchMark_Operator._gl_benchmark.draw(context)
    
    @staticmethod
    def handle_add(self, context):
        GL_BenchMark_Operator._handle_draw = bpy.types.SpaceView3D.draw_handler_add(
                self.draw_callback_px, (self, context), 'WINDOW', 'POST_PIXEL')
        GL_BenchMark_Operator._timer = context.window_manager.event_timer_add(
                1.0/60.0, context.window)
    
    @staticmethod
    def handle_remove(context):
        if GL_BenchMark_Operator._handle_draw is not None:
            context.window_manager.event_timer_remove(GL_BenchMark_Operator._timer)
            bpy.types.SpaceView3D.draw_handler_remove(GL_BenchMark_Operator._handle_draw, 'WINDOW')
            GL_BenchMark_Operator._timer = None
            GL_BenchMark_Operator._handle_draw = None
            GL_BenchMark_Operator.is_enabled = False
    
    @classmethod
    def poll(cls, context):
        return context.area.type == 'VIEW_3D'
    
    def modal(self, context, event):
        if context.area:
            context.area.tag_redraw()
        
        if GL_BenchMark_Operator.is_enabled == False:
            return {'CANCELLED'}
        
        if event.type == 'TIMER':
            return {'PASS_THROUGH'}
        
        return GL_BenchMark_Operator._gl_benchmark.event(context, event)
    
    def invoke(self, context, event):
        if context.area.type == 'VIEW_3D':
            if GL_BenchMark_Operator.is_enabled:
                self.cancel(context)
                return {'FINISHED'}
            else:
                GL_BenchMark_Operator._gl_benchmark = GL_BenchMark()
                GL_BenchMark_Operator.handle_add(self, context)
                GL_BenchMark_Operator.is_enabled = True
                
                context.area.tag_redraw()
                context.window_manager.modal_handler_add(self)
                return {'RUNNING_MODAL'}
        else:
            self.report({'WARNING'}, "View3D not found, cannot run operator")
            return {'CANCELLED'}
    
    def cancel(self, context):
        GL_BenchMark_Operator.handle_remove(context)
        
        if GL_BenchMark_Operator._gl_benchmark is not None:
            GL_BenchMark_Operator._gl_benchmark.remove()
            GL_BenchMark_Operator._gl_benchmark = None


class GL_Benchmark_panel(bpy.types.Panel):
    bl_label = "GL Benchmark"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    
    def draw(self, context):
        layout = self.layout
        if GL_BenchMark_Operator.is_enabled:
            layout.operator("view3d.gl_bechmark_operator", "Stop", icon="PAUSE")
        else:
            layout.operator("view3d.gl_bechmark_operator", "Start", icon="PLAY")


def register():
    bpy.utils.register_class(GL_BenchMark_Operator)
    bpy.utils.register_class(GL_Benchmark_panel)

def unregister():
    bpy.utils.unregister_class(GL_Benchmark_panel)
    bpy.utils.unregister_class(GL_BenchMark_Operator)

if __name__ == "__main__":
    register()

Copy and paste into Blender's text editor.

This time as well, the image file uses this. If you do not have a suitable image at hand, please use it. blender_rogo.png

For ʻimage_file_path` at the top, write the actual file path.

Execution result

First, a button will appear in the 3D View property panel, so press it. 2016-05-04_19h51_33.png

Then it becomes like this. 2016-05-04_19h55_01.png The number of images that count is displaying. It also displays FPS.

You can increase or decrease the number of images displayed with the up and down keys on the keyboard. Please use it as a simple bench mark.

Description

    GL_BenchMark_Operator._timer = context.window_manager.event_timer_add(
            1.0/60.0, context.window)

The event timer is set. Since it seems to be specified in seconds, set 1 second divided by 60. It seems that they will send events at this interval.

The rest is almost the same as the last time.

Finally

I tried to make it like a benchmark, but how about it? In my environment, I was able to stably output 60 FPS up to about 2600.

It might be a good idea to use this to create something like a game. By the way, sound related can be used from ʻaud`.

Recommended Posts

[Blender Python] Set an event timer to refresh the screen 60 times per second
[Python] I tried to summarize the set type (set) in an easy-to-understand manner.
[Blender x Python] How to make an animation
An article summarizing the pitfalls addicted to python
How to know the current directory in Python in Blender
[Blender x Python] How to create an original object
The first step to getting Blender available from Python
[Blender] How to dynamically set the selection of EnumProperty
[Introduction to Udemy Python3 + Application] 30. How to use the set
How to scrape at speed per second with Python Selenium