PDA

View Full Version : Request: Item replacement LScript for Layout



BlueApple
07-17-2008, 09:39 AM
We have scene files containing lots of objects that need to be replaced with similarly named objects. Specifically, objects named my:Object in the scene editor need to be replaced with .lwos named my_Object.

Here is the pseudo code for what I'm looking for.

1. Start with first object in Scene Editor.
2. Save object name as a string.
3. Replace any colon (:) in the name's string with an underscore (_), and add .lwo to the end.
Ex: my:Object becomes my_Object.lwo.
4. Use the new object name string to search for a replacement object in the same directory.
Ex: If my_Object.lwo exists in the directory, then use it to replace the object named my:Object in the scene.
5. If more unchecked objects remain in the scene, repeat steps 1-4 on the next object.
Otherwise, stop.

I am just starting to work on this, but have never written an LScript before. If anyone has already written an object replacement script that I can cannibalize, or wants to point me in the right direction I'd appreciate it. Attached is a .zip containing sample objects and a scene that will show you what I am working with.

Thanks.

BlueApple
07-17-2008, 12:32 PM
Below is where I'm at with the script. I am trying to figure out the syntax for all of this still. I am familiar with ActionScript, but LScript is different in enough places that I am doing a little digging.

Also, I forgot to upload the files last time.

//BEGIN CODE

@version 2.3
@warnings
@script generic

generic
{
objList = curScene.getSelect(MESH);

//Creates a variable containing the object directory.
sObjectDirectory = getdir(OBJECTSDIR);

//Creates an array that will contain all of the objects in the scene.
aObjectsInScene = [];

//Go through all of the objects in the scene.
for(i=0; i<size(aObjectsInScene); i++){

//Create a variable for the object name as a string.
sCurrentObject = aObjectsInScene[i];

//Look at the object name and see if there are any colons (:).
for(k=0; k<size(sCurrentObject); k++){
//Creates a variable for the current character.
sCurrentCharacter = strsub(sCurrentObject, 1, k);
//If the current character is a colon, replace it with an underscore.
if(sCurrentCharacter == :){
//Replace any colon with an underscore (_).
strsub(sCurrentObject, 1, k) = "_";
}

//Add ".lwo" to the end of the string.
sCurrentObject += sCurrentObject+".lwo";

//Use the new object name string to search for a replacement object in the same directory.
//Ex: If my_Object.lwo exists in the directory, then use it to replace the object named my:Object in the scene.

}

}

//END CODE

If anyone might be able to assist, it'd be appreciated.

BlueApple
07-17-2008, 02:27 PM
To clarify what I would like to do:

I have a scene containing many, multi-layered objects. Each individual object needs to be replaced by a new single layered object.

So if there is a scene with a multi-layered object called Cube, and the layers are RedCube and BlueCube, the scene editor displays Cube:RedCube and Cube:BlueCube.

In my objects directory I have single-layered objects named Cube_RedCube and Cube_BlueCube. The layers are unnamed.

I would like a script that will swap out the individual objects from the multi-layered files with their single layer counterparts.

Lightwolf
07-17-2008, 02:31 PM
To clarify what I would like to do:

I have a scene containing many, multi-layered objects. Each individual object needs to be replaced by a new single layered object.

So if there is a scene with a multi-layered object called Cube, and the layers are RedCube and BlueCube, the scene editor displays Cube:RedCube and Cube:BlueCube.

In my objects directory I have single-layered objects named Cube_RedCube and Cube_BlueCube. The layers are unnamed.

I would like a script that will swap out the individual objects from the multi-layered files with their single layer counterparts.
You should be able to load the scene into a text editor and replace all occurances of ":" with "_", that should do the trick. (A colon shouldn't really show up anywhere else in a scene file... unless you are using the OSX CFM version, since HFS+ uses a colon to separate directories in a file path).

Cheers,
Mike

BlueApple
07-17-2008, 02:39 PM
I cracked open the attached (above) test scene and here is what the object load appears to be.

LoadObjectLayer 1 10000000 C:Documents and Settings/AMARTIN/Desktop/ObjectReplacementTest/Objects/Spheres.lwo

This looks like it loads layer 1 from Spheres.lwo. What I would like it to load instead is layer 1 from myYellowSphere.lwo. I don't think that this can be be accomplished by doing a search and replace for the ":" with "_".

adamredwoods
07-17-2008, 02:45 PM
Hi. Got your PM so here's what I can add. I'm not near a copy of Lightwave, so there will be mistakes.

First, use Mike Green's awesome Lscript HTML lookup table for help on specific commands:
http://www.mikegreen.name/Lscript/Lscript%20Index.html



My outstanding questions are:
1. how to acquire all objects in a scene.
2. how to replace an object with another.

1. How to acquire all objects in the scene:


obj = Mesh(); //get first object agent

// go through entire object mesh list
while(obj) {
///check for valid obj mesh
if(isMesh(obj.id))
...
do stuff
...
obj = obj.next();
}


The second one is difficult. Do we use obj replace or generic for the whole scene. Let me think...

adamredwoods
07-17-2008, 02:52 PM
Ok I re-read the problem. My brain is reaaaal rusty today. (beer)

in above, where there is "do stuff", we need to add code. Basically, we need to unparent if there's a parent.



if(obj.parent) {
ParentItem();
}



It may also be ParenItem(null); but I cant checkit right now.

Does that fulfill your requirements?

BlueApple
07-17-2008, 02:57 PM
Adam and Mike:
Thanks to both of you for your assistance. I'm working through what Adam has supplied and will update with success or failure.

adamredwoods
07-17-2008, 02:59 PM
I re-read again. Not so simple!


In my objects directory I have single-layered objects named Cube_RedCube and Cube_BlueCube. The layers are unnamed.

Well, when we unparent, we can add the name to our final filename:


if(obj.parent) {
finalName = obj.parent.name +"_" + obj.name;
ParentItem();
}


So now clarify, do we need to swap out just the name of the object (ie, rename object) or do we need to do an actual "load and replace object"?


ReplaceWithObject("folder/"+finalName);

Lightwolf
07-17-2008, 03:02 PM
I cracked open the attached (above) test scene and here is what the object load appears to be.

LoadObjectLayer 1 10000000 C:Documents and Settings/AMARTIN/Desktop/ObjectReplacementTest/Objects/Spheres.lwo

This looks like it loads layer 1 from Spheres.lwo. What I would like it to load instead is layer 1 from myYellowSphere.lwo. I don't think that this can be be accomplished by doing a search and replace for the ":" with "_".
Ah, darn it, the new scene file format.

Allright, I had a quick look at the LScript reference, LScript Reference, Chapters 4, 5 and 6 seem to be appropriate.

Something like this:


mesh = Mesh(); // get first mesh
while (mesh)
{
// check if the name contains a colon
objName = mesh.name;
// if it needs to be replaced
SelectItem mesh.id
ReplaceObjectLayer 1 newfilename
mesh = mesh.next();
}

This is just a rough skeleton, I'm not fluent in LScript to go into any more detail, sorry.

Cheers,
Mike

BlueApple
07-17-2008, 03:04 PM
We need to actually replace objects. If you look at the assets in the above ObjectReplacementTest.zip you may see what I'm trying to do.

My third post details what needs to happen. Basically, every object in a scene needs to be replaced. They must be replaced with single layer objects that are already residing inside of the Objects folder in the content directory.

Lightwolf
07-17-2008, 03:06 PM
My third post details what needs to happen. Basically, every object in a scene needs to be replaced. They must be replaced with single layer objects that are already residing inside of the Objects folder in the content directory.
You can use either the ReplaceObjectLayer or ReplaceWithObject command.
I'm not sure which is better in this case.

Cheer,s
Mike

BlueApple
07-17-2008, 03:12 PM
Mike,

True, ReplaceObjectLayer could work, it's just that this is something we need to do a lot with scenes containing hundreds of assets from different multi-layered objects. If this can be automated it'd be a big help. But I've already eaten up plenty of your time so if you want to move on to other things I won't be offended :)

Time to dig deeper into the documents...

adamredwoods
07-17-2008, 03:12 PM
@version 2.3
@warnings
@script generic

generic
{
obj = Mesh(); //get first object agent

// go through entire object mesh list
while(obj) {
///check for valid obj mesh
if(isMesh(obj.id)) {
if(obj.parent) {
finalName = obj.parent.name +"_" + obj.name;
ParentItem(); //de-parent to remove the colon
ReplaceWithObject(finalName); //replace object
}
}
obj = obj.next();
}
}

If replace with object isn't finding the filename, you may need to add the directory structure before it.

Lightwolf
07-17-2008, 03:18 PM
finalName = obj.parent.name +"_" + obj.name;


*puzzled* What are you getting the name of the parent item in the Layout hierarchy for?

Cheers,
Mike

BlueApple
07-17-2008, 03:18 PM
Adam, I ran your script and got these 2 errors:

Unresolved function reference: isMesh()
Line 12, unable to locate function reference isMesh

Any thoughts?

adamredwoods
07-17-2008, 03:21 PM
Doh!


@version 2.3
@warnings
@script generic

generic
{
obj = Mesh(); //get first object agent

// go through entire object mesh list
while(obj) {
///check for valid obj mesh
if( obj.isMesh() ) {
if(obj.parent) {
finalName = obj.parent.name +"_" + obj.name;
ParentItem(); //de-parent to remove the colon
ReplaceWithObject(finalName); //replace object
}
}
obj = obj.next();
}
}

obj.isMesh()

adamredwoods
07-17-2008, 03:23 PM
*puzzled* What are you getting the name of the parent item in the Layout hierarchy for?

Cheers,
Mike

To confuse you... ;P
No, b/c his files are named cube_myredcube

so if his object is cube:myredcube
break it apart and preserve the parent in the finalName.

Maybe.

Lightwolf
07-17-2008, 03:28 PM
To confuse you... ;P
No, b/c his files are named cube_myredcube

so if his object is cube:myredcube
break it apart and preserve the parent in the finalName.

I can't check at the moment, but afaik the layer/item name won't change if you change the parenting in layout.
You would have to get obj.name as a string, replace the colon, and then use that to load a replacement object.
If it is in a subdirectory it can be trickier, since you need to parse and split obj.filename first to extract the path and then append the new object name.

I wish I could help more, but by the time I go through the LScript I'd have written in twice in C++...

Cheers,
Mike

BlueApple
07-17-2008, 03:31 PM
Adam,
Not getting any errors now, however nothing is happening. I threw info(finalName); in there to see what finalName was returning but nothing pops up when the script is run.



@version 2.3
@warnings
@script generic

generic
{
obj = Mesh(); //get first object agent

// go through entire object mesh list
while(obj) {
///check for valid obj mesh
if( obj.isMesh() ) {
if(obj.parent) {
finalName = obj.parent.name +"_" + obj.name;
ParentItem(); //de-parent to remove the colon
ReplaceWithObject(finalName); //replace object
info(finalName);
}
}
obj = obj.next();
}
}

BlueApple
07-17-2008, 03:32 PM
And if I grab the directory (as shown below) how do I use it in conjunction with the object replacement?


sObjectDirectory = getdir(OBJECTSDIR);

adamredwoods
07-17-2008, 03:35 PM
I can't check at the moment, but afaik the layer/item name won't change if you change the parenting in layout.
You would have to get obj.name as a string, replace the colon, and then use that to load a replacement object.
If it is in a subdirectory it can be trickier, since you need to parse and split obj.filename first to extract the path and then append the new object name.

I wish I could help more, but by the time I go through the LScript I'd have written in twice in C++...

Cheers,
Mike

Hmmm. A colon in the filename? crud. I was hoping it was easier.

From the manual:

split
split() works specifically with file path/name character strings. It accepts a character string, and returns an array of four elements representing the drive, path, filename, and filename extension that could be derived from the string argument.

file = getfile(“Select A Scene...”,”*.lws”);
if(file == nil)
return;
if(fileexists(file))
{
base = split(file);
// get the components (base becomes an array)


Components of the string that do not exist (i.e., no drive designation) are returned as nil.

That's all i've got so far.

So if this is true, the amendment to the code would be to remove the ParentItem() and add the filename parsing to remove the ":" and add in a "_".

good luck!

adamredwoods
07-17-2008, 03:36 PM
Adam,
Not getting any errors now, however nothing is happening. I threw info(finalName); in there to see what finalName was returning but nothing pops up when the script is run.


get rid of the if(obj.parent)

Seems this has nothing to do with parenting.... my fault! ;D

adamredwoods
07-17-2008, 03:37 PM
And if I grab the directory (as shown below) how do I use it in conjunction with the object replacement?


sObjectDirectory = getdir(OBJECTSDIR);

finalName = sObjectDirectory + finalName;

BlueApple
07-17-2008, 04:46 PM
Below is where I'm at. I'm getting the "unterminated string literal" error, as I think the forward slash is causing the problem. Is that an escape character in LScript?


@version 2.3
@warnings
@script generic

generic
{
sObjectDirectory = getdir(OBJECTSDIR);
obj = Mesh(); //get first object agent

// go through entire object mesh list
while(obj) {

///check for valid obj mesh
if( obj.isMesh() ) {

sTempName = string(obj.name);
//finalName = obj.parent.name +"_" + obj.name;
//ParentItem(); //de-parent to remove the colon


//Go through each character and if it's a colon, change it to an underscore.
for(i=1; i<size(sTempName); i++){
if(":" == strsub(sTempName, i, 1)){
nStartA = 1;
nLengthA = -1+i;
nStartB = (i+1);
nLengthB = size(sTempName)-i;
sNameWithoutColon = strsub(sTempName, nStartA, nLengthA)+"_"+strsub(sTempName, nStartB, nLengthB);
sTempName = sNameWithoutColon+".lwo";
}
}
finalName = sObjectDirectory+"\"+sTempName;
info(finalName);
ReplaceWithObject(finalName); //replace object
}
obj = obj.next();
}
}

adamredwoods
07-17-2008, 07:09 PM
Quick thoughts:
try it without the "\", try it with two "\\", and finally check the manual or Mike Green's link above. That html is a quick way to search through lscript docs. real lifesaver.

BlueApple
07-17-2008, 10:18 PM
Adam,
Will check that out when I'm back in the salt mine in the morning.
Gracias.

Lightwolf
07-18-2008, 03:31 AM
One more thing. ReplaceObject is performed on the first selected item in Layout. So you need to select it first.


SelectItem(obj.id);

Cheers,
Mike

BlueApple
07-21-2008, 08:26 AM
Mike & Adam,

Thanks for your help; I think it's working now.

Using "\\" worked great. The only other ingredient missing was selecting each object. We were running through the whole list but the script never actually selected each subsequent mesh.

Below is the completed script.



@version 2.3
@warnings
@script generic

generic
{
//Creates a string variable for the directory
sObjectDirectory = getdir(OBJECTSDIR);
//Gets first object agent
obj = Mesh();
//Goes through entire object mesh list
while(obj) {
//Selects current obj
obj.select();
//Checks for valid obj mesh
if( obj.isMesh() ) {
//Creates a string variable for the current object
sTempName = string(obj.name);
//Goes through each character and if it's a colon, changes the colon to an underscore
for(i=1; i<size(sTempName); i++){
if(":" == strsub(sTempName, i, 1)){

//Creates 4 variables that will be used to create the new object string.
nStartA = 1;
nLengthA = -1+i;
nStartB = (i+1);
nLengthB = size(sTempName)-i;

sNameWithoutColon = strsub(sTempName, nStartA, nLengthA)+"_"+strsub(sTempName, nStartB, nLengthB);
sTempName = sNameWithoutColon+".lwo";
}
}
finalName = sObjectDirectory+"\\"+sTempName;
//Tests to see if the proper file name is being returned
info(finalName);
//Replaces object
ReplaceWithObject(finalName);
}
obj = obj.next();
}
}


Thanks again you two.

-Adam

Lightwolf
07-21-2008, 08:33 AM
Mike & Adam,

Thanks for your help; I think it's working now.
...
Thanks again you two.

Cool! ...and you're welcome.

To speed things up you might want to consider to only select and replace the mesh if you've actually found a ":" in the name.

Cheers,
Mike

BlueApple
07-21-2008, 09:50 AM
Mike,

I have added a check to see if the "_" is present; that is a useful check. The other situations I need to deal with are 1) instanced geometry and 2) replacement objects not being present in the object directory.

For instanced geometry, I want the user to manually replace items. For this I need to determine if an object is an instanced. Currently I am just checking to see if there is a "(" in the string. I think this should work.

Objects not being present in the directory is a different problem. Currently an error comes up every time an object is not found. It's very tedious to click OK on each error window.

Is there a way to suppress these warnings, and maybe give the user a single error message at the end stating that 1 or more objects were not replaced?


@version 2.3
@warnings
@script generic

generic
{
//Creates a string variable for the directory
sObjectDirectory = getdir(OBJECTSDIR);
//Gets first object agent
obj = Mesh();
//Goes through entire object mesh list
while(obj) {
//Selects current obj
obj.select();
//Checks for valid obj mesh
if( obj.isMesh() ) {
//Creates a string variable for the current object
sTempName = string(obj.name);
//Creates a variable that will be used to check whether or not a replacement is necessary
//It will be set to true only if an underscore was present in the name
bUnderscorePresent = false;
//Creates a variable that will track whether or not "(" is present in the name. "(" is used in instance object names
bIsAnInstance = false;
//Goes through each character and if it's a colon, changes the colon to an underscore
//Also looks for a "(" and if encountered, switches bIsAnInstance to true
for(i=1; i<size(sTempName); i++){
if(":" == strsub(sTempName, i, 1)){
bUnderscorePresent = true;
//Creates 4 variables that will be used to create the new object string
nStartA = 1;
nLengthA = -1+i;
nStartB = (i+1);
nLengthB = size(sTempName)-i;

sNameWithoutColon = strsub(sTempName, nStartA, nLengthA)+"_"+strsub(sTempName, nStartB, nLengthB);
sTempName = sNameWithoutColon+".lwo";
}
if("(" == strsub(sTempName, i, 1)){
bIsAnInstance = true;
}
}
finalName = sObjectDirectory+"\\"+sTempName;
//Checks to see if a repleacement is needed by verifying 1) that an underscore was present in the name...
if(bUnderscorePresent == true){
//...and that this was not an instanced object
if(bIsAnInstance == false){
//Replaces object
ReplaceWithObject(finalName);
}
}
}
obj = obj.next();
}
}

BlueApple
07-21-2008, 10:46 AM
I think that I need to change the alert level, right?

At the top of the script I'd like to get the user's current alert level and store it in a variable. Then, set the alert level to low. At the end of the script, the user's alert level can be reset.

What is the syntax to acquire the user's alert level? I've tried the below and they don't seem to work.



info(AlertLevel.());//(nil)
info(AlertLevel());//Invalid argument
info(AlertLevel);//(nil).());

Thoughts?

adamredwoods
07-21-2008, 12:07 PM
You didn't do a search on Mike Green's lscript site, did you? :(
http://www.mikegreen.name/Lscript/Lscript%20Index.html


alertlevel
Indicates the state of the Alert Level setting in Layout. Will be one of ALERT_BEGINNER, ALERT_INTERMEDIATE or ALERT_EXPERT.

The usage to set is: alertlevel("<level>");

adamredwoods
07-21-2008, 12:13 PM
Cool! ...and you're welcome.

To speed things up you might want to consider to only select and replace the mesh if you've actually found a ":" in the name.

Cheers,
Mike

Teamwork!

Group high-five. :)

Lightwolf
07-21-2008, 12:19 PM
Teamwork!
Oh no, I just make smart arse comments... :D

Cheers,
Mike

BlueApple
07-21-2008, 12:32 PM
I totally checked out the link to Mike Green's site. I did a find for "alert" and all I saw was the below:

AlertLevel("<level>");

Is there a better way to search M. Green's site besides the "Find" function in my browser?

This may indeed allow you to set the alert level, however I am unable to retrieve the user's current alert level. I tried the below methods and came up with nothing.


info(AlertLevel(<>));//error
info(AlertLevel.());//(nil)
info(AlertLevel());//Invalid argument
info(AlertLevel);//(nil)

What syntax would I use to retrieve the alert level currently used?

Lightwolf
07-21-2008, 12:38 PM
What syntax would I use to retrieve the alert level currently used?
Since commands can't return values, that won't work.

Scene().alertlevel should get you the value.

Well, rather something like:


sc = Scene();
info(sc.alertlevel);

Cheers,
Mike

BlueApple
07-21-2008, 12:49 PM
I tried all of the below methods to set the alert level and none of them work.


alertlevel("ALERT_EXPERT");//Unresolved function reference
AlertLevel("ALERT_EXPERT");//Invalid argument
AlertLevel("<ALERT_EXPERT>");//Invalid argument
AlertLevel("EXPERT");//Invalid argument
AlertLevel("High");//Invalid argument
AlertLevel("ALERT_BEGINNER");//Invalid argument
AlertLevel("BEGINNER");//Invalid argument
AlertLevel(ALERT_BEGINNER);//Invalid argument

Mike, your code worked perfectly. The value in the info box was a number and not a string. So I tried this:


AlertLevel(1);//This appears to work

Does AlertLevel actually accept strings, or is this outdated? I am currently using LW 9.3.

Lightwolf
07-21-2008, 12:52 PM
Does AlertLevel actually accept strings, or is this outdated? I am currently using LW 9.3.
I think it just accepts numbers.

In the C SDK they are defined as such:


#define LWALERT_BEGINNER 0
#define LWALERT_INTERMEDIATE 1
#define LWALERT_EXPERT 2


Cheers,
Mike

BlueApple
07-21-2008, 01:00 PM
Cool. Thanks again to both of you for all of your great assistance.
-Adam

adamredwoods
07-21-2008, 01:15 PM
I think it just accepts numbers.

In the C SDK they are defined as such:


#define LWALERT_BEGINNER 0
#define LWALERT_INTERMEDIATE 1
#define LWALERT_EXPERT 2


Cheers,
Mike

Yet another minus with Lscripting. I wonder if
AlertLevel(LWALERT_EXPERT);

would work?

BlueApple
07-21-2008, 01:23 PM
Adam,

That's an invalid argument as well. The numbers work great; less typing :)

Blochi
07-21-2008, 11:29 PM
err... yeah, Alertlevel is all messed up.
The number used to set it used to be offset from the number you get when you read it.

Here is a little snippet from one of my scripts, that forces beginners mode:



curScene = Scene();
alerts = curScene.alertlevel;
AlertLevel(1);

....do my thing in beginners mode, making sure info() and warning() are shown...

if(integer(hostBuild()) > 1140) AlertLevel(alerts);
else AlertLevel(alerts+1); // workaround for pre-9.2



If you want to suppress load and replace requesters, you would need this pair (inaccessible through the interface):



AutoConfirm(true);

.... do your thing....

AutoConfirm(false);



Make sure to catch all errors in the script, and reset Autoconfirm back even if you have to terminate early. Because otherwise it will stick, and even quitting Lightwave will happen without "Are you sure?" requester....

Blochi

BlueApple
07-22-2008, 08:20 AM
Gracias Blochi. I am writing this for LW 8.5 so the AlertLevel offset information is very useful.