BIMsmith Winter Wonderland Holiday Revit Family Competition 2024

How I built a custom particle system in Revit to simulate falling snow - complete with wind physics, 2,500 animation frames, and the code to make it happen.

What I Built: A Particle System in Revit

The challenge: Create a realistic falling snow animation using only Revit families and parameters.

The result: A custom snowflake family with wind physics, animated across 2,500 frames on the Snowdon Towers sample project.


Why Take on This Challenge?

Every December, I reserve time for the BIMsmith Winter Wonderland competition. It’s my annual excuse to push Revit’s boundaries and discover what’s actually possible.

This year’s question: Can I build a working particle system in Revit?

While testing my plugins on Paul Aubin’s Revit sample project (thanks Paul!), I decided to make it snow on Snowdon Towers. Not just static snow - animated snowflakes falling and swirling in the wind.


How the Snowflake Family Works

The snowflake family looks simple, but it packs some clever engineering:

  • Parametric shape control - Basic formulas adjust the snowflake geometry
  • Wind physics - A mathematical formula creates realistic swaying as it falls
  • Single-parameter animation - The entire fall is controlled by one value: altitude
  • Positive AND negative lengths - Yes, you can do this in Revit (it’s a neat trick)

Design principle: Following the KISS rule, I wanted to animate thousands of snowflakes by changing just one parameter per element.


Performance: Why Optimization Mattered

I’ve built fancy parametric snowflakes before, but this time the goal was different.

Previous approach: Complex, detailed, visually impressive

This year’s approach: Minimal, optimized, animation-ready

The reason? I needed to export nearly 2,500 frames. With thousands of snowflake instances updating per frame, every bit of optimization counted.


Try It Yourself

Happy New Year! I hope you enjoy the animation.

Pro tip: The family structure supports swapping geometries. Replace the snowflake with frogs, leaves, or confetti - the physics still work!

Snowflake Animation Demo

Below you can see the final animation result:

Behind the Scenes

Here’s another look at how the parametric families work together:

The Code

using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using System.Collections.Generic;
using System.Linq;

namespace W7k.Drafter
{
    [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
    internal class TestAnimation : IExternalCommand
    {
        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elementSet)
        {
            // SETUP
            Autodesk.Revit.ApplicationServices.Application app = commandData.Application.Application;
            Autodesk.Revit.DB.Document doc = commandData.Application.ActiveUIDocument.Document;
            UIDocument uidoc = new UIDocument(doc);

            //SELECTION
            var collectorByBuiltInCategory = new W7k.RevitAPI.CollectorByBuiltInCategory(doc, BuiltInCategory.OST_Site);
            List<Element> elements = (from s in collectorByBuiltInCategory.GetElements() 
                                      where s is FamilyInstance fI && fI.Symbol.FamilyName == "SnowFlake" 
                                      select s).ToList();
            TaskDialog.Show("Count", elements.Count.ToString());


            double step = 0.05;
            /* Code to update the animation after crash
            using Transaction t2 = new Transaction(doc, $"Previous animation");
            {
                t2.Start();
                foreach (Element el in elements)
                {
                    Parameter param = el.GetParameters("Altitude").First();
                    param.Set(param.AsDouble() - step * 1013);
                }
                t2.Commit();
            }
            */

            //for (int i = 1013; i < 3000; i++)

            for (int i = 0; i < 3000; i++)
            {
                using Transaction t1 = new Transaction(doc, $"Animate {i}");
                {
                    t1.Start();
                    foreach (Element el in elements)
                    {
                        Parameter param = el.GetParameters("Altitude").First();
                        param.Set(param.AsDouble() - step);
                    }
                    t1.Commit();
                }

                uidoc.RefreshActiveView();

                ImageExportOptions options = new ImageExportOptions
                {
                    FilePath = @"D:\test\" + i + ".png",
                    ZoomType = ZoomFitType.Zoom,
                    HLRandWFViewsFileType = ImageFileType.PNG,
                    ImageResolution = ImageResolution.DPI_300,
                    ShouldCreateWebSite = false
                };
                options.ExportRange = ExportRange.CurrentView;
                doc.ExportImage(options);
            }
            return Result.Succeeded;
        }
    }
}