PDA

View Full Version : A script to extract every "Part" to it's own Layer



Audiodacious
03-01-2011, 05:02 PM
I'm still an LScript newbie but I'm also a C/C++ programmer and so the semantics aren't so hard. The lack of documentation however is lamentable! So I must give a big shout to Mike Green for his excellent LScript index:

http://www.mikegreen.name/Lscript/Lscript%20Index.html

I've recently been working with some models that have loads of Parts and Surfaces which need to be worked on in all sorts of different ways.

The models are imported from DXF or OBJ files and each surface loads into a layer of it's own. I sometimes end up with 100s of surfaces which slows Modeler down to a crawl. Many of the materials are duplicates. So I have a script which consolidates the Materials and creates a Part name for every layer. This makes it a bit easier to deal with the various sections of the model when needed. ( That script is still a bit flaky so I'm not posting it yet ! ).

The bigger problem was the inverse. When I found myself needing to extract all the parts in an object to their own layers. As others have found, there's no way in LScript to get the Part name from a poly.

There are several scripts around that extract parts from an LWO file and I want to thank those authors for the inspiration it gave me to write my own.
As a games programmer I know my way around the interior of an LWO file so as part of this script I created a simple function that seeks to the start of any chunk.

As it could be really useful, I'd like to share it to help pay back all those who do the same so us newbs can get off the ground...

So here it is, already I can see a couple of ways of improving it but hey!
Use it at your own risk, copy and paste and improve as you see fit...



// ================================================== ================================================== ===============
// Parts2Layers
// Author: Andy Bull March 1st 2011
// No Copyright: free to copy.
// ================================================== ================================================== ===============
//
// Creates a layer for each "Part" in the current object in the modeller. ( undoable ).
// Renames layers to the part names.
//
// --------------------------------------------------------------------------------------------------------------------
// What a pain we can't get the part names easily in LScript!
//
// But with a lot of effort it is possible ...
//
// I've seen several scripts that extract part names from an LWO file but most seem to make unsafe assumptions
// about the layout the file. In reality about the only thing you can rely on is that the names will be in the TAGS chunk.
// So here I've tried to be a little bit more reliable when parsing and extracting part names from an object file.
//
// This script reads a list of part names from the file of the current object in the modeller.
// It assumes the object has been saved or loaded from a file, so that's an important caveat.
// ( it won't work on an object that's never been saved! )
//
// The part names are read from TAGS chunk and to save parsing the entire LWO file properly this script reads ALL
// the names from the TAGS chunk and then verifies a part's existance by attempting to select it.
// If the selection contains polys the part must exist! ( It's a cheat and quite slow but it should work ok ).
// --------------------------------------------------------------------------------------------------------------------

@version 2.2
@script modeler
@warnings
@name Parts2Layers

// ================================================== ================================================== ===============
main
{
// Set selection mode and clear any selected polys.
selmode( USER );
selpolygon( CLEAR );

// check for geometry and abort if none.
editbegin();
if ( polygons == nil )
error( "duh?" );
editend();

// Get the current modeller mesh
partsobj = Mesh();

// Check the mesh filename, abort if not LWO
fileName = partsobj.filename;
if ( fileName == nil || strupper(fileName).contains( "LWO" ) == false )
error( "The object file is not an LWO file, save the model and try again." );

// Open the object's file for reading in binary mode.
file = File( fileName , "rb");
if ( file == nil )
error( "ERROR: Cannot open the file." );

// We can pretty well assume it's an LWO FORM so skip the header
// and point to the start of the first sub-chunk in the file.
file.offset( 12, FROMSTART );

// Find the TAGS chunk
chunkID[1] = 'T';
chunkID[2] = 'A';
chunkID[3] = 'G';
chunkID[4] = 'S';
chunkSize = SkipToChunk( file,chunkID );

// Read the list of names from the chunk data as a comma separated string.
tmpStr = "";
for ( i = 1; i <= chunkSize; i++ )
{
c = file.readByte();
if ( c )
tmpStr += c.asAsc();
else
tmpStr += ",";
}

// Close the file
file.close();

if ( chunkSize == 0 )
error ( "FAILED TO FIND CHUNK: ", chunkID[1].asAsc(), chunkID[2].asAsc(), chunkID[3].asAsc(), chunkID[4].asAsc() );

// Convert the long string into an array
tagNames = parse( ",", tmpStr );
tagSize = size( tagNames );

// Loop for each name in the TAGS list
// And cut and paste from layer 1 to new layer
cancelled = false;
undogroupbegin();

// We require all geometry to be in layer 1
usedLayers = lyrdata();
if ( usedLayers.count() > 1 )
{
// Cut and paste to layer 1 ( still in USER mode here ).
lyrsetfg( usedLayers );
cut();
lyrsetfg( 1 );
paste();
}

// Set direct selection mode because we don't
// want LW reporting the unselected polygon count.
selmode( DIRECT );

// show a progress indicator
moninit( tagSize, "Processing..." );

// now extract each part into it's own layer
for( i = 1; i <= tagSize; i++ )
{
// Set layer 1
lyrsetfg( 1 );

// try to select the part
selpolygon( SET, PART, tagNames[i] );

// If there's a selection it's a valid part.
editbegin();
polycount = polygons ? true : false;
editend();

if ( polycount )
{
cut();
emptyLayers = lyrempty();
lyrsetfg( emptyLayers[1] );
paste();
setlayername( tagNames[i] );
}

// Clear the selection ready for the next iteration
selpolygon( CLEAR );

if ( monstep( 1 ) == true )
{
i = tagSize + 1;
cancelled = true;
}
}
monend();
undogroupend();

// Reset foreground layers or undo if cancelled.
if ( cancelled )
{
undo();
lyrsetfg( 1 );
}
else
{
usedLayers = lyrdata();
lyrsetfg( usedLayers );
}
}
// ================================================== ================================================== ===============
// Function:
// Starts at the current file position and skips over chunks until it finds the specified chunk ID.
// Assumes the file is currently pointing to a chunk ID. ( The first chunk ID is at position 12 in the file ).
//
// If the chunk is found the file position will be left at the first byte of data in the chunk.
//
// Returns: The length of the chunk or zero if not found.
//
SkipToChunk:file,chunkID
{
while ( file.eof() == false )
{
// Get chunk ID
c1 = file.readByte();
c2 = file.readByte();
c3 = file.readByte();
c4 = file.readByte();

// Get chunk size
// I can't see how to read an "unsigned" int with file.readInt() so I'm reading each
// byte of the offset individually in order to do the LSB to MSB swaps...
cs1 = file.readByte();
cs2 = file.readByte();
cs3 = file.readByte();
cs4 = file.readByte();
chunkSize = cs1.asInt() << 24 | cs2.asInt() << 16 | cs3.asInt() << 8 | cs4.asInt();

// return if chunk found...
if ( c1 == chunkID[1] && c2 == chunkID[2] && c3 == chunkID[3] && c4 == chunkID[4] )
{
return chunkSize;
}
else
if ( file.eof() == false )
{
// Skip "chunklength" bytes to reach the next chunk..

file.offset( chunkSize, FROMHERE );
}
}

// not found
return 0;
}

papou
03-03-2011, 04:28 AM
thank you very much.
I will try this soon. It will help for the current job.

-EsHrA-
03-03-2011, 08:34 AM
hmm.. excellent! cheers!

papou - nice avatar you got there :)


mlon

papou
03-03-2011, 04:13 PM
Audiodacious, it was possible to use the modeler command "Select Entire Part" to select a part from a poly.
cmdseq("Select Entire Part");
This way you don't have to save the mesh first.

This is a modified script based on Distribute.ls from Fujio Ichikawa.



//======================================
var scriptName = "Distribute v0.1.5";
var author = "F.Ichikawa & Tais";
var contact = "[email protected]";
//======================================

@version 2.2
@warnings
@script modeler
@strict

var setPiv = false;
var mode = 1;

main
{

reqbegin(scriptName);
var c0 = ctlchoice("",mode,@"Connected Polys","Parts"@);
ctlposition(c0,10,4,240,19);
var c1 = ctlcheckbox("Set Pivot to Bounding Center",setPiv);
ctlposition(c1,16,30,234,19);
if(!reqpost()) return;

selmode(USER);
selpolygon(CLEAR);
var pc;
(pc) = polycount();
if(pc == 0) return;

setPiv = getvalue(c1);
mode = getvalue(c0);
reqend();
var fg = lyrfg(); fg.sortA();
cut();
lyrsetfg(fg[1]);
paste();
moninit(pc);
while(1){
var empty;
empty = lyrempty();
if(empty == nil){
error("Out of empty layers.");
}
empty.sortA();
(pc) = polycount();
if(pc == 0) break;
selpolygon(SET,POLYNDX,1);

if (mode==2)
cmdseq("Select Entire Part");
else
selpolygon(SET,CONNECT);

var sc;
(sc) = polycount();
monstep(sc);
if(pc == sc){
selpolygon(CLEAR);
SetPivot2BoundingCenter();
break;
}
cut();
lyrsetfg(empty[1]);
paste();
SetPivot2BoundingCenter();
lyrsetfg(fg[1]);
}
monend();
}

SetPivot2BoundingCenter
{
if(!setPiv) return;
var bbl,bbh;
(bbl,bbh) = boundingbox();
setpivot(center(bbl,bbh));
}





hey -EsHrA-, i like your jittering actor more! haha

Audiodacious
03-03-2011, 09:30 PM
Audiodacious, it was possible to use the modeler command "Select Entire Part" to select a part from a poly.
cmdseq("Select Entire Part");
This way you don't have to save the mesh first.



...

Thanks papou :thumbsup:

Actually I did miss that, I can see how it would work and it's a good trick. Problem is you still can't get the Part name, or can you ?

It was/is important for my task to be able to name the layers with the part name as it's much easier to see which part went to each layer then.

But cases where the name doesn't matter I think the Select Entire Part trick will probably work a bit faster.

:)