Snow Flake Generator

This is an experimental script that creates a simple particle simulation in the Node Graph (DAG) with dots and unicode snowflake icons. It uses QTimer() to update the dot positions every 50 milliseconds, or 20 times a second, to make it appear like the snow is animated.

This is just a fun script to help me learn more about QTimer() and PySide, and is not intended for use in any production environments!

Nukepedia

How To Use

Step 1: Copy and Paste the whole script into nukes Script Editor

Step 2: Run the script

Step 3: Happy Holidays!

The illusion works best if you disable Shade Nodes in Preferences > Node Colors > Shade Nodes

❅❆❃❊❉The Script ❅❆❃❊❉

#==============================================================================
#                           ---- About ----
#==============================================================================
"""
    DESCRIPTION: This is a silly script that generates dot nodes with unicode snowflake icons
                 The dots will be randomly generated and fall like snow, you can control them in the panel
                 Please be careful where you run this, it can make a mess, and is best used in an empty file
    AUTHOR:      Hiram Gifford
    CONTACT:     hiramgifford.com
    VERSION:     01.01
    PUBLISHED:   2025-12-14
    DOCS:        https://www.hiramgifford.com/nuke-tools-and-scripts/nuke-snow-generator

"""
#==============================================================================
#                       ---- How To Install ----
#==============================================================================
"""
    Step #1:     Copy and Paste the whole script into nukes Script Editor
    Step #2:     Run the script
    Step #3:     Happy Holidays!
"""
#===============================================================================
#                          ---- Imports ----
#===============================================================================

import nuke
import nukescripts
import random
import math

# --- [ Import PySide based on nuke version ] ---
if nuke.NUKE_VERSION_MAJOR < 11:
    from PySide import QtCore, QtGui
    QtWidgets = QtGui
elif nuke.NUKE_VERSION_MAJOR < 16:
    from PySide2 import QtWidgets, QtCore, QtGui
else:
    from PySide6 import QtWidgets, QtCore, QtGui

#===============================================================================
#                       ---- Variables ----
#===============================================================================

max_flakes = 100
spawn_chance = 0.3
fall_speed = 15
sway_amount = 15
update_rate = 50
min_size = 20
max_size = 70
floor_value = 1500
spawn_width = 1200

#===============================================================================
#                       ---- Scripts ----
#===============================================================================

class SnowGenerator(object):
    """Simple snow simulator"""
    def __init__(self):
        self.flakes = []
        self.icons = ["❅", "❆", "❃", "❊", "❉"]
        self.running = False
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.update)
        
        # --- [ Default Settings (Updated by panel) ] ---
        self.max_flakes = max_flakes
        self.spawn_chance = spawn_chance
        self.spawn_width = spawn_width
        self.fall_speed = fall_speed
        self.sway_amount = sway_amount
        self.update_rate = update_rate
        self.min_size = min_size
        self.max_size = max_size
        self.floor_value = floor_value
        
        # --- [ Spawn origin ] --
        self.spawn_origin_x = 0
        self.spawn_origin_y = 0

    def start_snow(self):
        """Starts sim"""
        if self.running:
            return
            
        center_x, center_y = nuke.center()
            
        self.spawn_origin_x = center_x
        self.spawn_origin_y = center_y - 400
        
        self.running = True
        self.timer.start(self.update_rate)

    def create_flake(self):
        """Creates a snowflake dot"""
        half_width = int(self.spawn_width / 2)
        x_pos = self.spawn_origin_x + random.randint(-half_width, half_width)
        y_pos = self.spawn_origin_y + random.randint(-200, 200)
        
        lower = min(self.min_size, self.max_size)
        upper = max(self.min_size, self.max_size)
        font_size = random.randint(lower, upper)

        flake = nuke.nodes.Dot(
            xpos=x_pos, 
            ypos=y_pos,
            hide_input=True, 
            note_font_size=font_size,
            tile_color=858993663,       # Node Graph Grey, to hide the dots
            note_font_color=0xffffffff, # Snow White
            label=random.choice(self.icons)
        )
        
        flake_data = {
            "node": flake,
            "orig_x": x_pos,
            "curr_y": y_pos,
            "phase": random.random() * 10,
            "speed_mult": random.uniform(0.8, 1.2)
        }
        
        self.flakes.append(flake_data)

    def update(self):
        """Animation Spawn Loop"""        
        missing_flakes = self.max_flakes - len(self.flakes)
        
        # --- [  Figure out missing flake amount ] ---
        if missing_flakes > 0:
            spawn_attempts = min(missing_flakes, 5) # Capped at 5 per update to prevent lag spikes
            for _ in range(spawn_attempts):
                if random.random() < self.spawn_chance:
                    self.create_flake()
        
        to_remove = []
        
        for data in self.flakes:
            try:
                node = data["node"]
                
                # --- [  Y gravity ] ---
                data["curr_y"] += (self.fall_speed * data["speed_mult"])
                
                # --- [  X wind/sways  ] ---
                sway = math.sin((data["curr_y"] * 0.01) + data["phase"]) * self.sway_amount
                new_x = data["orig_x"] + sway
                
                node.setXYpos(int(new_x), int(data["curr_y"]))
                
                # --- [ Kill if falls too far ] ---
                if (data["curr_y"] - self.spawn_origin_y) > self.floor_value:
                    to_remove.append(data)
                    
            except ValueError:
                to_remove.append(data)

        # --- [ Cleanup ] ---
        for item in to_remove:
            if item in self.flakes:
                self.flakes.remove(item)
                try:
                    nuke.delete(item["node"])
                except:
                    pass

    def stop_snow(self):
        """Stops timer and cleans up all snow"""
        self.running = False
        self.timer.stop()
        
        for item in self.flakes:
            try:
                nuke.delete(item["node"])
            except:
                pass
        self.flakes = []

class SnowGeneratorPanel(nukescripts.PythonPanel):
    """Creates Snow Generator Panel"""
    def __init__(self):
        nukescripts.PythonPanel.__init__(self, "Snow Generator")
        self.manager = SnowGenerator()
        
        # --- [ Create Knobs ] ---
        self.warning_text = nuke.Text_Knob("warning_text", "<b>&nbsp;Warning!</b>", "This can be slow in large files, and is not intended for production use!\nRun the script in an empty file.\nDon't close the panel without stoping the simulation!")
        
        self.start_btn = nuke.PyScript_Knob("start", "Start Snow")
        self.start_btn.setFlag(nuke.STARTLINE)
        self.stop_btn = nuke.PyScript_Knob("stop", "Stop Snow")
        
        self.divider1 = nuke.Text_Knob("divider1", "")
        
        self.speed_knob = nuke.Double_Knob("speed", "Gravity")
        self.speed_knob.setRange(1, 50)
        self.speed_knob.setValue(fall_speed)
        
        self.sway_knob = nuke.Double_Knob("sway", "Wind/Sway")
        self.sway_knob.setRange(0, 100)
        self.sway_knob.setValue(sway_amount)
        
        self.count_knob = nuke.Int_Knob("count", "Max Flakes")
        self.count_knob.setValue(max_flakes)
        
        self.spawn_chance_knob = nuke.Double_Knob("chance", "Spawn Chance")
        self.spawn_chance_knob.setRange(0, 1)
        self.spawn_chance_knob.setValue(spawn_chance)
        
        self.width_knob = nuke.Int_Knob("width", "Width")
        self.width_knob.setValue(spawn_width)

        self.min_size_knob = nuke.Int_Knob("min_size", "Min Size")
        self.min_size_knob.setValue(min_size)

        self.max_size_knob = nuke.Int_Knob("max_size", "Max Size")
        self.max_size_knob.setValue(max_size)
        
        self.floor_value_knob = nuke.Int_Knob("floor_value", "Floor")
        self.floor_value_knob.setValue(floor_value)
        
        self.update_rate_knob = nuke.Int_Knob("update_rate", "Refresh Rate")
        self.update_rate_knob.setValue(update_rate)

        # --- [ Add Knobs ] ---
        self.addKnob(self.warning_text)
        self.addKnob(self.start_btn)
        self.addKnob(self.stop_btn)
        self.addKnob(self.divider1)
        self.addKnob(self.speed_knob)
        self.addKnob(self.sway_knob)
        self.addKnob(self.count_knob)
        self.addKnob(self.spawn_chance_knob)
        self.addKnob(self.min_size_knob)
        self.addKnob(self.max_size_knob)
        self.addKnob(self.floor_value_knob)
        self.addKnob(self.width_knob)
        self.addKnob(self.update_rate_knob)

    def knobChanged(self, knob):
        """This updates the simulation variables based on the panel values"""
        # --- [ Buttons ] ---
        if knob == self.start_btn:
            self.manager.start_snow()
        elif knob == self.stop_btn:
            self.manager.stop_snow()
            
        # --- [ Realtime knob updates ] ---
        elif knob == self.speed_knob:
            self.manager.fall_speed = self.speed_knob.value()
        elif knob == self.sway_knob:
            self.manager.sway_amount = self.sway_knob.value()
        elif knob == self.width_knob:
            self.manager.spawn_width = max(1, int(self.width_knob.value()))
        elif knob == self.count_knob:
            self.manager.max_flakes = self.count_knob.value()
        elif knob == self.spawn_chance_knob:
            self.manager.spawn_chance = self.spawn_chance_knob.value()
        elif knob == self.min_size_knob:
            self.manager.min_size = self.min_size_knob.value()
        elif knob == self.max_size_knob:
            self.manager.max_size = self.max_size_knob.value()
        elif knob == self.floor_value_knob:
            self.manager.floor_value = self.floor_value_knob.value()            
            
        # --- [ Update Timer Interval ] ---
        elif knob == self.update_rate_knob:
            new_rate = int(self.update_rate_knob.value())
            self.manager.update_rate = new_rate
            if self.manager.running:
                self.manager.timer.setInterval(new_rate)

# --- [ Run to show panel ] ---
SnowGeneratorPanel().show()
Previous
Previous

RenderSwitch