FreezePFlow - Snapshot As Mesh |
This
MacroScript lets you create a Mesh Snapshot of a Particle Flow particle
system.
Currently, neither Mesher Compound Object nor Snapshot Utility can create an
Editable Mesh copy of a Particle Flow system.
Natural Language |
We
will create a MacroScript called FreezePFlow which will be located in the
"Particle Flow" Category. This will give the user the options to
put the script on a toolbar, QuadMenu, Menu or Shortcut for quick access.
The
MacroScript will require the user to select a single Particle Flow Source in
the scene first, then to call the MacroScript.
When
on a toolbar, FreezePFlow button will be disabled (grayed out) when no valid
Particle Flow Source has been selected. It will be only enabled when a
single Source is selected.
When
on a QuadMenu, the FreezePFlow item will be invisible when the selection is
not valid and will only appear when a single Particle Flow Source is
selected in the scene.
When
the user executes the script, the selected object will be assigned to a
local variable for easier access throughout the script.
A
new empty Editable mesh object should be created as the holder of the
snapshot. Its name will be based on the source's name with a suffix
"_Freeze" and a unique number
The
script should loop through all particles in the Particle Flow Source until
it encounters a particle with no shape (mesh) assigned. A
counter will be incremented to point at the current particle. A boolean
variable will flag the state of the loop. When true, the last particle has
been reached. When false, the loop goes on.
The
shape (which is a TriMesh value) of the current particle will be copied to a
local variable.
If
the shape is not a valid TriMesh, the loop is ready - the flag should be set
to true.
If
the shape is a valid TriMesh, a new Editable Mesh object should be
created to store the particle's geometry
The
TriMesh value of the particle shape should be copied to the new Mesh.
The
Transofrmation matrix of the particle should be copied to the Transformation
of the new Editable Mesh. This will place the newly created object exactly
at the position of the source particle and also keep the orientation and
scale identical.
As
a last step, the new mesh should be attached to the snapshot's mesh object
created in the beginning.
When
the loop is ready, the snapshot mesh should be updated to refresh all
caches.
A message with the number of particles copied will be sent to the Prompt line of 3ds max.
Complete Script |
macroScript
FreezePFlow
category:"Particle Flow"
tooltip:"Snapshot PFlow Particles as Mesh"
(
fn filter_one_pflow = (selection.count == 1 and
classof selection[1] == PF_Source)
on isEnabled return
filter_one_pflow()
on isVisible return
filter_one_pflow()
on Execute do
(
pick_pflow = selection[1]
holder_mesh = Editable_Mesh()
holder_mesh.name = uniquename (pick_pflow.name+"_Freeze")
with undo off
(
i = 0
finish = false
while not
finish do
(
i += 1
pick_pflow.particleIndex = i
current_mesh =
pick_pflow.particleShape
if
current_mesh == undefined then
finish = true
else
(
new_mesh = Editable_Mesh()
new_mesh.mesh =
current_mesh
new_mesh.transform =
pick_pflow.particleTM
attach holder_mesh
new_mesh
)
)
)
update holder_mesh
pushPrompt (i as string +"
Particle(s) Frozen!")
)
)
Step-By-Step Comments |
macroScript
FreezePFlow
category:"Particle Flow"
tooltip:"Snapshot PFlow Particles as Mesh"
(
The script starts with the keyword macroScript which defines a script that can be used as an Action Item on a Toolbar, Menu, QuadMenu or as a Shortcut.
It is followed by the name of the macroScript, in our case FreezePFlow.
A macroScript requires the name of the category it will appear in.
The optional Tooltip is the text displayed in the Customize... dialog and when the mouse rolls over the button on a Toolbar.
fn filter_one_pflow = (selection.count == 1 and classof selection[1] == PF_Source)
This line defines a local function which will be accessible only inside this macroScript.
It compares the number of selected objects with 1 and the class of the first selected object with the PF_Source class which is the class of the Particle Flow Source objects. Because of the logical AND, if any of the two comparisons returns false, the result of the function will be false. If both are true, the result of the function will be true. This function will be used to define the enabled and visible state of the script's ActionItem.
on isEnabled return filter_one_pflow()
The isEnabled handler is called every time a Toolbar button has to be updated by the 3ds max UI.
When it returns true, the button can be pressed by the user.
When it returns false, the button will be grayed out .and cannot be pressed.
We will call the function defined above to decide when to enable the button.
At the end, the button will only be accessible if a single object is selected in the scene and this object is a Particle Flow Source.
on isVisible return filter_one_pflow()
The isVisible handler is called every time a QuadMenu Item has to be updated by the 3ds max UI.
When it returns true, the item appears on the QuadMenu.
When it returns false, the item does not appear at all.
We will call the same function defined above to decide when to make the item visible.
At the end, the item will only appear on the QuadMenu if a single object is selected in the scene and this object is a Particle Flow Source.
on Execute do
(
The Execute handler is called whenever the user presses the button, selects the Menu Item or presses the keyboard shortcut the macroScript is assigned to. It contains the body of the script.
pick_pflow = selection[1]
The pick_pflow is a local variable for the macroScript. (Variables are implicitly local in MAXScript).
We will store the selected Particle Flow Source object in this variable for easier access in the rest of the script.
holder_mesh = Editable_Mesh()
We will create a local variable called holder_mesh and assign it a new empty Editable Mesh object as value.
This Editable Mesh has no vertices or faces yet and will be used as the holder for the complete snapshot of the particle system.
holder_mesh.name = uniquename (pick_pflow.name+"_Freeze")
The name of the snapshot mesh will be based on the original name of the Particle Flow source plus the suffix "_Freeze" and a unique number added automatically by MAXScript (for the cases where multiple snapshots of the same particle system have been created).
with undo off
(
This disables the automatic undo record creation of 3ds max to speed things up.
i = 0
The i variable will be used as an index to the current particle.
We initialize it to 0 and will increment it for each particle we process.
finish = false
The finish variable will be used as a flag to tell the script whether there are more particles to process.
It is initialized to false (not finished yet) and will be set to true when the last particle has been reached.
while not finish do
(
The while loop will repeat the following code inside the brackets for each particle.
It will continue until the finish variable becomes true.
While expects a true value to keep going.
Since finish is set to false in the beginning, we use a logical NOT to turn the false to true to keep the loop running.
i += 1
Now we increase the particle index by 1.
pick_pflow.particleIndex = i
Using the particle index variable stored in i, we set the current particle in the Particle Flow Source to that index.
Particle Flow lets you perform operations on particles by first setting the current particle index.
Any following particle property get/set operations will be performed with the particle whose index was set by .particleIndex.
Note that .particleIndex is actually a property of the ParticleObjectExt Interface exposed by the Particle Flow Source Class, but we don't need to explicitly mention the Interface. The above could be written as pick_pflow.ParticleObjectExt.particleIndex = i, but it would just make the script too crowded.
current_mesh = pick_pflow.particleShape
Using another property of the ParticleObjectExt interface, .particleShape, we get the shape of the current (i-th) particle.
The .particleShape property returns a TriMesh value which is the internal representation of a mesh in 3ds max.
A TriMesh value contains all vertex, face etc. arrays defining a mesh, but the TriMesh value is not an object and cannot be seen in the viewport unless assigned to a holder object like an Editable_Mesh object or a Particle Flow Particle. It is great for copying geometry around without polluting the scene with temporary objects.
if current_mesh == undefined then
After getting the particle shape, we have to check we actually got a valid value. A particle that is not active in the scene will have no shape assigned. When we encounter such a particle, we will set the finish variable to true to break the loop and finish the script execution (see next line).
finish = true
As described above, if the particle has no valid mesh assigned, we set the finish variable to true to stop the script.
else
(
The following block in the brackets will be executed if the current_mesh variable actually received a valid TriMesh value.
new_mesh = Editable_Mesh()
We will need a temporary Editable Mesh object in the scene to prepare the current particle's mesh. At first, it will have no vertices or faces and will be located at the origin of the world with zero position and rotation values and default 100% scaling.
new_mesh.mesh = current_mesh
Now we assign the TriMesh value of the current particle to the mesh property of the Editable Mesh. This effectively copies the complete mesh definition of the current particle to the temporary Editable Mes.
new_mesh.transform = pick_pflow.particleTM
Next we get the transformation matrix of the current particle which defines the position, rotation and scale of the particle, and copy the result to the transformation property of the temporary Editable Mesh containing the particle mesh value. In result, the Editable Mesh is moved, rotated and scaled exactly as the current particle.
attach holder_mesh new_mesh
Finally, we attach the temporary mesh object to the first Editable Mesh we created. This adds the current particle's mesh to the snapshot object.
)
This is the end of the else block.
)
This is the end of the while loop.
)
This is the end of the undo disabling context.
update holder_mesh
After the script has copied all particles to the snapshot object, we update all its cashes to ensure correct display in the scene.
pushPrompt (i as string +" Particle(s) Frozen!")
And at the end, we push a new message to the 3ds max Prompt line. The text will contain the final value of the particle index variable to tell the user how many particles have been processed.
)
This is the end of the execute handler.
)
And this is the end of the macroScript.
Copyright © 2003 by Borislav 'Bobo' Petrov. |