347 lines
12 KiB
Python
347 lines
12 KiB
Python
|
#!/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)
|
||
|
|