I'm trying to render a video of two moving fibers. After applying an emitter to the fibers meshes and applying the constraint copy transform to the emitter I am able to see the emitter moving in sync with the fiber mesh, but when I save the file the emitter freezes on the current frame in the UI and the animation output.
For context I'm running version 2.79 of Blender, I am using a render farm, but I don't think that's the issue since I see freezing in the .blend file, and the .blend file is on cycles render.
Any help is appreciated! Thanks (Also put the hair emitter script below)
import bpy
import math
import mathutils
import os
import threading
import time
import logging
from bpy.app.handlers import persistent
Tested with Blender 2.66
File configuration
meshName = "hair"
emitterMeshName = "hair-emitter"
shouldRemoveOldMesh = True
Hair configuration
useBSplineInterpolation = True
numberOfSegmentsToDrawForPreview = 10
numberOfSegmentsToRender = 12
hairShape = -0.4
hairWidth = 0.7
def RemoveOldMesh(theMeshName):
candidateList = [item.name for item in bpy.data.objects]
for name in candidateList:
bpy.data.objects[name].select = (name == theMeshName)
bpy.ops.object.delete()
def BuildVertex2EdgeMap(theMesh):
vertex2EdgeMap = [-1] * len(theMesh.data.vertices)
for edge in theMesh.data.edges:
vertex2EdgeMap[edge.vertices[0]] = edge.index
return vertex2EdgeMap
def BuildStrand(theMesh, theVertex2EdgeMap, theStartVertexIndex):
strand = []
maximumNumberOfSegments = 1000
currentVertex = theMesh.data.vertices[theStartVertexIndex]
strand.append(currentVertex.index)
for i in range(maximumNumberOfSegments):
currentEdgeIndex = theVertex2EdgeMap[currentVertex.index]
if currentEdgeIndex == -1:
break
currentEdge = theMesh.data.edges[currentEdgeIndex]
currentVertex = theMesh.data.vertices[currentEdge.vertices[1]]
strand.append(currentVertex.index)
return strand
def BuildStrands(theMesh, theVertex2EdgeMap):
strands = []
currentStartVertexIndex = 0
maximumNumberOfStrands = 100000
for i in range(maximumNumberOfStrands):
if currentStartVertexIndex >= len(theMesh.data.vertices):
break
strand = BuildStrand(theMesh, theVertex2EdgeMap, currentStartVertexIndex)
strands.append(strand)
currentStartVertexIndex = strand[-1] + 1
return strands
def CreateEmitterMesh(theMesh):
mesh = bpy.data.meshes.new(emitterMeshName)
mesh.from_pydata([(0, 0, 0)], [], [])
mesh.update()
object = bpy.data.objects.new(emitterMeshName, mesh)
object.data = mesh
object.rotation_euler = mathutils.Euler((0.5 * math.pi, 0, 0))
scene = bpy.context.scene
scene.objects.link(object)
bpy.context.scene.objects.active = object
bpy.ops.object.constraint_add(type = "COPY_TRANSFORMS")
object.constraints[0].target = theMesh
return object
def HideMesh(theMesh):
theMesh.hide = True
theMesh.hide_render = True
def SubSampleStrandSegments(theStrands):
strands = []
maximumNumberOfSegments = 51
for strand in theStrands:
if len(strand) > maximumNumberOfSegments:
strands.append(strand[0::math.ceil(len(strand)/maximumNumberOfSegments)])
else:
strands.append(strand)
return strands
def SortStrands(theStrands):
sortedStrands = []
strandSegments = sorted([len(strand) for strand in theStrands])
numberOfSegmentsForSystems = sorted(list(set(strandSegments)))
for numberOfSegments in numberOfSegmentsForSystems:
sortedStrands.append([strand for strand in theStrands if len(strand) == numberOfSegments])
return sortedStrands
def CreateParticleSystem(theEmitMesh, theNumberOfParticles, theNumberOfSegments):
bpy.ops.object.particle_system_add()
particleSystem = theEmitMesh.particle_systems.active
particleSystem.name = "hair"
particleSettings = particleSystem.settings
particleSettings.name = "hair-settings"
particleSettings.type = "HAIR"
particleSettings.count = theNumberOfParticles
particleSettings.emit_from = "FACE"
particleSettings.use_hair_bspline = useBSplineInterpolation
particleSettings.draw_step = numberOfSegmentsToDrawForPreview
particleSettings.render_step = numberOfSegmentsToRender
particleSettings.hair_step = theNumberOfSegments - 1
particleSettings.use_advanced_hair = True
particleSettings.physics_type = "KEYED"
particleSettings.cycles.shape = hairShape
particleSettings.cycles.root_width = hairWidth
return particleSystem
def CreateParticleSystems(theStrands, theEmitMesh):
for strandsForParticleSystem in theStrands:
numberOfParticles = len(strandsForParticleSystem)
numberOfSegments = len(strandsForParticleSystem[0])
CreateParticleSystem(theEmitMesh, numberOfParticles, numberOfSegments)
def SetParticleStrand(theStrand, theParticles, theMesh, theFrame):
numberOfSegments = len(theStrand)
vertices = mesh.data.shape_keys.key_blocks[theFrame - 1].data
startCoordinates = vertices[theStrand[0]].co
theParticles.location = startCoordinates
theParticles.hair_keys[0].co = startCoordinates
for i in range(numberOfSegments - 1):
theParticles.hair_keys[i + 1].co = vertices[theStrand[i + 1]].co
def SetParticleStrands(theStrands, theParticleSystem, theMesh, theFrame, isInitialSetting):
if isInitialSetting:
bpy.ops.particle.particle_edit_toggle()
for i in range(len(theStrands)):
SetParticleStrand(theStrands[i], theParticleSystem.particles[i], theMesh, theFrame)
if isInitialSetting:
bpy.ops.particle.particle_edit_toggle()
def SetStrandsForParticleSystems(theMesh, theEmitMesh, theFrame, isInitialSetting):
theStrands = theEmitMesh["strands"]
for i in range(len(theStrands)):
SetParticleStrands(theStrands[i], theEmitMesh.particle_systems[i], theMesh, theFrame, isInitialSetting)
def SaveParticleStrands(theStrands, theEmitMesh):
theEmitMesh["strands"] = theStrands
@persistent
def UpdateParticleSystem(scene):
global mesh, emitterMesh
oldMesh = bpy.context.scene.objects.active
bpy.context.scene.objects.active = emitterMesh
start = scene.frame_start
end = scene.frame_end
current = max(start, min(scene.frame_current, end))
SetStrandsForParticleSystems(mesh, emitterMesh, current, False)
bpy.context.scene.objects.active = oldMesh
def SetFrameHandlers(theUpdateMethod):
bpy.app.handlers.frame_change_pre.clear()
bpy.app.handlers.frame_change_pre.append(theUpdateMethod)
bpy.app.handlers.frame_change_post.clear()
bpy.app.handlers.frame_change_post.append(theUpdateMethod)
if name == "main":
# Remove possibly existing old mesh
if shouldRemoveOldMesh:
RemoveOldMesh(emitterMeshName);
mesh = bpy.context.scene.objects[meshName]
# Calculate strand segments
vertex2EdgeMap = BuildVertex2EdgeMap(mesh)
strands = BuildStrands(mesh, vertex2EdgeMap)
# Subsample strand segments
strands = SubSampleStrandSegments(strands)
strands = SortStrands(strands)
# Create dummy emitter and particle system
emitterMesh = CreateEmitterMesh(mesh)
HideMesh(mesh)
# Create particle systems
CreateParticleSystems(strands, emitterMesh)
# Save strands into emitter mesh
SaveParticleStrands(strands, emitterMesh)
# Set current strands
start = bpy.context.scene.frame_start
end = bpy.context.scene.frame_end
current = max(start, min(bpy.context.scene.frame_current, end))
SetStrandsForParticleSystems(mesh, emitterMesh, current, True)
# Set frame handlers
SetFrameHandlers(UpdateParticleSystem)
else:
# Get existing meshes
mesh = bpy.context.scene.objects[meshName]
emitterMesh = bpy.context.scene.objects[emitterMeshName]
# Set frame handlers
SetFrameHandlers(UpdateParticleSystem)
```
bpy.app.handlers.*inSetFrameHandlers()one by one and see if the error persists. Also have a look into the console, as of Blender 2.79b or something they changed the arguments for handlers... https://blender.stackexchange.com/a/167899/31447 – brockmann Jun 30 '20 at 21:20