from PyQt5.QtCore import QCoreApplication
import time
import numpy as np

def set_layer_visibility(layer, visible):
    QgsProject.instance().layerTreeRoot().findLayer(layer.id()).setItemVisibilityChecked(visible)

layers = list(QgsProject.instance().mapLayers().values())

count = len(layers)

# hide all layers
[set_layer_visibility(l, False) for l in layers]
finished = False
frames = list(range(count))[::-1]
frames = np.concatenate([frames, frames]) # animate twice
times = []
current_frame = 0

# delay between frames in ms
delay = 50

def animate_next_frame():
    global current_frame, times, finished

    if current_frame < len(frames):
        if current_frame != 0:
            set_layer_visibility(layers[frames[current_frame-1]], False)
    
        set_layer_visibility(layers[frames[current_frame]], True)

        # iface.mapCanvas().refresh()
        
        # This line is going to update (process) everything which might wait in cue like refreshing the layers in map in print composer
        QCoreApplication.processEvents()
    else:
        if not finished:
            finished = True
            print('FPS: ', int(1000 / np.gradient(times).mean()))
            disconnect_signals()

def on_render_complete():
    global current_frame, times
    
    current_frame = current_frame + 1

    t = int(round(time.time() * 1000))
    times.append(t)

    if current_frame <= len(frames):
        QTimer.singleShot(delay, animate_next_frame)
   


connected = True
def disconnect_signals():
    global connected
    
    if not connected:
        return
    else:
        connected = False
    
    # dirty hack to disconnect signals
    try:
        iface.mapCanvas().renderComplete.disconnect()
    except:
        print('Exception during signal disconnect')
        pass
        
iface.mapCanvas().renderComplete.connect(on_render_complete)

QTimer.singleShot(delay, animate_next_frame)

