PDA

View Full Version : simple lscript homiletics



faulknermano
03-17-2003, 08:14 AM
Okay, I've seen a couple of situations where people want to do something to a bunch of objects. LS Commander, however, forces us to be explicit with our selections, making that script good for only one case. Here I'll demonstrate simply how to cycle through your selections.



generic
{
mySelection = Scene().getSelect();
}


Scene().getSelect() is an LScript command that retrieves the currently selected items and puts them in an array. Specifically, it retrieves an Object Agent, which is essentially a container full of stuff. Object Agents contain stuff like the name of the item, the item's id, and other attributes.



generic
{
mySelection = Scene().getSelect();
for(i=1 ; i<=mySelection.size() ; i++)
{

}
}


Here we have a for() loop. This is what we use to cycle through our selections. Inside the for() loop we have three 'arguments' separated by semi-colons (";"). The first argument tells that the variable i will be equals to one. The variable i is our counter, so we can cycle through mySelection. The second argument tells when our for loop keeps on going. In this case, as long as the variable i is less than, or equal to the size of mySelection (which is, obviously, how many items we have selected), LScript continues to loop. The third and last argument is what LScript does after each cycle. Here we increase the value of i by one, signifying that we have finished this particular item and wish to go to the next.



generic
{
mySelection = Scene().getSelect();
for(i=1 ; i<=mySelection.size() ; i++)
{
SelectItem(mySelection[i].id);
rx = random(0.0,360.0);
ry = random(0.0,360.0);
rz = random(0.0,360.0);
Rotation(rx,ry,rz);
}
}


So, now we execute SelectItem() and insert mySelection[i].id. The first thing you should always think of is what you are to place inside a particular function. In this case, SelectItem() accepts only strings or item ids. If you put something other than that, it will generate errors. Notice that I also put mySelection[i], that is, mySelection is an array, and the only way to access the individual Object Agents from that array is to enclose their respective indices with brackets.

In the following lines, I'm sure you can guess what the script is trying to do. You can find the random() function in the LScript docs. The Rotation() function, on the other hand, can accept a couple of types of arguments. But in this case I push in three arguments signifying the heading, pitch, and bank.

Mike_RB
03-17-2003, 10:03 AM
Thanks for the example code. Much appreciated.

Mike

faulknermano
03-25-2003, 07:23 AM
Here's another one: Layout Invert Selection.

Let's say you want something like Modeler's Invert Selection in Layout. You select one item you wish to exclude and select all of the items in Layout inversely. This is how we go about doing it.

We first want to get the item or items that are selected. So:



generic
{
s = Scene().getSelect();
}


Now, just like what we did previously: all items that are currently selectd are stored in the variable s. Also note: even if you only have one item selected it doesnt mean that selected is no longer an array; it is still one, though the size of it is just one element.

Now the flow of thinking is this: with all of the currently selected items recorded, execute a SelectAllObjects(), SelectAllLights(), SelectAllCameras(), or SelectAllBones(), depending on context, and then minus out the selection of the ones stored in the s array.



generic
{
s = Scene().getSelect();
myGenus = s[1].genus;
if(myGenus == MESH)
SelectAllObjects();
if(myGenus == LIGHT)
SelectAllLights();
if(myGenus == CAMERA)
SelectAllCamera();
if(myGenus == BONE)
SelectAllBones();

for(i=1;i<=s.size();i++)
RemoveFromSelection(s[i].id);
}


So, to get the genus, or type of item, we access the first element of the s array. Because LightWave can only select items of the same type at one given time, we can be sure that the first item selected is of the same type as everybody else in the array.

The for() loop, just like before, iterates through the s array, which contains the original selection, and one-by-one deselects them from the selection.

faulknermano
04-01-2003, 09:30 AM
How to access channels and how to manipulate them.

In this example, the function of the script is to mirror the selected item's position and rotation keyframes along the X axis. To keep the script simple, the script will only process one item.

as usual, the first step is to get the selected item.



generic
{
s = Scene().firstSelect();
}


Here, the firstSelect() method is used rather than getSelect().

The next thing is to get the channels of that.



generic
{
s = Scene().firstSelect();
chan = s.firstChannel();
}


The method firstChannel() get the first Channel Object Agent of that item. The variable chan now contains a Channel Object Agent. We have to process six channels in total:

1.) Position.X
2.) Position.Y
3.) Position.Z
4.) Rotation.H
5.) Rotation.P
6.) Rotation.B

Usually, all selectable Layout items have Position.X as their first channel. But for the sake of good programming practices, let us not assume this. What we need to do is cycle through all the channels that are contained in s, looking for these six channels.



generic
{
s = Scene().firstSelect();
chan = s.firstChannel();
while(chan)
{
if(chan.name == "Position.X")
...
if(chan.name == "Position.Y")
...
if(chan.name == "Position.Z")
...
if(chan.name == "Rotation.H")
...
if(chan.name == "Rotation.P")
...
if(chan.name == "Rotation.B")
...
chan = s.nextChannel();
}
}


I've left "..." where there should be some code. But for now, they'll be blank. As you can see, I've also put chan = s.nextChannel(). If you've cycled to the end of the list of channels, and that there are no more channels to be had, nextChannel() will output, or return, a nil. If you also notice, our loop is a while() loop, and its condition is that as long as the variable chan is NOT a nil, the loop will keep on looping. In other words, the loop will stop the moment chan becomes a nil, which will happen if there are no more channels to be processed.

So the question now is what do we do if chan.name == "Position.X"? We try to read the keys that are contained in that channel. As we read those keys, we modify them.



generic
{
s = Scene().firstSelect();
chan = s.firstChannel();
while(chan)
{
if(chan.name == "Position.X")
{
for(i=1;i<=chan.keyCount;i++)
{
myKey = chan.keys[i]; // gets key identifier
myKeyValue = chan.getKeyValue(myKey); // gets value
chan.setKeyValue(myKey,-myKeyValue); // modifies key value by negativ-izing it
}
}
if(chan.name == "Position.Y")
...
if(chan.name == "Position.Z")
...
if(chan.name == "Rotation.H")
...
if(chan.name == "Rotation.P")
...
if(chan.name == "Rotation.B")
...
chan = s.nextChannel();
}
}


We use a for() loop to cycle through all the keys that the channel has. If you look at the second argument to the for() loop you will see chan.keyCount. keyCount is a datamember denoting how many keys there are in the channel you are accessing the datamember from.

the keys[] array contains the key identifiers. So it is just a matter of picking off the one that you like. chan.keys[1] means the key identifier of the first key in chan. Now, the script gets the key value it immedidately applies a negative symbol to it, effectively 'mirroring' it. Then it applies that negativized value into the channel by the last line.

Now you can use this for() loop and simply paste it below the other channels. But to make the script more efficient and easier to read and thus debug later on, you can place the for() loop into a user-defined function (or UDF). Then you simply call the functions below the if() conditions so that it refers to the for() loop...



generic
{
s = Scene().firstSelect();
chan = s.firstChannel();
while(chan)
{
if(chan.name == "Position.X")
processChannel(chan);
if(chan.name == "Position.Y")
processChannel(chan);
if(chan.name == "Position.Z")
processChannel(chan);
if(chan.name == "Rotation.H")
processChannel(chan);
if(chan.name == "Rotation.P")
processChannel(chan);
if(chan.name == "Rotation.B")
processChannel(chan);
chan = s.nextChannel();
}
}
processChannel: chan
{
for(i=1;i<=chan.keyCount;i++)
{
myKey = chan.keys[i]; // gets key identifier
myKeyValue = chan.getKeyValue(myKey); // gets value
chan.setKeyValue(myKey,-myKeyValue); // modifies key value by negativ-izing it
}
}


When processChannel(chan) is called, it feeds the variable chan into the processChannel() UDF. In the actual UDF you will see processChannel: chan. This means that the function is ready to receive something, and this something will be called chan _within the scope of the UDF_. To clearly state the concept, you could write it this way as well:



processChannel: theThing
{
for(i=1;i<=theThing.keyCount;i++)
{
myKey = theThing.keys[i]; // gets key identifier
myKeyValue = theThing.getKeyValue(myKey); // gets value
theThing.setKeyValue(myKey,-myKeyValue); // modifies key value by negativ-izing it
}
}


I replaced chan with theThing, and replaced all the instances of chan within the array with theThing. In this case, theThing is a Channel Object Agent that's being fed to it by the generic() function above. It amounts to the same result. Again: processChannel(chan) calls the UDF while passing the variable chan, which is a Channel Object Agent. The UDF, in turn, accepts the variable, but uses a different name instead: theThing. Same contents, just a different name.

Lastly, we can even make the code shorter by using a string function called strleft(). As written in the documentation: "The strleft() function returns a portion of a character string beginning from the left. It accepts two parameters, where the first is the character string to be processed, and the second is a number of characters to extract." So, we can write the generic() function like so:



generic
{
s = Scene().firstSelect();
chan = s.firstChannel();
while(chan)
{
if(strleft(chan.name,8) == "Position")
processChannel(chan);
if(strleft(chan.name,8) == "Rotation")
processChannel(chan);
chan = s.nextChannel();
}
}