Real Snow Addon Error

i tried to use this Addon in Blender 4.3.2, right after i click Add Snow, this appear

1 Like

Bugs in addons need to be forward to the addon developer…
(one can look in the extensions.blender.org, how to open an new ‘issue’ for any specific addon, or to contact the developer directly)

In this case, you should visit this page (and also look for already filed issues, to avoid duplicates)

2 Likes

Most likely the addon was made for a previous Blender version, since the specific error line relies on getting a node input socket by index rather than by name. The principled BSDF inputs have been reshuffled in one of the most recent versions (4.2 I think ?) so you might want to try it on V4.1.

I believe issues are tracked on https://projects.blender.org/extensions/real_snow

Sounds like a perfect new year issue! Replace the __init__.py file contents in real_snow folder with this.

# SPDX-FileCopyrightText: 2020-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later

bl_info = {
    "name": "Real Snow",
    "description": "Generate snow mesh",
    "author": "Marco Pavanello, Drew Perttula",
    "version": (1, 3, 2),
    "blender": (4, 1, 0),
    "location": "View 3D > Properties Panel",
    "doc_url": "{BLENDER_MANUAL_URL}/addons/object/real_snow.html",
    "tracker_url": "https://gitlab.com/marcopavanello/real-snow/-/issues",
    "support": "COMMUNITY",
    "category": "Object",
}


# Libraries
import math
import os
import random
import time

import bpy
import bmesh
from bpy.props import BoolProperty, FloatProperty, IntProperty, PointerProperty
from bpy.types import Operator, Panel, PropertyGroup
from mathutils import Vector
from typing import TypeVar, Any

T = TypeVar("T")


# Panel
class REAL_PT_snow(Panel):
    bl_space_type = "VIEW_3D"
    bl_context = "objectmode"
    bl_region_type = "UI"
    bl_label = "Snow"
    bl_category = "Real Snow"

    def draw(self, context):
        scn = context.scene
        settings = scn.snow
        layout = self.layout

        col = layout.column(align=True)
        col.prop(settings, "coverage", slider=True)
        col.prop(settings, "height")

        layout.use_property_split = True
        layout.use_property_decorate = False
        flow = layout.grid_flow(row_major=True, columns=0, even_columns=False, even_rows=False, align=True)
        col = flow.column()
        col.prop(settings, "vertices")

        row = layout.row(align=True)
        row.scale_y = 1.5
        row.operator("snow.create", text="Add Snow", icon="FREEZE")


class SNOW_OT_Create(Operator):
    bl_idname = "snow.create"
    bl_label = "Create Snow"
    bl_description = "Create snow"
    bl_options = {"REGISTER", "UNDO"}

    @classmethod
    def poll(cls, context) -> bool:
        return bool(context.selected_objects)

    def execute(self, context):
        coverage = context.scene.snow.coverage
        height = context.scene.snow.height
        vertices = context.scene.snow.vertices

        # Get a list of selected objects, except non-mesh objects
        input_objects = [obj for obj in context.selected_objects if obj.type == "MESH"]
        snow_list = []
        # Start UI progress bar
        length = len(input_objects)
        context.window_manager.progress_begin(0, 10)
        timer = 0
        for obj in input_objects:
            # Timer
            context.window_manager.progress_update(timer)
            # Duplicate mesh
            bpy.ops.object.select_all(action="DESELECT")
            obj.select_set(True)
            context.view_layer.objects.active = obj
            object_eval = obj.evaluated_get(context.view_layer.depsgraph)
            mesh_eval = bpy.data.meshes.new_from_object(object_eval)
            snow_object = bpy.data.objects.new("Snow", mesh_eval)
            snow_object.matrix_world = obj.matrix_world
            context.collection.objects.link(snow_object)
            bpy.ops.object.select_all(action="DESELECT")
            context.view_layer.objects.active = snow_object
            snow_object.select_set(True)
            bpy.ops.object.mode_set(mode="EDIT")
            bm_orig = bmesh.from_edit_mesh(snow_object.data)
            bm_copy = bm_orig.copy()
            bm_copy.transform(obj.matrix_world)
            bm_copy.normal_update()
            # Get faces data
            delete_faces(vertices, bm_copy, snow_object)
            ballobj = add_metaballs(context, height, snow_object)
            context.view_layer.objects.active = snow_object
            surface_area = area(snow_object)
            snow = add_particles(context, surface_area, height, coverage, snow_object, ballobj)
            add_modifiers(snow)
            # Place inside collection
            context.view_layer.active_layer_collection = context.view_layer.layer_collection
            if "Snow" not in context.scene.collection.children:
                coll = bpy.data.collections.new("Snow")
                context.scene.collection.children.link(coll)
            else:
                coll = bpy.data.collections["Snow"]
            coll.objects.link(snow)
            context.view_layer.layer_collection.collection.objects.unlink(snow)
            add_material(snow)
            # Parent with object
            snow.parent = obj
            snow.matrix_parent_inverse = obj.matrix_world.inverted()
            # Add snow to list
            snow_list.append(snow)
            # Update progress bar
            timer += 0.1 / length
        # Select created snow meshes
        for s in snow_list:
            s.select_set(True)
        # End progress bar
        context.window_manager.progress_end()

        return {"FINISHED"}


def add_modifiers(snow):
    bpy.ops.object.transform_apply(location=False, scale=True, rotation=False)
    # Decimate the mesh to get rid of some visual artifacts
    snow.modifiers.new("Decimate", "DECIMATE")
    snow.modifiers["Decimate"].ratio = 0.5
    snow.modifiers.new("Subdiv", "SUBSURF")
    snow.modifiers["Subdiv"].render_levels = 1
    snow.modifiers["Subdiv"].quality = 1
    snow.cycles.use_adaptive_subdivision = True


def add_particles(
    context,
    surface_area: float,
    height: float,
    coverage: float,
    snow_object: bpy.types.Object,
    ballobj: bpy.types.Object,
):
    # Approximate the number of particles to be emitted
    number = int(surface_area * 50 * (height**-2) * ((coverage / 100) ** 2))
    bpy.ops.object.particle_system_add()
    particles = snow_object.particle_systems[0]
    psettings = particles.settings
    psettings.type = "HAIR"
    psettings.render_type = "OBJECT"
    # Generate random number for seed
    random_seed = random.randint(0, 1000)
    particles.seed = random_seed
    # Set particles object
    psettings.particle_size = height
    psettings.instance_object = ballobj
    psettings.count = number
    # Convert particles to mesh
    bpy.ops.object.select_all(action="DESELECT")
    context.view_layer.objects.active = ballobj
    ballobj.select_set(True)
    bpy.ops.object.convert(target="MESH")
    snow = bpy.context.active_object
    snow.scale = [0.09, 0.09, 0.09]
    bpy.ops.object.origin_set(type="ORIGIN_GEOMETRY")
    bpy.ops.object.select_all(action="DESELECT")
    snow_object.select_set(True)
    bpy.ops.object.delete()
    snow.select_set(True)
    return snow


def add_metaballs(context, height: float, snow_object: bpy.types.Object) -> bpy.types.Object:
    ball_name = "SnowBall"
    ball = bpy.data.metaballs.new(ball_name)
    ballobj = bpy.data.objects.new(ball_name, ball)
    bpy.context.scene.collection.objects.link(ballobj)
    # These settings have proven to work on a large amount of scenarios
    ball.resolution = 0.7 * height + 0.3
    ball.threshold = 1.3
    element = ball.elements.new()
    element.radius = 1.5
    element.stiffness = 0.75
    ballobj.scale = [0.09, 0.09, 0.09]
    return ballobj


def delete_faces(vertices, bm_copy, snow_object: bpy.types.Object):
    # Find upper faces
    if vertices:
        selected_faces = set(face.index for face in bm_copy.faces if face.select)
    # Based on a certain angle, find all faces not pointing up
    down_faces = set(
        face.index for face in bm_copy.faces if Vector((0, 0, -1.0)).angle(face.normal, 4.0) < (math.pi / 2.0 + 0.5)
    )
    bm_copy.free()
    bpy.ops.mesh.select_all(action="DESELECT")
    # Select upper faces
    mesh = bmesh.from_edit_mesh(snow_object.data)
    for face in mesh.faces:
        if vertices:
            if face.index not in selected_faces:
                face.select = True
        if face.index in down_faces:
            face.select = True
    # Delete unnecessary faces
    faces_select = [face for face in mesh.faces if face.select]
    bmesh.ops.delete(mesh, geom=faces_select, context="FACES_KEEP_BOUNDARY")
    mesh.free()
    bpy.ops.object.mode_set(mode="OBJECT")


def area(obj: bpy.types.Object) -> float:
    bm_obj = bmesh.new()
    bm_obj.from_mesh(obj.data)
    bm_obj.transform(obj.matrix_world)
    area = sum(face.calc_area() for face in bm_obj.faces)
    bm_obj.free
    return area


def safe_cast(typ: type[T], value: Any) -> T:
    assert isinstance(value, typ)
    return value


def add_material(obj: bpy.types.Object):
    mat_name = "Snow"
    # If material doesn't exist, create it
    if mat_name in bpy.data.materials:
        bpy.data.materials[mat_name].name = mat_name + ".001"
    mat = bpy.data.materials.new(mat_name)
    mat.use_nodes = True
    assert mat.node_tree
    nodes = mat.node_tree.nodes
    # Delete all nodes
    for node in nodes:
        nodes.remove(node)
    # Add nodes
    output = safe_cast(bpy.types.ShaderNodeOutputMaterial, nodes.new("ShaderNodeOutputMaterial"))
    principled = safe_cast(bpy.types.ShaderNodeBsdfPrincipled, nodes.new("ShaderNodeBsdfPrincipled"))
    vec_math = safe_cast(bpy.types.ShaderNodeVectorMath, nodes.new("ShaderNodeVectorMath"))
    com_xyz = safe_cast(bpy.types.ShaderNodeCombineXYZ, nodes.new("ShaderNodeCombineXYZ"))
    dis = safe_cast(bpy.types.ShaderNodeDisplacement, nodes.new("ShaderNodeDisplacement"))
    mul1 = safe_cast(bpy.types.ShaderNodeMath, nodes.new("ShaderNodeMath"))
    add1 = safe_cast(bpy.types.ShaderNodeMath, nodes.new("ShaderNodeMath"))
    add2 = safe_cast(bpy.types.ShaderNodeMath, nodes.new("ShaderNodeMath"))
    mul2 = safe_cast(bpy.types.ShaderNodeMath, nodes.new("ShaderNodeMath"))
    mul3 = safe_cast(bpy.types.ShaderNodeMath, nodes.new("ShaderNodeMath"))
    range1 = safe_cast(bpy.types.ShaderNodeMapRange, nodes.new("ShaderNodeMapRange"))
    range2 = safe_cast(bpy.types.ShaderNodeMapRange, nodes.new("ShaderNodeMapRange"))
    range3 = safe_cast(bpy.types.ShaderNodeMapRange, nodes.new("ShaderNodeMapRange"))
    vor = safe_cast(bpy.types.ShaderNodeTexVoronoi, nodes.new("ShaderNodeTexVoronoi"))
    noise1 = safe_cast(bpy.types.ShaderNodeTexNoise, nodes.new("ShaderNodeTexNoise"))
    noise2 = safe_cast(bpy.types.ShaderNodeTexNoise, nodes.new("ShaderNodeTexNoise"))
    noise3 = safe_cast(bpy.types.ShaderNodeTexNoise, nodes.new("ShaderNodeTexNoise"))
    mapping = safe_cast(bpy.types.ShaderNodeMapping, nodes.new("ShaderNodeMapping"))
    coord = safe_cast(bpy.types.ShaderNodeTexCoord, nodes.new("ShaderNodeTexCoord"))
    # Change location
    output.location = (100, 0)
    principled.location = (-200, 600)
    vec_math.location = (-400, 400)
    com_xyz.location = (-600, 400)
    dis.location = (-200, -100)
    mul1.location = (-400, -100)
    add1.location = (-600, -100)
    add2.location = (-800, -100)
    mul2.location = (-1000, -100)
    mul3.location = (-1000, -300)
    range1.location = (-400, 200)
    range2.location = (-1200, -300)
    range3.location = (-800, -300)
    vor.location = (-1500, 200)
    noise1.location = (-1500, 0)
    noise2.location = (-1500, -250)
    noise3.location = (-1500, -500)
    mapping.location = (-1700, 0)
    coord.location = (-1900, 0)
    # Change node parameters

    # Principled.
    principled.distribution = "MULTI_GGX"
    principled.subsurface_method = "RANDOM_WALK_SKIN"
    safe_cast(bpy.types.NodeSocketColor, principled.inputs["Base Color"]).default_value = (0.904, 0.904, 0.904, 1.0)
    safe_cast(bpy.types.NodeSocketFloatFactor, principled.inputs["Subsurface Weight"]).default_value = 1
    safe_cast(bpy.types.NodeSocketFloatDistance, principled.inputs["Subsurface Scale"]).default_value = 1
    safe_cast(bpy.types.NodeSocketVector, principled.inputs["Subsurface Radius"]).default_value = (0.36, 0.46, 0.6)
    safe_cast(bpy.types.NodeSocketFloatFactor, principled.inputs["Specular IOR Level"]).default_value = 0.224
    safe_cast(bpy.types.NodeSocketFloatFactor, principled.inputs["Roughness"]).default_value = 0.1
    safe_cast(bpy.types.NodeSocketFloatFactor, principled.inputs["Coat Roughness"]).default_value = 0.1
    safe_cast(bpy.types.NodeSocketFloat, principled.inputs["Coat IOR"]).default_value = 1.2

    safe_cast(bpy.types.NodeSocketColor, principled.inputs["Base Color"]).default_value = (0.904, 0.904, 0.904, 1.0)
    safe_cast(bpy.types.NodeSocketFloatFactor, principled.inputs["Subsurface Weight"]).default_value = 1
    safe_cast(bpy.types.NodeSocketFloatDistance, principled.inputs["Subsurface Scale"]).default_value = 1
    safe_cast(bpy.types.NodeSocketVector, principled.inputs["Subsurface Radius"]).default_value = (0.36, 0.46, 0.6)
    safe_cast(bpy.types.NodeSocketFloatFactor, principled.inputs["Specular IOR Level"]).default_value = 0.224
    safe_cast(bpy.types.NodeSocketFloatFactor, principled.inputs["Roughness"]).default_value = 0.1
    safe_cast(bpy.types.NodeSocketFloatFactor, principled.inputs["Coat Roughness"]).default_value = 0.1
    safe_cast(bpy.types.NodeSocketFloat, principled.inputs["Coat IOR"]).default_value = 1.2

    vec_math.operation = "MULTIPLY"
    safe_cast(bpy.types.NodeSocketVector, vec_math.inputs[1]).default_value = (0.5, 0.5, 0.5)

    # Combine XYZ.
    safe_cast(bpy.types.NodeSocketFloat, com_xyz.inputs[0]).default_value = 0.36
    safe_cast(bpy.types.NodeSocketFloat, com_xyz.inputs[1]).default_value = 0.46
    safe_cast(bpy.types.NodeSocketFloat, com_xyz.inputs[2]).default_value = 0.6

    safe_cast(bpy.types.NodeSocketFloat, dis.inputs[1]).default_value = 0.3
    safe_cast(bpy.types.NodeSocketFloat, dis.inputs[2]).default_value = 0.3

    mul1.operation = "MULTIPLY"
    safe_cast(bpy.types.NodeSocketFloat, mul1.inputs[1]).default_value = 0.1

    mul2.operation = "MULTIPLY"
    safe_cast(bpy.types.NodeSocketFloat, mul2.inputs[1]).default_value = 0.6

    mul3.operation = "MULTIPLY"
    safe_cast(bpy.types.NodeSocketFloat, mul3.inputs[1]).default_value = 0.4

    safe_cast(bpy.types.NodeSocketFloat, range1.inputs["From Min"]).default_value = 0.525
    safe_cast(bpy.types.NodeSocketFloat, range1.inputs["From Max"]).default_value = 0.58
    safe_cast(bpy.types.NodeSocketFloat, range2.inputs["From Min"]).default_value = 0.069
    safe_cast(bpy.types.NodeSocketFloat, range2.inputs["From Max"]).default_value = 0.757
    safe_cast(bpy.types.NodeSocketFloat, range3.inputs["From Min"]).default_value = 0.069
    safe_cast(bpy.types.NodeSocketFloat, range3.inputs["From Max"]).default_value = 0.757

    vor.feature = "N_SPHERE_RADIUS"
    safe_cast(bpy.types.NodeSocketFloat, vor.inputs["Scale"]).default_value = 30
    safe_cast(bpy.types.NodeSocketFloat, noise1.inputs["Scale"]).default_value = 12
    safe_cast(bpy.types.NodeSocketFloat, noise2.inputs["Scale"]).default_value = 2
    safe_cast(bpy.types.NodeSocketFloat, noise2.inputs["Detail"]).default_value = 4
    safe_cast(bpy.types.NodeSocketFloat, noise3.inputs["Scale"]).default_value = 1
    safe_cast(bpy.types.NodeSocketFloat, noise3.inputs["Detail"]).default_value = 4

    safe_cast(bpy.types.NodeSocketVectorXYZ, mapping.inputs["Scale"]).default_value = (12, 12, 12)

    # Link nodes
    link = mat.node_tree.links
    link.new(principled.outputs[0], output.inputs[0])
    link.new(vec_math.outputs[0], principled.inputs[8])
    link.new(com_xyz.outputs[0], vec_math.inputs[0])
    link.new(dis.outputs[0], output.inputs[2])
    link.new(mul1.outputs[0], dis.inputs[0])
    link.new(add1.outputs[0], mul1.inputs[0])
    link.new(add2.outputs[0], add1.inputs[0])
    link.new(mul2.outputs[0], add2.inputs[0])
    link.new(mul3.outputs[0], add2.inputs[1])
    link.new(range1.outputs[0], principled.inputs[18])
    link.new(range2.outputs[0], mul3.inputs[0])
    link.new(range3.outputs[0], add1.inputs[1])
    link.new(vor.outputs[4], range1.inputs[0])
    link.new(noise1.outputs[0], mul2.inputs[0])
    link.new(noise2.outputs[0], range2.inputs[0])
    link.new(noise3.outputs[0], range3.inputs[0])
    link.new(mapping.outputs[0], vor.inputs[0])
    link.new(mapping.outputs[0], noise1.inputs[0])
    link.new(mapping.outputs[0], noise2.inputs[0])
    link.new(mapping.outputs[0], noise3.inputs[0])
    link.new(coord.outputs[3], mapping.inputs[0])
    # Set displacement and add material
    mat.displacement_method = "DISPLACEMENT"
    obj.data.materials.append(mat)


# Properties
class SnowSettings(PropertyGroup):
    coverage: IntProperty(
        name="Coverage",
        description="Percentage of the object to be covered with snow",
        default=100,
        min=0,
        max=100,
        subtype="PERCENTAGE",
    )

    height: FloatProperty(
        name="Height", description="Height of the snow", default=0.3, step=1, precision=2, min=0.1, max=1
    )

    vertices: BoolProperty(name="Selected Faces", description="Add snow only on selected faces", default=False)


#############################################################################################
classes = (REAL_PT_snow, SNOW_OT_Create, SnowSettings)

register, unregister = bpy.utils.register_classes_factory(classes)


# Register
def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.Scene.snow = PointerProperty(type=SnowSettings)


# Unregister
def unregister():
    for cls in classes:
        bpy.utils.unregister_class(cls)
    del bpy.types.Scene.snow


if __name__ == "__main__":
    register()
2 Likes

The Gitlab link in my post also has an ‘Issues’ page. Though it only depends which server the developer will look first. (From the blender site, the only thing they can do is to disable the addon for 4.2+ and contact the developer directly)

For this error in particular, there’s already a filed issue in gitlab that if addressed will most likely fix OP’s bug.

It’s working
Thanks man

It works. Thank you dude.