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!
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> 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()