Difference between revisions of "Particle (lx-particle.hpp)"
(Header Documentation) |
(No difference)
|
Latest revision as of 21:01, 19 February 2018
Contents
- 1 Particle Source
- 1.1 (1) SDK: ParticleItem::Prepare, etc.
- 1.2 (2) SDK: LXu_PARTICLEITEM define
- 1.3 (3) SDK: CLxUser_ParticleItem::Alloc method
- 1.4 (4) PY: empty ParticleItem user class
- 1.5 (5) SDK: LXsPKG_READPARTICLES, etc. defines
- 1.6 (6) SDK: LXsPARTICLEATTR_SEED, etc. defines
- 1.7 (7) SDK: LXiTBLX_PARTICLES, etc. defines
- 1.8 (8) SDK: LXu_REPLICATORENUMERATOR define
- 1.9 (9) SDK: ReplicatorEnumerator::Enumerate, etc.
- 1.10 (10) SDK: CLxUser_ReplicatorEnumerator::Enum method
- 1.11 (11) PY: empty ReplicatorEnumerator user class
- 2 Particle Simulations
- 2.1 Evaluation State
- 2.1.1 (12) SDK: ParticleEvalFrame::VertexDescription
- 2.1.2 (13) SDK: ParticleEvalFrame::MaxCount, etc.
- 2.1.3 (14) SDK: ParticleEvalFrame::AddParticle
- 2.1.4 (15) SDK: ParticleEvalFrame::KillParticle
- 2.1.5 (16) SDK: ParticleEvalFrame::IsAlive
- 2.1.6 (17) SDK: ParticleEvalFrame::GetVector, etc.
- 2.1.7 (18) SDK: ParticleEvalFrame::AliveRun, etc.
- 2.1.8 (19) SDK: LXfFRAME_ALIVE, etc. defines
- 2.1.9 (20) SDK: ParticleEvalFrame::Neighbors
- 2.1.10 (21) SDK: LXu_PARTICLEEVALFRAME define
- 2.1.11 (22) SDK: empty ParticleEvalFrame User Class
- 2.1.12 (23) PY: empty ParticleEvalFrame user class
- 2.2 Particle Filters
- 2.2.1 (24) SDK: ParticleFilter::Vertex
- 2.2.2 (25) SDK: ParticleFilter::Flags
- 2.2.3 (26) SDK: LXiPFILT_FRAME, etc. defines
- 2.2.4 (27) SDK: ParticleFilter::Initialize
- 2.2.5 (28) SDK: ParticleFilter::Step
- 2.2.6 (29) SDK: ParticleFilter::Cleanup
- 2.2.7 (30) SDK: ParticleFilter::Frame
- 2.2.8 (31) SDK: ParticleFilter::Run
- 2.2.9 (32) SDK: ParticleFilter::Particle
- 2.2.10 (33) SDK: LXu_PARTICLEFILTER, etc. defines
- 2.2.11 (34) SDK: empty ParticleFilter User Class
- 2.2.12 (35) PY: empty ParticleFilter user class
- 2.3 Particle Co-Operators
- 2.4 Point Cache
- 2.5 Simulation Item
- 2.1 Evaluation State
- 3 Particle Service
- 3.1 (46) SDK: LXa_PARTICLESERVICE, etc. defines
- 3.2 (47) SDK: ParticleService::ScriptQuery
- 3.3 (48) SDK: ParticleService::GetReplicatorEnumerator
- 3.4 (49) SDK: ParticleService::EnumParticleFeatures
- 3.5 (50) SDK: ParticleService::FeatureIdent
- 3.6 (51) SDK: ParticleService::FeatureOffset
- 3.7 (52) SDK: CLxUser_ParticleService::EnumFeatures method
- 3.8 (53) SDK: ParticleService::TriGroupToParticle
- 3.9 (54) SDK: ParticleService::TriGroupBlend
- 3.10 (55) SDK: ParticleService::EnumeratorPrepare, etc.
- 3.11 (56) SDK: ParticleService::ItemToParticle
- 3.12 (57) PY: empty Service.Particle user class
Particle Source
Any item can potentially act as a particle source. This interface allocates a particle object for a given item.
Prepare | Given an ILxEvaluation interface, the item selects that channels it needs for input. It can return an index, generally for the first channel it added. |
Evaluate | After prepping, this method is called to allocate the particle source object. The ILxAttributes interface and index are used to read channels selected by the Prepare() method. |
The particle object doesn't have a specialized interface, and is instead implemented using a TableauSurface interface to get the point data, and an attributes interface to set the special attributes.
(1) SDK: ParticleItem::Prepare, etc.
LXxMETHOD( LxResult, Prepare) ( LXtObjectID self, LXtObjectID eval, unsigned *index); LXxMETHOD( LxResult, Evaluate) ( LXtObjectID self, LXtObjectID attr, unsigned index, void **ppvObj);
(2) SDK: LXu_PARTICLEITEM define
#define LXu_PARTICLEITEM "BA13DD5D-5093-4D0D-BEFE-119AD4F1FACB"
(3) SDK: CLxUser_ParticleItem::Alloc method
bool Alloc ( ILxUnknownID attr, unsigned index, CLxLocalizedObject &loc) { LXtObjectID obj; if (LXx_OK (Evaluate (attr, index, &obj))) return loc.take (obj); loc.clear (); return false; }
Empty ParticleItem Python user class.
(4) PY: empty ParticleItem user class
pass
Any item can read particle sources by setting this tag. This allows particle graph connections for the item.
(5) SDK: LXsPKG_READPARTICLES, etc. defines
#define LXsPKG_READPARTICLES "readsParticles" #define LXsITYPE_SINGLEPARTICLE "1" #define LXsITYPE_MULTIPARTICLE "*" #define LXsITYPE_ORDEREDPARTICLE "o"
The particle object can also support an ILxAttributes interface to provide options that the client can set.
SEED | integer attribute that is the random seed for the particle ID or other random attributes of the paricle source. |
USEPOL | integer setting that can be set on mesh particle sources. 0 generates particles on all vertices; 1 generates particles at polygon centers rather than on points; and 2 generates particles only at detached vertices. |
UPAXIS | integer channel which defines the up axis as 0, 1 or 2. This is used for point clouds to determine the best way to orient the particles. |
DEFORM | integer boolean that can be set on mesh particle sources. When true particles are on the deformed mesh, on the base mesh if not. |
SPACING | spacing is used to increase the number of particles at render time with the render multiplier |
(6) SDK: LXsPARTICLEATTR_SEED, etc. defines
#define LXsPARTICLEATTR_SEED "seed" #define LXsPARTICLEATTR_USEPOL "usePol" #define LXsPARTICLEATTR_UPAXIS "upAxis" #define LXsPARTICLEATTR_DEFORM "deform" #define LXsPARTICLEATTR_SPACING "spacing"
Particle features defined for vertices in the source. For legacy reasons these defines have a tableau prefix.
(7) SDK: LXiTBLX_PARTICLES, etc. defines
#define LXiTBLX_PARTICLES LXxID4('p','r','t','i') #define LXsTBLX_PARTICLE_POS "pos" #define LXsTBLX_PARTICLE_XFRM "xfrm" #define LXsTBLX_PARTICLE_ID "id" #define LXsTBLX_PARTICLE_SIZE "size" #define LXsTBLX_PARTICLE_VEL "vel" #define LXsTBLX_PARTICLE_MASS "mass" #define LXsTBLX_PARTICLE_FORCE "force" #define LXsTBLX_PARTICLE_AGE "age" #define LXsTBLX_PARTICLE_PATH "pathLength" #define LXsTBLX_PARTICLE_DISS "dissolve" #define LXsTBLX_PARTICLE_RATE "rate" #define LXsTBLX_PARTICLE_ITEM "item" #define LXsTBLX_PARTICLE_ANGVEL "angVel" #define LXsTBLX_PARTICLE_TORQUE "torque" #define LXsTBLX_PARTICLE_PPREV "posPrev" #define LXsTBLX_PARTICLE_LUM "lum" #define LXsTBLX_PARTICLE_RGB "rgb" #define LXiTBLX_COLLISION LXxID4('c','o','l','l') #define LXsTBLX_COLLISION_VAL "val" #define LXsTBLX_COLLISION_POS "pos" #define LXsTBLX_COLLISION_NORM "norm" #define LXsTBLX_COLLISION_COUNT "count" #define LXsTBLX_COLLISION_TIME "time"
The ReplicatorEnumerator interface allows traversal of the members of the particle item.
(8) SDK: LXu_REPLICATORENUMERATOR define
#define LXu_REPLICATORENUMERATOR "01EC90A9-924F-4475-BA6A-FFF8A2691CD5"
The Enumerate() method takes a visitor and calls its Evaluate() method for each replicant of a replicator. During each Evaluate() call, the visitor can get information about the member. If 'localXform' is true, then the transforms from the Replicator Item & Particle Source are *not* applied.
(9) SDK: ReplicatorEnumerator::Enumerate, etc.
LXxMETHOD( LxResult, Enumerate) ( LXtObjectID self, LXtObjectID visitor, LXtObjectID chan, int localXform); LXxMETHOD( LxResult, Item) ( LXtObjectID self, void **ppvObj); LXxMETHOD( void, Position) ( LXtObjectID self, LXtVector pos); LXxMETHOD( void, Orientation) ( LXtObjectID self, LXtMatrix mx); LXxMETHOD( float, Id) ( LXtObjectID self); LXxMETHOD( float, Dissolve) ( LXtObjectID self); LXxMETHOD (float, GroupId) ( LXtObjectID self);
The two Enum() methods correspond to the two ways of acquiring an enumerator. The channel-read object is not needed for enumerators allocated as part of a modifier.
(10) SDK: CLxUser_ReplicatorEnumerator::Enum method
LxResult Enum ( CLxImpl_AbstractVisitor *visitor, ILxUnknownID chan, bool localXform) { CLxInst_OneVisitor<CLxGenericVisitor> gv; gv.loc.vis = visitor; return Enumerate (gv, chan, localXform ? 1 : 0); } LxResult Enum ( CLxImpl_AbstractVisitor *visitor, bool localXform) { CLxInst_OneVisitor<CLxGenericVisitor> gv; gv.loc.vis = visitor; return Enumerate (gv, 0, localXform ? 1 : 0); } bool GetItem ( CLxLoc_Item &item) { LXtObjectID obj; if (LXx_OK (Item (&obj))) return item.take (obj); item.clear (); return false; }
Empty ReplicatorEnumerator Python user class.
(11) PY: empty ReplicatorEnumerator user class
pass
Particle Simulations
A particle simulation computes the state of a set of particles as they evolve over time. The state is affected by filter layers, which can create and destroy particles, and can alter their state vector.
Evaluation State
The particle evaluation frame object maintains the state of the particle simulation.
There can be any number of particles, but all of them contain the same information in the form of a vector of floats. This method gets the description of the complete vertex vector for the simulation.
(12) SDK: ParticleEvalFrame::VertexDescription
LXxMETHOD( LXtObjectID, VertexDescription) ( LXtObjectID self);
MaxCount() returns the number of particle slots, although not all will contain active particles. AliveCount() returns the number of active particles.
(13) SDK: ParticleEvalFrame::MaxCount, etc.
LXxMETHOD( unsigned, MaxCount) ( LXtObjectID self); LXxMETHOD( unsigned, AliveCount) ( LXtObjectID self);
AddParticle() adds a new particle with an initial state given by the vector. After this the tags can be set through the StringTag interface. The index of the particle is returned.
(14) SDK: ParticleEvalFrame::AddParticle
LXxMETHOD( LxResult, AddParticle) ( LXtObjectID self, const float *state, unsigned *index);
This changes an active particle into an inactive one. Inactive particle slots may be reused for new particles.
(15) SDK: ParticleEvalFrame::KillParticle
LXxMETHOD( LxResult, KillParticle) ( LXtObjectID self, unsigned index);
This method tests if a given particle is active.
(16) SDK: ParticleEvalFrame::IsAlive
LXxMETHOD( unsigned, IsAlive) ( LXtObjectID self, unsigned index);
The state vector for a particle can be read and set using these methods. For GetVector() the client has to pass a sufficient buffer to recieve the requested state vector.
(17) SDK: ParticleEvalFrame::GetVector, etc.
LXxMETHOD( LxResult, GetVector) ( LXtObjectID self, unsigned index, float *vector); LXxMETHOD( LxResult, SetVector) ( LXtObjectID self, unsigned index, const float *vector);
Particles can also be accessed in runs to improve performance. These methods return runs of 'alive' flags and vector values starting from particle index 'first' and containing 'count' particles.
(18) SDK: ParticleEvalFrame::AliveRun, etc.
LXxMETHOD( LxResult, AliveRun) ( LXtObjectID self, unsigned first, const unsigned **alive, unsigned *count); LXxMETHOD( LxResult, VectorRun) ( LXtObjectID self, unsigned first, float **values, unsigned *count);
The 'alive' run holds state bits for each particle. Newborn particles are alive+changed, just killed particles are dead+changed. The changed flag is cleared at the start of each timestep.
(19) SDK: LXfFRAME_ALIVE, etc. defines
#define LXfFRAME_ALIVE 0x01 #define LXfFRAME_CHANGED 0x02
Find nearest particles to the given 3D position. The maxDist is the range to search for particles, and can be -1 for searching everywhere. The maxCount is the maximum number to return, and the index and dist arrays should be at least that long. If you are giving an existing particle position you need to ask for one more particle than you want since the particle itself will be first.
(20) SDK: ParticleEvalFrame::Neighbors
LXxMETHOD( LxResult, Neighbors) ( LXtObjectID self, LXtFVector pos, double maxDist, int maxCount, unsigned *index, double *dist, unsigned *count);
(21) SDK: LXu_PARTICLEEVALFRAME define
#define LXu_PARTICLEEVALFRAME "1AC26263-A422-4B17-A929-2F616037754F"
(22) SDK: empty ParticleEvalFrame User Class
(23) PY: empty ParticleEvalFrame user class
pass
Particle Filters
Process layers are filters that are called in sequence to generate the simulation.
The filter provides a peek at the vertex features that it wants to read or write. For all operations on the particle state, these are the only features that the filter will see. The particle frame will remap the full state into the state requested by the filter.
If 'full' is null, then this should return the features required by this filter. If 'full' is non-null, then it contains the full set of features in the simulation and the filter can return a second vertex containing any of the features in the full set. Filters that don't want optional features can just return the same required vertex for both cases.
(24) SDK: ParticleFilter::Vertex
LXxMETHOD( LXtObjectID, Vertex) ( LXtObjectID self, LXtObjectID full);
A filter can have one of several types. These determine which processing method will be used for the filter. Additional flags can be set to select the stage of the process where the filter wants to run.
(25) SDK: ParticleFilter::Flags
LXxMETHOD( unsigned, Flags) ( LXtObjectID self);
(26) SDK: LXiPFILT_FRAME, etc. defines
#define LXiPFILT_FRAME 0x00 #define LXiPFILT_RUN 0x01 #define LXiPFILT_PARTICLE 0x02 #define LXiPFILT_NEW_PARTICLE 0x03 #define LXmPFILT_TYPE 0x0F #define LXfPFILT_PRESTAGE 0x10 #define LXfPFILT_DERIVSTAGE 0x20 #define LXfPFILT_POSTSTAGE 0x40 #define LXfPFILT_ALIVERUN 0x80
Initialize() is called at start of simulation and gets the frame object and start time.
(27) SDK: ParticleFilter::Initialize
LXxMETHOD( LxResult, Initialize) ( LXtObjectID self, LXtObjectID vertex, LXtObjectID frame, double time);
Step() is called for each new time step. The 'other' pointer is another instance of this same filters, and contains the evaluation state for this timestep.
(28) SDK: ParticleFilter::Step
LXxMETHOD( LxResult, Step) ( LXtObjectID self, LXtObjectID other, double dt);
Cleanup() is called when the simulation is complete.
(29) SDK: ParticleFilter::Cleanup
LXxMETHOD( void, Cleanup) ( LXtObjectID self);
Frame() is called for filters of type LXi_PFILT_FRAME and allows the filter to process the entire frame in any manner. This is the only way to add or remove particles, for example.
(30) SDK: ParticleFilter::Frame
LXxMETHOD( LxResult, Frame) ( LXtObjectID self, unsigned stage);
Run() is called for filters of type LXi_PFILT_RUN and allows the filter to process a run of particles starting at 'base' for 'count' particles. The values arrays that are passed in match the description of the requested vertex vector. Some of these particles may be dead, but it may be faster to process them than to test. The alive state can be read by setting the ALIVERUN flag.
(31) SDK: ParticleFilter::Run
LXxMETHOD( LxResult, Run) ( LXtObjectID self, unsigned stage, float **values, const unsigned *alive, unsigned base, unsigned count);
Particle() is called for filters of type LXi_PFILT_PARTICLE or LXi_PFILT_NEW_PARTICLE and allows the filter to process a single particle at a time. Again the vector passed matches the request by the filter. For the 'NEW' case this method is only called for particles added by a previous particle filter. This can return LXePARTICLE_KILL to kill the particle.
(32) SDK: ParticleFilter::Particle
LXxMETHOD( LxResult, Particle) ( LXtObjectID self, unsigned stage, float *vertex);
(33) SDK: LXu_PARTICLEFILTER, etc. defines
#define LXu_PARTICLEFILTER "04A3C0C5-1C5C-43F5-8559-ACF3BAE79C0B" #define LXsPKG_PFILT_CHANNEL "particleFilter.channel" #define LXsPFILT_ENABLECHANNEL "pfiltEnable" #define LXePARTICLE_KILL LXxGOODCODE(LXeSYS_PSYS,1)
(34) SDK: empty ParticleFilter User Class
Empty ParticleFilter Python user class.
(35) PY: empty ParticleFilter user class
pass
Particle Co-Operators
A particle co-operator is an item that can be linked to a particle operator. This allows it to be part of the process for a specific operator. Init and cleanup are called at the start and end of the sim.
(36) SDK: ParticleCoOperator::Initialize, etc.
LXxMETHOD( LxResult, Initialize) ( LXtObjectID self, LXtObjectID eval); LXxMETHOD( LxResult, Cleanup) ( LXtObjectID self);
The operator is called at the start of the step with the time, and for each particle.
(37) SDK: ParticleCoOperator::Step, etc.
LXxMETHOD( LxResult, Step) ( LXtObjectID self, double dT); LXxMETHOD( LxResult, Particle) ( LXtObjectID self);
(38) SDK: LXu_PARTICLECOOPERATOR, etc. defines
#define LXu_PARTICLECOOPERATOR "DFBCF67B-C327-496E-8A30-20B64C31A9BB" #define LXsPKG_PCOOP_CHANNEL "particleCoop.channel" #define LXsGRAPH_PARTICLEOP "particleOp"
(39) SDK: empty ParticleCoOperator User Class
Empty ParticleCoOperator Python user class.
(40) PY: empty ParticleCoOperator user class
pass
Point Cache
Other items can function as point caches. If they provide a PointCacheItem interface then the particle simulation can store its results into the cache. The cache item will typically also have a ParticleItem interface to allow the cache to be used.
Prepare | Allow the point cache to attach itself to an evaluation context by adding channels. |
Initialize | This is called to start writing frames into the cache. Any previous contents of the cache should be cleared. This is passed the vertex description for the particle features to be saved, and the attributes for reading added channels. It also gets the start time and sample rate. |
SaveFrame | This saves a single frame of particle data to the cache. The object is a particle object with a TableauSurface interface. |
Cleanup | This is called when writing data to the cache is complete. |
(41) SDK: PointCacheItem::Prepare, etc.
LXxMETHOD( LxResult, Prepare) ( LXtObjectID self, LXtObjectID eval, unsigned *index); LXxMETHOD( LxResult, Initialize) ( LXtObjectID self, LXtObjectID vdesc, LXtObjectID attr, unsigned index, double time, double sample); LXxMETHOD( LxResult, SaveFrame) ( LXtObjectID self, LXtObjectID pobj, double time); LXxMETHOD( LxResult, Cleanup) ( LXtObjectID self);
(42) SDK: LXu_POINTCACHEITEM, etc. defines
#define LXu_POINTCACHEITEM "10930C44-62D3-42D1-BD6B-8FE015D9C566" #define LXsGRAPH_POINTCACHE "pointCache"
(43) SDK: empty PointCacheItem User Class
Empty PointCacheItem Python user class.
(44) PY: empty PointCacheItem user class
pass
Simulation Item
(45) SDK: LXsITYPE_PSIM, etc. defines
#define LXsITYPE_PSIM "particleSim" #define LXsICHAN_PSIM_ENABLE "enable" #define LXsICHAN_PSIM_SAMPLES "samples" #define LXsICHAN_PSIM_ADDAGE "addAge" #define LXsICHAN_PSIM_ADDPATH "addPath" #define LXsICHAN_PSIM_STOREFORCE "storeForce" #define LXsICHAN_PSIM_STOREMASS "storeMass" #define LXsICHAN_PSIM_AGEKILL "ageKill" #define LXsICHAN_PSIM_AGEMAX "ageMax" #define LXsICHAN_PSIM_AGEEXTEND "ageExtend" #define LXsICHAN_PSIM_GRAVENABLE "gravEnable" #define LXsICHAN_PSIM_GRAVAXIS "gravAxis" #define LXsICHAN_PSIM_GRAVITY "gravity" #define LXsICHAN_PSIM_DRAGENABLE "dragEnable" #define LXsICHAN_PSIM_DRAG "drag" #define LXsICHAN_PSIM_COLOR "color" #define LXsICHAN_PSIM_TGROUP "tgroup" #define LXsICHAN_PSIM_PCOUNT "pCount" #define LXsICHAN_PSIM_NTHREAD "nThread" #define LXsICHAN_PSIM_CACHEMEM "cacheMem" #define LXsGRAPH_PSIM "particleSim"
Particle Service
The particle service provides methods for accessing particle-related information.
(46) SDK: LXa_PARTICLESERVICE, etc. defines
#define LXa_PARTICLESERVICE "particleservice" #define LXu_PARTICLESERVICE "34928BF8-3917-47EF-8350-DD3A3DDED7EE"
(47) SDK: ParticleService::ScriptQuery
LXxMETHOD( LxResult, ScriptQuery) ( LXtObjectID self, void **ppvObj);
Get an enumerator for a replicator. This requires a valid replicator item, and will need an evaluated channel-read object to enumerate.
(48) SDK: ParticleService::GetReplicatorEnumerator
LXxMETHOD( LxResult, GetReplicatorEnumerator) ( LXtObjectID self, LXtObjectID replicatorItem, void **ppvObj);
Enumerate the particle features added to an item. Any item can set the USEFEATURES server tag to allow features to be added. For each feature the visitor can read the ident string and channel offset.
(49) SDK: ParticleService::EnumParticleFeatures
LXxMETHOD( LxResult, EnumParticleFeatures) ( LXtObjectID self, LXtObjectID item, LXtObjectID visitor);
(50) SDK: ParticleService::FeatureIdent
LXxMETHOD( LxResult, FeatureIdent) ( LXtObjectID self, const char **ident);
(51) SDK: ParticleService::FeatureOffset
LXxMETHOD( LxResult, FeatureOffset) ( LXtObjectID self, unsigned *offset);
(52) SDK: CLxUser_ParticleService::EnumFeatures method
LxResult EnumFeatures ( ILxUnknownID item, CLxImpl_AbstractVisitor *visitor) { CLxInst_OneVisitor<CLxGenericVisitor> gv; gv.loc.vis = visitor; return EnumParticleFeatures (item, gv); }
If you have a triGroup object with positional data and other particle features, you can convert it to a particle object with this function.
(53) SDK: ParticleService::TriGroupToParticle
LXxMETHOD( LxResult, TriGroupToParticle) ( LXtObjectID self, LXtObjectID triGroup, void **ppvObj);
You can also blend two tri-groups representing particle state. Group 0 will be changed based on the amount, with 0.0 being no change and 1.0 being complete replacement.
(54) SDK: ParticleService::TriGroupBlend
LXxMETHOD( LxResult, TriGroupBlend) ( LXtObjectID self, LXtObjectID triGroup0, LXtObjectID triGroup1, double blend);
These methods are provided for accessing a replicator enumerator as part of a modifier. The channel-read object in this case should be NULL.
(55) SDK: ParticleService::EnumeratorPrepare, etc.
LXxMETHOD( LxResult, EnumeratorPrepare) ( LXtObjectID self, LXtObjectID eval, LXtObjectID replItem, unsigned *index); LXxMETHOD( LxResult, EnumeratorEvaluate) ( LXtObjectID self, LXtObjectID attr, unsigned index, void **ppvObj);
Get a particle object from any particle item. Takes an eval channel read object.
(56) SDK: ParticleService::ItemToParticle
LXxMETHOD( LxResult, ItemToParticle) ( LXtObjectID self, LXtObjectID item, LXtObjectID chanRead, void **ppvObj);
Empty particle service Python user class.
(57) PY: empty Service.Particle user class
pass