Set the Coordinates of The Particle System and Render

Hello, I am trying to use Bledenr to visualize my molecular dynamics results. I have nearly 30,000 particles and nearly 10,000 chemical bonds. So I had to use particle system for modeling. I have read an related topic and try my code as:

import bpy
import numpy as np
import gsd.hoomd
from mathutils import Vector;

bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False,confirm=False)

with gsd.hoomd.open("trajecoryt.gsd") as traj:
    snap = traj[-2]

radius     = [  0.01,   0.01, 0.2, 0.2, 0.2, 0.2, 0.2, 4, 4]


boxsize = snap.configuration.box.tolist()[1]

#############################
# create light datablock, set attributes
light_data = bpy.data.lights.new(name="light_2.80", type='SUN')
light_data.energy = 3
# create new object with our light datablock
light_object = bpy.data.objects.new(name="light_2.80", object_data=light_data)
# link light object
bpy.context.collection.objects.link(light_object)
# make it active 
bpy.context.view_layer.objects.active = light_object
#change location
light_object.location = (50, 50, 50)
############################### 
camera_data = bpy.data.cameras.new(name='Camera')
camera_object = bpy.data.objects.new('Camera', camera_data)
bpy.context.scene.collection.objects.link(camera_object)
##################################
# update scene, if needed
dg = bpy.context.evaluated_depsgraph_get() 
dg.update()

bpy.ops.mesh.primitive_uv_sphere_add(radius=1, enter_editmode=False, align='WORLD', location=(-999, -999, -999), scale=(1, 1, 1))
box = bpy.ops.mesh.primitive_cube_add(location = (0,0,0),scale = (boxsize/2,boxsize/2,boxsize/2))
bpy.context.object.name = "Cube"
obj = bpy.context.active_object
bpy.ops.object.shade_smooth()

    
for index,type in enumerate(snap.particles.types):
    obj.modifiers.new(type, type='PARTICLE_SYSTEM')
    N_particles = sum(snap.particles.typeid == index)
    bpy.context.active_object.particle_systems[type].settings.count = N_particles
    bpy.context.active_object.particle_systems[type].settings.emit_from = 'VOLUME'
    bpy.context.active_object.particle_systems[type].settings.render_type = 'OBJECT'
    bpy.context.active_object.particle_systems[type].settings.instance_object = bpy.data.objects["Sphere"]
    bpy.context.active_object.particle_systems[type].settings.particle_size = radius[index]
    bpy.context.active_object.particle_systems[type].settings.frame_start = 0
    bpy.context.active_object.particle_systems[type].settings.frame_end = 0
    bpy.context.active_object.particle_systems[type].settings.normal_factor = 0
    bpy.context.active_object.particle_systems[type].settings.effector_weights.gravity = 0
    bpy.context.active_object.particle_systems[type].settings.lifetime = 10000
bpy.context.object.show_instancer_for_render = False
bpy.context.object.show_instancer_for_viewport = False



# Dependancy graph
degp = bpy.context.evaluated_depsgraph_get()

# Emitter Object
object = bpy.data.objects["Cube"]

# Evaluate the depsgraph (Important step)
particle_systems = object.evaluated_get(degp).particle_systems

scene = bpy.context.scene
cFrame = scene.frame_current
sFrame = scene.frame_start

# at start-frame, clear the particle cache
if cFrame == sFrame:
    psSeed = object.particle_systems[0].seed
    object.particle_systems[0].seed = psSeed


# All particles of first particle-system which has index "0"
for index,type in enumerate(snap.particles.types):
    particles = particle_systems[type].particles

    # Total Particles
    totalParticles = len(particles)
    # length of 1D array or list = 3*totalParticles, "3" due to XYZ in vector/location.
    # If the length is wrong then it will give you an error "internal error setting the array"
#    flatList = [0]*(3*totalParticles)

    # To get the loaction of all particles
    particles.foreach_set("location",snap.particles.position[snap.particles.typeid==index].flatten())

I haven’t considered making the result move, so I only set one frame. And I set up different particle systems for different atoms. The output looks like great as:

However, after rendering, the position of the particls changed. And I even found that it will happend after I switch to the edit model and then back to object model.

I think that It may be related to the source of the particle system. Did I forget something?

After rendering

After switching to edit model and back to object model:

The only way I ever managed to make particles with python was to write a particle cache files with python. I mean the .bphys file cache files that are created when you write a particle system to disk which you can then use in a particle system when you tick “external cache”.

Perhaps there are better ways to do this with geometry nodes theses days but just in case I will write up what I remember:

Unfortunately it is quite a long time ago and i forgot the details and the bphys file format was pretty difficult to figure out. Especially because there are different types of bphys (several for the different types of particles, one for rigid body simulations one for cloth and so on). The format is also not documented really well. Or at least I as an on programmer found it pretty difficult.

I remember that you have to figure out which header to use to determin which type of bphys the cache will be used for. Then you have to write a whole bunch of stuff into the header of each file.
You also have to write a whole bunch of stuff (such as total particle amount) into the very first bphys file but not into all the other bphys files. This took me forever to figure out.

Then you have to gather all the information your particles need to cointain stuff like date of birth, locatio, rotation and so on and put into your cache files. The files are binary files, so you have to pack then with “struct.pack”

Thank you! This helps me a lot. Now I know that the problem is the coordinates were not sent to the cache so that the render failed. I hope that there would be are easy way.

Would it not make more sense to create a mesh with python consisting of one vertex per sphere you want to spawn.
Then use either dupliverts or geonodes to instance your spheres onto the vertices.

The codes below could clear the cache and save current position:

#clear the post frame handler
bpy.app.handlers.frame_change_post.clear()

#run the function on each frame
bpy.app.handlers.frame_change_post.append(particleSetter)