LeitLightLightLeitLeidLite/simulate.py

347 lines
12 KiB
Python
Raw Normal View History

2025-02-13 23:04:06 +01:00
#!/usr/bin/env python3
# Ugly, but working:
#
# 1. Start blender
# 2. Import your light strip with one vertex per LED, ensure vertex index matches LED index
# 3. Load this script in blender and run it
# 4. Start animation
# 5. Switch to material preview or rendered view
# 6. Broadcast your WLED DNRGB updates to the config below
#FIXME: This might need a bit of https://b3d.interplanety.org/en/accessing-custom-attributes-created-in-geometry-nodes/ to get the input indices
HOST = "localhost"
PORT = 21324
#import LeitLightLightLeitLeidLite as l6
import bpy
import mathutils
import socket
obj = bpy.data.objects['strips']
if True:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((HOST, PORT))
sock.setblocking(False)
ledCount = len(obj.data.vertices)
print("%d LEDs" % ledCount)
state = bytearray([0x00,0x00,0x00] * ledCount)
durations = bytearray([0x00] * ledCount)
mesh = obj.data #bpy.context.collection.objects["strips"].data
def setAttrib(attribName, data):
if attribName in mesh.attributes:
attribute = mesh.attributes[attribName]
else:
attribute = mesh.attributes.new(name=attribName, type="FLOAT", domain="POINT")
#attribute_values = [i for i in range(len(mesh.vertices))]
attribute.data.foreach_set("value", data)
# Created using https://extensions.blender.org/add-ons/node-to-python/
def createMaterial(materialName):
mat = bpy.data.materials.new(name = materialName)
mat.use_nodes = True
material = mat.node_tree
#start with a clean node tree
for node in material.nodes:
material.nodes.remove(node)
material.color_tag = 'NONE'
material.description = ""
material.default_group_node_width = 140
#node Material Output
material_output = material.nodes.new("ShaderNodeOutputMaterial")
material_output.name = "Material Output"
material_output.is_active_output = True
material_output.target = 'ALL'
#Displacement
material_output.inputs[2].default_value = (0.0, 0.0, 0.0)
#Thickness
material_output.inputs[3].default_value = 0.0
#node Emission
emission = material.nodes.new("ShaderNodeEmission")
emission.name = "Emission"
#Color
emission.inputs[0].default_value = (1.0, 0.0, 0.018193421885371208, 1.0)
#node Add Shader
add_shader = material.nodes.new("ShaderNodeAddShader")
add_shader.name = "Add Shader"
#node Emission.001
emission_001 = material.nodes.new("ShaderNodeEmission")
emission_001.name = "Emission.001"
#Color
emission_001.inputs[0].default_value = (0.021969836205244064, 1.0, 0.0, 1.0)
#node Add Shader.001
add_shader_001 = material.nodes.new("ShaderNodeAddShader")
add_shader_001.name = "Add Shader.001"
#node Emission.002
emission_002 = material.nodes.new("ShaderNodeEmission")
emission_002.name = "Emission.002"
#Color
emission_002.inputs[0].default_value = (0.0, 0.0016676115337759256, 1.0, 1.0)
#node Attribute
attribute = material.nodes.new("ShaderNodeAttribute")
attribute.name = "Attribute"
attribute.attribute_name = "red"
attribute.attribute_type = 'GEOMETRY'
#node Attribute.001
attribute_001 = material.nodes.new("ShaderNodeAttribute")
attribute_001.name = "Attribute.001"
attribute_001.attribute_name = "green"
attribute_001.attribute_type = 'GEOMETRY'
#node Attribute.002
attribute_002 = material.nodes.new("ShaderNodeAttribute")
attribute_002.name = "Attribute.002"
attribute_002.attribute_name = "blue"
attribute_002.attribute_type = 'GEOMETRY'
#Set locations
material_output.location = (300.0, 300.0)
emission.location = (-90.9997329711914, 320.24908447265625)
add_shader.location = (119.9999771118164, 290.62481689453125)
emission_001.location = (-95.54425048828125, 215.4246063232422)
add_shader_001.location = (115.45545959472656, 163.01229858398438)
emission_002.location = (-100.0887680053711, 87.81208801269531)
attribute.location = (-358.0585021972656, 399.44940185546875)
attribute_001.location = (-350.34014892578125, 246.54885864257812)
attribute_002.location = (-354.8846740722656, 114.37872314453125)
#Set dimensions
material_output.width, material_output.height = 140.0, 100.0
emission.width, emission.height = 140.0, 100.0
add_shader.width, add_shader.height = 140.0, 100.0
emission_001.width, emission_001.height = 140.0, 100.0
add_shader_001.width, add_shader_001.height = 140.0, 100.0
emission_002.width, emission_002.height = 140.0, 100.0
attribute.width, attribute.height = 140.0, 100.0
attribute_001.width, attribute_001.height = 140.0, 100.0
attribute_002.width, attribute_002.height = 140.0, 100.0
#emission.Emission -> add_shader.Shader
material.links.new(emission.outputs[0], add_shader.inputs[0])
#emission_001.Emission -> add_shader.Shader
material.links.new(emission_001.outputs[0], add_shader.inputs[1])
#emission_002.Emission -> add_shader_001.Shader
material.links.new(emission_002.outputs[0], add_shader_001.inputs[1])
#add_shader.Shader -> add_shader_001.Shader
material.links.new(add_shader.outputs[0], add_shader_001.inputs[0])
#attribute.Fac -> emission.Strength
material.links.new(attribute.outputs[2], emission.inputs[1])
#attribute_002.Fac -> emission_002.Strength
material.links.new(attribute_002.outputs[2], emission_002.inputs[1])
#attribute_001.Fac -> emission_001.Strength
material.links.new(attribute_001.outputs[2], emission_001.inputs[1])
#add_shader_001.Shader -> material_output.Surface
material.links.new(add_shader_001.outputs[0], material_output.inputs[0])
return mat
# Created using https://extensions.blender.org/add-ons/node-to-python/
def createGeoNodes(geoNodeName, material):
geometry_nodes = bpy.data.node_groups.new(type = 'GeometryNodeTree', name = geoNodeName)
geometry_nodes.color_tag = 'NONE'
geometry_nodes.description = ""
geometry_nodes.default_group_node_width = 140
geometry_nodes.is_modifier = True
#geometry_nodes interface
#Socket Geometry
geometry_socket = geometry_nodes.interface.new_socket(name = "Geometry", in_out='OUTPUT', socket_type = 'NodeSocketGeometry')
geometry_socket.attribute_domain = 'POINT'
#Socket Geometry
geometry_socket_1 = geometry_nodes.interface.new_socket(name = "Geometry", in_out='INPUT', socket_type = 'NodeSocketGeometry')
geometry_socket_1.attribute_domain = 'POINT'
#initialize geometry_nodes nodes
#node Group Input
group_input = geometry_nodes.nodes.new("NodeGroupInput")
group_input.name = "Group Input"
#node Group Output
group_output = geometry_nodes.nodes.new("NodeGroupOutput")
group_output.name = "Group Output"
group_output.is_active_output = True
#node Instance on Points
instance_on_points = geometry_nodes.nodes.new("GeometryNodeInstanceOnPoints")
instance_on_points.name = "Instance on Points"
#Selection
instance_on_points.inputs[1].default_value = True
#Pick Instance
instance_on_points.inputs[3].default_value = False
#Instance Index
instance_on_points.inputs[4].default_value = 0
#Rotation
instance_on_points.inputs[5].default_value = (0.0, 0.0, 0.0)
#Scale
instance_on_points.inputs[6].default_value = (1.0, 1.0, 1.0)
#node Ico Sphere
ico_sphere = geometry_nodes.nodes.new("GeometryNodeMeshIcoSphere")
ico_sphere.name = "Ico Sphere"
#Radius
ico_sphere.inputs[0].default_value = 1.0
#Subdivisions
ico_sphere.inputs[1].default_value = 1
#node Store Named Attribute
store_named_attribute = geometry_nodes.nodes.new("GeometryNodeStoreNamedAttribute")
store_named_attribute.name = "Store Named Attribute"
store_named_attribute.data_type = 'INT'
store_named_attribute.domain = 'POINT'
#Selection
store_named_attribute.inputs[1].default_value = True
#Name
store_named_attribute.inputs[2].default_value = "index"
#node Index
index = geometry_nodes.nodes.new("GeometryNodeInputIndex")
index.name = "Index"
#node Realize Instances
realize_instances = geometry_nodes.nodes.new("GeometryNodeRealizeInstances")
realize_instances.name = "Realize Instances"
#Selection
realize_instances.inputs[1].default_value = True
#Realize All
realize_instances.inputs[2].default_value = True
#Depth
realize_instances.inputs[3].default_value = 0
#node Capture Attribute
capture_attribute = geometry_nodes.nodes.new("GeometryNodeCaptureAttribute")
capture_attribute.name = "Capture Attribute"
capture_attribute.active_index = 0
capture_attribute.capture_items.clear()
capture_attribute.capture_items.new('FLOAT', "Index")
capture_attribute.capture_items["Index"].data_type = 'INT'
capture_attribute.domain = 'POINT'
#node Set Material
set_material = geometry_nodes.nodes.new("GeometryNodeSetMaterial")
set_material.name = "Set Material"
#Selection
set_material.inputs[1].default_value = True
set_material.inputs[2].default_value = material
#Set locations
group_input.location = (-537.77783203125, 192.87576293945312)
group_output.location = (1020.145263671875, 338.443115234375)
instance_on_points.location = (-40.82560348510742, 80.0461654663086)
ico_sphere.location = (-380.4179382324219, -85.53570556640625)
store_named_attribute.location = (503.62579345703125, 345.2210388183594)
index.location = (-583.5319213867188, 81.38147735595703)
realize_instances.location = (235.0821075439453, 147.38453674316406)
capture_attribute.location = (-276.59991455078125, 312.9664306640625)
set_material.location = (815.2620239257812, 334.15179443359375)
#Set dimensions
group_input.width, group_input.height = 140.0, 100.0
group_output.width, group_output.height = 140.0, 100.0
instance_on_points.width, instance_on_points.height = 140.0, 100.0
ico_sphere.width, ico_sphere.height = 140.0, 100.0
store_named_attribute.width, store_named_attribute.height = 140.0, 100.0
index.width, index.height = 140.0, 100.0
realize_instances.width, realize_instances.height = 140.0, 100.0
capture_attribute.width, capture_attribute.height = 140.0, 100.0
set_material.width, set_material.height = 140.0, 100.0
#initialize geometry_nodes links
#ico_sphere.Mesh -> instance_on_points.Instance
geometry_nodes.links.new(ico_sphere.outputs[0], instance_on_points.inputs[2])
#group_input.Geometry -> capture_attribute.Geometry
geometry_nodes.links.new(group_input.outputs[0], capture_attribute.inputs[0])
#capture_attribute.Geometry -> instance_on_points.Points
geometry_nodes.links.new(capture_attribute.outputs[0], instance_on_points.inputs[0])
#index.Index -> capture_attribute.Index
geometry_nodes.links.new(index.outputs[0], capture_attribute.inputs[1])
#capture_attribute.Index -> store_named_attribute.Value
geometry_nodes.links.new(capture_attribute.outputs[1], store_named_attribute.inputs[3])
#instance_on_points.Instances -> realize_instances.Geometry
geometry_nodes.links.new(instance_on_points.outputs[0], realize_instances.inputs[0])
#realize_instances.Geometry -> store_named_attribute.Geometry
geometry_nodes.links.new(realize_instances.outputs[0], store_named_attribute.inputs[0])
#store_named_attribute.Geometry -> set_material.Geometry
geometry_nodes.links.new(store_named_attribute.outputs[0], set_material.inputs[0])
#set_material.Geometry -> group_output.Geometry
geometry_nodes.links.new(set_material.outputs[0], group_output.inputs[0])
return geometry_nodes
def my_handler(scene):
global state
print("Frame Change", scene.frame_current)
try:
data, addr = sock.recvfrom(4096)
#print("received message: %s" % data)
assert(data[0] == 4) # DNRGB
duration = data[1]
baseIndex = (data[2] << 8) | data[3]
payloadLength = len(data) - 4
assert(payloadLength % 3 == 0)
count = payloadLength // 3
newState = data[4:]
beforeCount = baseIndex
afterIndex = baseIndex + count
state = state[0:beforeCount*3] + newState + state[afterIndex*3:]
except BlockingIOError:
pass
setAttrib('red', [x / 255.0 for x in state[0::3]])
setAttrib('green', [x / 255.0 for x in state[1::3]])
setAttrib('blue', [x / 255.0 for x in state[2::3]])
def regHandler(my_handler):
for func in bpy.app.handlers.frame_change_post[:]:
if func.__name__ == my_handler.__name__:
bpy.app.handlers.frame_change_post.remove(func)
bpy.app.handlers.frame_change_pre.append(my_handler)
material = createMaterial("l6-geometry-nodes")
geometry_nodes = createGeoNodes("l6-material", material)
mod = obj.modifiers.new('l6-modifier', 'NODES')
mod.node_group = geometry_nodes
regHandler(my_handler)