i tried to use this Addon in Blender 4.3.2, right after i click Add Snow, this appear
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)
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()
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.