OverviewFilesCommitsIssuesDocsReleasesPeers

hardops / operators / misc / timer.py

135 lines · python ref:
import bpy
import blf
import time

class HOPS_OT_Timer(bpy.types.Operator):
    bl_idname = "hops.timer"
    bl_label = "Hops Timer"
    bl_options = {'REGISTER', 'UNDO'}
    bl_description = "Draw 3D timer"

    _timer: float = None
    _start_time: float = None
    _handle: int = None
    _fade_start_time: float = None
    _fade_alpha: float = 1.0
    _time_stop: float = 0.0

    # Poperty updates
    def timer_position_x_upd(self, context):
        HOPS_OT_Timer._timer_position_x = self.timer_position_x

    def timer_position_y_upd(self, context):
        HOPS_OT_Timer._timer_position_y = self.timer_position_y

    def timer_text_size_upd(self, context):
        HOPS_OT_Timer._timer_text_size = self.timer_text_size

    def timer_fade_time_upd(self, context):
        HOPS_OT_Timer._timer_fade_time = self.timer_fade_time

    def timer_text_color_upd(self, context):
        HOPS_OT_Timer._timer_text_color = self.timer_text_color
    # Properties

    timer_position_x: bpy.props.IntProperty(name="X Position", default=37, min=0, max=100, update=timer_position_x_upd)
    timer_position_y: bpy.props.IntProperty(name="Y Position", default=86, min=0, max=100, update=timer_position_y_upd)
    timer_text_size: bpy.props.IntProperty(name="Text Size", default=120, min=10, update=timer_text_size_upd)
    timer_fade_time: bpy.props.FloatProperty(name="Fade Time", default=3.0, min=0.1, max=10.0, update=timer_fade_time_upd)
    timer_text_color: bpy.props.FloatVectorProperty(name="Text Color", subtype='COLOR', default=(1.0, 1.0, 1.0), min=0.0, max=1.0, update=timer_text_color_upd)

    _timer_position_x = 50
    _timer_position_y = 50
    _timer_text_size = 120
    _timer_fade_time = 3.0
    _timer_text_color = [1.0, 1.0, 1.0]

    def draw(self, context):
        layout = self.layout
        layout.prop(self, 'timer_position_x', slider=True)
        layout.prop(self, 'timer_position_y', slider=True)
        layout.prop(self, 'timer_text_size')
        layout.prop(self, 'timer_fade_time')
        layout.prop(self, 'timer_text_color')

    def execute(self, context):
        return {'FINISHED'}

    def modal(self, context, event):
        if event.type == 'ESC' and event.value == 'PRESS':
            context.scene.hops.timer_running = False

        if event.type == 'TIMER':
            if context.area:
                context.area.tag_redraw()
            else:
                self.cancel(context)
                return {'CANCELLED'}

        if not context.scene.hops.timer_running:
            if self._fade_start_time is None:
                self._fade_start_time = time.time()
            else:
                elapsed_fade_time = time.time() - self._fade_start_time
                self._fade_alpha = max(0.0, 1.0 - elapsed_fade_time / self.__class__._timer_fade_time)
                if self._fade_alpha <= 0.0:
                    self.cancel(context)
                    return {'CANCELLED'}

        return {'PASS_THROUGH'}

    def invoke(self, context, event):
        if event.ctrl:
            return context.window_manager.invoke_props_dialog(self)

        if context.scene.hops.timer_running:
            context.scene.hops.timer_running = False
            return {'CANCELLED'}

        self.__class__._timer_position_x = self.timer_position_x
        self.__class__._timer_position_y = self.timer_position_y
        self.__class__._timer_text_size = self.timer_text_size
        self.__class__._timer_fade_time = self.timer_fade_time
        self.__class__._timer_text_color = self.timer_text_color

        context.scene.hops.timer_running = True
        self._start_time = time.time()
        self._fade_start_time = None
        self._fade_alpha = 1.0
        self._timer = context.window_manager.event_timer_add(0.01, window=context.window)
        self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_timer, (context,), 'WINDOW', 'POST_PIXEL')
        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}

    def cancel(self, context):
        if self._handle is not None:
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
        if self._timer is not None:
            context.window_manager.event_timer_remove(self._timer)
        if context.area: context.area.tag_redraw()
        return None

    def draw_timer(self, context):
        if self._fade_start_time is None:
            elapsed_time = time.time() - self._start_time
            time_text = "{:.2f}".format(elapsed_time)
            self._time_stop = elapsed_time
        else:
            time_text = "{:.2f}".format(self._time_stop)

        # Set position and size
        region = context.region
        width = region.width
        height = region.height
        x = width * (self.__class__._timer_position_x / 100)
        y = height * (self.__class__._timer_position_y / 100)

        # Draw the text with GPU shaders
        font_id = 0
        blf.position(font_id, x, y, 0)
        blf.size(font_id, self.__class__._timer_text_size)
        blf.enable(font_id, blf.SHADOW)
        blf.shadow(font_id, 5, 0, 0, 0, self._fade_alpha * 1)
        blf.color(font_id, *self.__class__._timer_text_color, self._fade_alpha * 0.3)
        blf.draw(font_id, time_text)