SOLVED Get Emitter's Number of Particles and their PSR?

Hi,

Is there a way I can retrieve the Emitter's number of particles (with their corresponding PSR) at a given frame using expresso?

I tried dragging the emitter to the xpresso editor but I couldn't see such data but I could be wrong.

Regards,
Ben

P.S. I'm referring to the emitter object and not the thinking particles.

Hello @bentraje,

thank you for reaching out to us. The pages you did discover are there for when does override ObjectData.ModifyParticles. They do not contain any particle emitter or amount information. The number of emitted particles for an emitter is being exposed with ParticleObject::GetParticleCount(). ParticleObject has never been exposed to Python, but one can use the raw data access one can use in many cases. The particle information is stored in a Tparticle VariableTag. When we then know the stride/data size a single particle has in a particle tag, we can produce this:

import c4d
import struct


def GetParticleCount(emitter):
    """Returns the particle count for an emitter object.
    """
    if (not isinstance(emitter, c4d.BaseObject) or
            not emitter.CheckType(c4d.Oparticle)):
        return 0

    tag = emitter.GetTag(c4d.Tparticle)
    buffer = tag.GetLowlevelDataAddressW()
    items = int(len(buffer)/88)

    count = 0
    for index in range(items):
        index *= 88
        bits = int(struct.unpack("B", buffer[index+80:index+81])[0])
        count += (bits != 0)
    return count


def main():
    """
    """
    print(f"{op.GetName()} has {GetParticleCount(op)} particles.")


# Execute main()
if __name__ == '__main__':
    main()

Cheers,
Ferdinand

Hi @ferdinand

Thanks for the response.

RE: import struct
I'm guessing this is the built-in struct library and not the maxon.Struct?

RE: /88
Is there a documentation on the list of parameters? I'm guess 88 is referring the particle number? Would be nice to have a list of them for reference like the PSR.

Hello @bentraje,

no, struct has nothing to do with maxon.Struct and 88 is not the particle count. Your question is effectively out of scope of support since it is about Python libraries and some computer science concepts. I did provide a "brief" explanation below. Please note that this is not a commitment of us to do this regularly, this is just a special case since we effectively mentioned the topic. But in the end, we cannot provide support for learning these concepts.

Thank you for your understanding,
Ferdinand

import c4d
import struct


def GetParticleCount(emitter):
    """Returns the particle count for an emitter object.
    
    Args:
        emitter (any): The entity to check for being an emitter and its 
         particle count.
    
    Returns:
        int: The number of particles that have been emitted. c4d.NOTOK if no
        particle data can be found for 'emitter'.

    References:
        [1] - https://docs.python.org/3/library/stdtypes.html#memoryview
        [2] - https://docs.python.org/3/library/struct.html
    """
    # Making sure that emitter is of type Oparticle and has a Tparticle tag
    # attached to it.
    if (not isinstance(emitter, c4d.BaseObject) or
            not emitter.CheckType(c4d.Oparticle)):
        return c4d.NOTOK

    tag = emitter.GetTag(c4d.Tparticle)
    if tag is None:
        return c4d.NOTOK

    # Get a memoryview of the particle tag data. memoryview is a type [1] of
    # Python which presents a block of memory. It is similar in purpose and
    # functionality to the older bytes and bytearray types. With S22 and 
    # prior, Cinema 4D used its own types to pass blocks of memory to the
    # user in Python. With R23 and the new Python 3 core, Cinema does use
    # memoryview instead. GetLowlevelDataAddressW() returns a memoryview
    # of the raw particle data stored in the particle tag.
    buffer = tag.GetLowlevelDataAddressW()

    # 88 is not the number of particles in the statement below, but the stride
    # of a block of memory.
    #
    # Imagine a single precision, three component float vector type, e.g., 
    # c4d.Vector. It is composed out of three floating point values with
    # 4 bytes, 32 bits, each. In memory this are then just 4 * 32 bits in a 
    # row. In a pseudo c-style we can define the type like that:
    #
    # type Vector
    # {
    #   float x, y, z;
    # }
    #
    # An instance of that type in memory looks then like this. N is the start 
    # of an instance of type Vector at a memory address, and the computer
    # knows that it has a total length of 3*4 bytes, i.e., 12 bytes, since we
    # said so in our type declaration by saying it has three float fields.
    #
    # Address      : N, ...................., N + M
    # Values (hex) : 0000 0000 0000 0000 0000 0000
    # Field        :     x         y         z
    #
    # A block of four digits in hex are 16 bit, we could also write this in
    # binary, i.e., as a number with 16 digits, but it is more convenient to 
    # read memory in this hexadecimal form as a human (unless you are Neo,
    # then binary is also game :) ).
    #
    # This area of memory then simply contains what makes up an instance of
    # a Vector, three floats. The first two blocks, 0000 0000, are where our 
    # first float x is being stored, since each block is 16 bit and our float
    # is single precision, i.e., 32 bits, i.e., two blocks. The 3rd and 4th
    # block then make up y and the 5th and 6th z.
    #
    # This principle can then be extended to more complex types, imagine a
    # matrix type and braking it down into atomic types and most importantly
    # also to arrays. So we can then have a block of memory which is an array
    # of our Vector instances. The size of the elements in this array is then
    # often called stride.
    #
    # Area of memory containing an array of three Vector instances. There are
    # of course no brackets and commas, I added them just for readability:
    #
    #   [[4B, 4B, 4B], [4B, 4B, 4B], [4B, 4B, 4B]]
    #      x,  y,  z     x,  y,  z     x,  y,  z
    #
    # So this memory structure would have a stride of 12 (bytes), i.e., a
    # single element in it, a Vector, is 12 bytes. Its total length would be
    # 36 bytes (3 * 12). The memoryview type is such a block of memory 
    # containing the data for all particles in the particle system, an array
    # of particles. 88 is the stride of that block of memory, a single 
    # particle is composed out of 88 bytes of data. So the variable items 
    # calculated below is the THEORETICAL amount of particles the system 
    # currently holds.
    items = int(len(buffer)/88)

    # So why THEORETICAL? We now loop over this block of memory with our 
    # stride.
    count = 0
    for index in range(items):
        index *= 88
        # And unpack the data for the 81th byte in that particle we are 
        # currently looking at into an integer. The Python library struct [2]
        # has nothing to do with maxon.Struct, but is an raw memory access
        # library. In this case we cast binary to "B", which is an unsigned
        # char, effectively an integer in Python.
        byte = int(struct.unpack("B", buffer[index+80:index+81])[0])
        # Now we increment the counter for the particles when this byte is
        # not zero. What we unpacked there was the particle flag. When the 
        # flag is PARTICLEFLAGS_NONE == 0, then we will not count it, since
        # the particle is then neither visible nor alive.
        count += (byte != 0)
    # So the final count is then only the amount of particles which are either
    # visible, alive or both.
    return count


def main():
    """
    """
    print(f"{op.GetName()} has {GetParticleCount(op)} particles.")


# Execute main()
if __name__ == '__main__':
    main()

Hi @ferdinand

RE: Please note that this is not a commitment of us to do this regularly . .
I understand. Thanks for the detailed explanation and background theory for the answer.

Have a nice ahead!
Will close this thread now.