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()