Rotation data is stored as unit quaternions in Particle data as stated in the API. And since it is a bpy_prop_collection
, it allows fast access through the foreach_get
function. So, we initialize a quaternion list with length equal to the number of particles using this expression:
QuaternionList(length = len(system.particles))
Then foreach_get
is used to copy the quaternion data from the particles to the initialized list using this expression:
(system.particles.foreach_get("rotation", quaternions.asMemoryView()), quaternions)[1]
The creation of the tuple and sampling of the second element is just to make sure quaternions are actually returned from the expression node. So the node tree is as follows:

You can now convert this to Eulers using the Convert Rotation Type Node and use it as you wish. The output list, however, is not masked based on the alive status of the particles, meaning all particles are output even if they are dead or aren't born yet. To mask it, we can first get the alive_state
of each particle, to do this we can use a list comprehension (since foreach_get
doesn't support string data, and alive_state
is either "DEAD", "UNBORN", "ALIVE" or "DYING"):
[particle.alive_state for particle in system.particles]
But we actually want a boolean that tells us whether or not this particle should be kept, and we can do this by some if and logic statements. For instance, to get the particles that are alive or unborn, we can write:
[particle.alive_state == "ALIVE" or particle.alive_state == "UNBORN" for particle in system.particles]
We can then use the Mask List Node to mask the quaternion list based on this boolean list:
