Jump to content

Popnet To BPHYS Cache Files


Atom

Recommended Posts

Hi All,

I came across some python code that generates a basic transfer from the Houdini particle system to the Blender 2.78 particle system. I tweaked it up a bit. The concept is that Houdini writes Blender format .bphys files that can then be referenced as an external cache from within Blender. The Blender particle system is very crude and as to date the script can only transfer particle location and velocity. I am throwing this up here as a frame work if anyone has any interest in pursuing this kind of binary file construction from within Houdini python. Other cache transfers might be possible as well.

Checkout the example HIP file for setup. To export the cache simply play the timeline. Make sure to set the output path in the script, manually, to a valid folder that you can browse to from within Blender 2.78.

Blender (age honored)blender_bphys_playback.gif.8d02b01f19fd7f3351e251de14e86895.gif

Houdini (reaping off, age generated but not displayed)houdini_playback.gif.14779313019462b7d5b06c7c388dd153.gif

Oddities...

The Blender particle system seems to want all particle to exist on frame #1. Then it manages what appears via unborn and dead flags over time. This means Reaping particles out of Houdini should be turned off. This will insure that unique particle ids are provided throughout time, otherwise you can get some velocity spikes in Blender if a particle id is reused and it was one place on one frame and then is suddenly in another location because of id reuse.

Tip:

Because Blender expects all particles to exist on frame 1 make sure you are not on frame #1, but somewhere in the middle of the simulation when you browse to the external cache. Otherwise Blender may only make the number of particles that exist on frame 1 for the entire cache. This is typically wrong and not what you want because particle counts grow over time.

In order for Blender to locate the cache you must Double Click on the blank cache entry and name it houdinipoints. You can use a different name, but that is the default generated by the python script.

# Export Blender .bphys file from a Houdini particle system or point based object.
# https://developer.blender.org/diffusion/B/browse/master/source/blender/blenkernel/intern/pointcache.c
# Original code found: gui2one
# https://blender.stackexchange.com/questions/47814/bphys-files-sp%C3%A9cifications
# (c) 2017 Atom.

import struct
node = hou.pwd()
geo = node.geometry()

# Set output path and partial name here.
cache_name = r"C:\Users\Admin\Desktop\bphys_cache\houdinipoints"     
point_count = len(geo.points())
frame_current = int(hou.frame())
cache_index = 0

# Attribute defaults, if they don't exist on points.
birth = 0
life = 32
age = 0

# Check for presence of attributes.
a_P = geo.findPointAttrib("P")
a_v = geo.findPointAttrib("v")
a_age = geo.findPointAttrib("age")
a_life = geo.findPointAttrib("life")
a_birth = geo.findPointAttrib("birth_time")     #Note: Constructed by nodes, not part of the default popnet output.

# Write frame #0 only on rewind to 1.
if frame_current ==1:
    # Frame #0 file has a different format that other frames.
    cache_filename = cache_name+"_{0:06d}_{1:02d}.bphys".format(0,cache_index)
    file_handle = open(cache_filename, "wb")
    
    # Use for frame #0 .bphys frame.
    dataType = 1        # 1= particles.
    data = struct.pack("8c","B","P","H","Y","S","I","C","S")
    data += struct.pack("III",dataType,point_count,64)

    for (i,point) in enumerate(geo.points()):
        if a_life: life = point.attribValue(a_life)
        if a_age: age = point.attribValue(a_age)
        if a_birth: birth_time = point.attribValue(a_birth)

        # time, lifetime, dietime.
        data += struct.pack('fff', hou.timeToFrame(hou.time()),hou.timeToFrame(life+birth_time),hou.timeToFrame(life))
    file_handle.write(data)
    file_handle.close

cache_filename = cache_name+"_{0:06d}_{1:02d}.bphys".format(frame_current,cache_index)
file_handle = open(cache_filename, "wb")

# Use for all other .bphys frame #s.
dataType = 1    # 1= particles.
data = struct.pack("8c","B","P","H","Y","S","I","C","S")
data += struct.pack("III",dataType,point_count,111) # Type + Bitwise data section types.
for (i,point) in enumerate(geo.points()):
    local_P = point.attribValue(a_P)
    local_v = point.attribValue(a_v)
            
    #Index 
    data += struct.pack('I',i) 
    #Location
    data += struct.pack('fff',local_P[0], local_P[2]*-1, local_P[1])            # Y negative 1 scale. YZ swapped?
    #Velocity
    data += struct.pack('fff',local_v[0], local_v[2]*-1, local_v[1])            # Y negative 1 scale. YZ swapped? 
    #Rotation
    data += struct.pack('ffff',0.0,0.0,0.0,0.0) # Values don't seem to transfer..?
    
    #Avelocity or Cloth?
    #data += struct.pack('fff',0.0,0.0,0.0) ## no values for now
    
    #frame # for this frame. (seems kind of redundant...)
    data += struct.pack('f',frame_current)  # recommended to be the same as the cache frame number.

    #times
    if a_life: life = point.attribValue(a_life)
    if a_birth: birth_time = point.attribValue(a_birth)
    data += struct.pack('fff',hou.timeToFrame(hou.time()),hou.timeToFrame(life+birth_time),hou.timeToFrame(life)) ## time, die_time, life_time

file_handle.write(data)
file_handle.close
    

 

ap_write_bphys_particles_070617.hipnc

Edited by Atom
  • Like 2
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...