0
$\begingroup$

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)
```
$\endgroup$
3
  • $\begingroup$ I don't have 2.79 anymore. However, sounds like there is something wrong with the handlers. Try to remove bpy.app.handlers.* in SetFrameHandlers() 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... blender.stackexchange.com/a/167899/31447 $\endgroup$ – brockmann Jun 30 '20 at 21:20
  • $\begingroup$ @brockmann it looks like i'm still having the same problem $\endgroup$ – Joshua C Jul 1 '20 at 22:40
  • $\begingroup$ Downloaded blender 2.79, renamed the cube to 'hair', added a particle system called 'hair-emitter' and I still get quite a bunch of errors. What else is required to set that up? $\endgroup$ – brockmann Jul 1 '20 at 23:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.