[Closed] PFlow – reading user data channels from outside of PFlow – solved
Ok, after a lot of fighting and with a lot of help from users here, I’ve found a way to do that.
Just use the following code in you script where you want to read the data from PFlow:
-- insert a script operator into every event in PFlow. This script will write out user channel values to a global particle_user_data array
global particle_user_data=#()
undo "Pflow edit" on ( -- undo is necessary for Max to see the PFlow change
particleFlow.BeginEdit()
ms_op = Script_Operator name:(uniquename "EFXtemp_ms_op_")
ms_op.Proceed_Script="on ChannelsUsed pCont do
(
pCont.useInteger = true
pCont.useFloat = true
pCont.useVector = true
pCont.useMatrix = true
)
on Proceed pCont do
(
pnum=pCont.NumParticles()
for i in 1 to pnum do
(
pCont.particleIndex = i
particle_user_data[pCont.particleID]=#(pCont.particleInteger,pCont.particleFloat,pCont.particleVector,pCont.particleMatrix)
)
)"
for o in helpers where classof o == Event do (
if o.numactions()>0 AND classof (o.getAction 1)==Birth then o.insertAction (instance ms_op) 2 else o.insertAction (instance ms_op) 1
)
particleFlow.EndEdit()
)
-- parse through animation range reading Particle User data from the global particle_user_data array at each frame
start = (animationrange.start as integer)*framerate/4800
end = (animationrange.end as integer)*framerate/4800
pf=$ -- store the PFlow object you want to work with
for fr in start to end do (
slidertime=fr
completeRedraw() -- without it Max will sometimes seem to hang until the whole process is over
if pf.numParticles()>0 then for i in 1 to pf.numParticles() do (
pf.particleIndex=i
pid=pf.particleId
my_integer=particle_user_data[pid][1]
my_float=particle_user_data[pid][2]
my_vector=particle_user_data[pid][3]
my_TM=particle_user_data[pid][4]
-- do something with the data - remember it will be overwritten on the next frame with new data, so you have to copy it here to a new array or something
)
)
-- PFlow cleanup
delete $'EFXtemp_ms_op_*'
try particleFlow.delete $'Action Recovery' catch()
i didn’t check it for working but
(animationrange.end as integer)*framerate/4800 is same as animationrange.end.frame
…*framerate/4800 is the same as …/ticksperframe
you can do the same thing but much easier. You don’t need to collect array of user channel values for every particle (it kills memory pretty soon). You can just store ParticleContainer itself on Procced event.
Here is a scenario:
- create custom Attribute for the patricleFlow of type #MaxObject (lets say “container”).
- On Proceed event check NumParticles in current ParticleContainer and if the number more then ZERO assign it to “container” property.
- Get any data for any particle using MaxscriptParticleContainer interface.
Well this script stores user data for every particle but just on the current frame, on the next frame it’s ovewritten by new data so memory is not so much an issue.
Also with your idea I also have to add a script operator to every event, that would do what you suggested, because for some reason any access to user data from the global event resets the user data as I posted in another thread.
The third thing, are you absolutely sure that it would work with User data channels.
I’ve seen many people fighting with this and for some reason user data channels are accessible only from within script operators – that’s why I’ve had this long issue to find a way of accessing it from an external script
any massive array operations made with max script causes memory leaking. And it’s THE issue
you can add you script operator in any place and store ParticleContainer before global event resets the data
i’m absolutely sure that my code works for any particle data including custom channels
try(destroydialog testRol) catch()
global pcf
global pAmount = if pAmount == undefined then 100 else pAmount
rollout testRol "PF Update by denisT"
(
spinner sp "Amount: " range:[0,1000,pAmount] type:#integer fieldwidth:56
button create "Create PF" width:180 offset:[0,4]
fn script =
(
source = ""
source += "on ChannelsUsed pCont do (pCont.usePosition = pCont.useVector = on)
"
source += "on Proceed pCont do if pCont.NumParticles() > 0 do
"
source += "(
"
source += " (pCont.getParticleSystemNode()).container = pCont
"
source += " seed currenttime
"
source += " for i=1 to pCont.NumParticles() do
"
source += " (
"
source += " pCont.particleIndex = i
"
source += " pCont.particleVector = [i, pCont.particleID, random 0.0 1.0]
"
source += " )
"
source += ")
"
)
local op1
on create pressed do --undo "Create" on
(
try (delete objects) catch()
theHold.Begin()
pcf = PF_Source name:"Test" X_Coord:0 Y_Coord:0 Emitter_Length:10 Emitter_Width:10 \
Quantity_Viewport:100 Show_Logo:on Show_Emitter:on wirecolor:blue
custAttributes.add pcf \
(
attributes DTS_NodeAttributes (parameters extra (container type:#maxobject))
)
op1 = Birth name:"_birth" amount:pAmount
op2 = Position_Icon()
op3 = Speed()
op4 = ShapeStandard shape:2 size:0.8
op5 = DisplayParticles name:"_display" color:pcf.wireColor
op6 = RenderParticles()
ev1 = Event name:"_event"
ev1.SetPViewLocation (pcf.X_Coord) (pcf.Y_Coord+100)
op7 = Script_Operator name:"_operator"
op7.Proceed_Script = script()
ev1.AppendAction op1
ev1.AppendAction op2
ev1.AppendAction op3
ev1.AppendAction op4
ev1.AppendAction op5
pcf.AppendAction op6
pcf.AppendAction op7
pcf.AppendInitialActionList ev1
sp.value = pcf._event._birth.amount
select pcf
)
on sp changed val do if iskindof pcf PF_Source do
(
pcf._event._birth.amount = pAmount = sp.value
pcf.activateParticles on
)
)
createdialog testRol width:200 height:60
/*
slidertime = 20
pcf.container.NumParticles()
67
pcf.container.particleIndex = 10
10
pcf.container.particleVector
[10,10,0.861543]
pcf.container.particlePosition
[-0.227157,-3.37211,-172.727]
pcf.getParticlePosition 10
[-0.227157,-3.37211,-172.727]
*/