/** * @author yomboprime / https://github.com/yomboprime/ * * LDraw object packer * * Usage: * * - Download official parts library from LDraw.org and unzip in a directory (e.g. ldraw/) * * - Download your desired model file and place in the ldraw/models/ subfolder. * * - Place this script also in ldraw/ * * - Issue command 'node packLDrawModel models/' * * The packed object will be in ldraw/models/_Packed.mpd and will contain all the object subtree as embedded files. * * */ var ldrawPath = './'; var materialsFileName = 'LDConfig.ldr'; var fs = require( 'fs' ); var path = require( 'path' ); if ( process.argv.length !== 3 ) { console.log( "Usage: node packLDrawModel " ); exit( 0 ); } var fileName = process.argv[ 2 ]; var materialsFilePath = path.join( ldrawPath, materialsFileName ); console.log( 'Loading materials file "' + materialsFilePath + '"...' ); var materialsContent = fs.readFileSync( materialsFilePath, { encoding: "utf8" } ); console.log( 'Packing "' + fileName + '"...' ); var objectsPaths = []; var objectsContents = []; var pathMap = {}; var listOfNotFound = []; // Parse object tree parseObject( fileName, true ); // Check if previously files not found are found now // (if so, probably they were already embedded) var someNotFound = false; for ( var i = 0; i < listOfNotFound.length; i ++ ) { if ( ! pathMap[ listOfNotFound[ i ] ] ) { someNotFound = true; console.log( 'Error: File object not found: "' + fileName + '".' ); } } if ( someNotFound ) { console.log( "Some files were not found, aborting." ); process.exit( - 1 ); } // Obtain packed content var packedContent = materialsContent + "\n"; for ( var i = objectsPaths.length - 1; i >= 0; i -- ) { packedContent += objectsContents[ i ]; } packedContent += "\n"; // Save output file var outPath = fileName + "_Packed.mpd"; console.log( 'Writing "' + outPath + '"...' ); fs.writeFileSync( outPath, packedContent ); console.log( 'Done.' ); // function parseObject( fileName, isRoot ) { // Returns the located path for fileName or null if not found console.log( 'Adding "' + fileName + '".' ); var originalFileName = fileName; var prefix = ""; var objectContent = null; for ( var attempt = 0; attempt < 2; attempt ++ ) { prefix = ""; if ( attempt === 1 ) { fileName = fileName.toLowerCase(); } if ( fileName.startsWith( '48/' ) ) { prefix = "p/"; } else if ( fileName.startsWith( 's/' ) ) { prefix = "parts/"; } var absoluteObjectPath = path.join( ldrawPath, fileName ); try { objectContent = fs.readFileSync( absoluteObjectPath, { encoding: "utf8" } ); break; } catch ( e ) { prefix = "parts/"; absoluteObjectPath = path.join( ldrawPath, prefix, fileName ); try { objectContent = fs.readFileSync( absoluteObjectPath, { encoding: "utf8" } ); break; } catch ( e ) { prefix = "p/"; absoluteObjectPath = path.join( ldrawPath, prefix, fileName ); try { objectContent = fs.readFileSync( absoluteObjectPath, { encoding: "utf8" } ); break; } catch ( e ) { try { prefix = "models/"; absoluteObjectPath = path.join( ldrawPath, prefix, fileName ); objectContent = fs.readFileSync( absoluteObjectPath, { encoding: "utf8" } ); break; } catch ( e ) { if ( attempt === 1 ) { // The file has not been found, add to list of not found listOfNotFound.push( originalFileName ); } } } } } } var objectPath = path.join( prefix, fileName ); if ( ! objectContent ) { // File was not found, but could be a referenced embedded file. return null; } if ( objectContent.indexOf( '\r\n' ) !== - 1 ) { // This is faster than String.split with regex that splits on both objectContent = objectContent.replace( /\r\n/g, '\n' ); } var processedObjectContent = isRoot ? "" : "0 FILE " + objectPath + "\n"; var lines = objectContent.split( "\n" ); for ( var i = 0, n = lines.length; i < n; i ++ ) { var line = lines[ i ]; var lineLength = line.length; // Skip spaces/tabs var charIndex = 0; while ( ( line.charAt( charIndex ) === ' ' || line.charAt( charIndex ) === '\t' ) && charIndex < lineLength ) { charIndex ++; } line = line.substring( charIndex ); lineLength = line.length; charIndex = 0; if ( line.startsWith( '0 FILE ' ) ) { if ( i === 0 ) { // Ignore first line FILE meta directive continue; } // Embedded object was found, add to path map var subobjectFileName = line.substring( charIndex ).trim().replace( "\\", "/" ); if ( subobjectFileName ) { // Find name in path cache var subobjectPath = pathMap[ subobjectFileName ]; if ( ! subobjectPath ) { pathMap[ subobjectFileName ] = subobjectFileName; } } } if ( line.startsWith( '1 ' ) ) { // Subobject, add it charIndex = 2; // Skip material, position and transform for ( var token = 0; token < 13 && charIndex < lineLength; token ++ ) { // Skip token while ( line.charAt( charIndex ) !== ' ' && line.charAt( charIndex ) !== '\t' && charIndex < lineLength ) { charIndex ++; } // Skip spaces/tabs while ( ( line.charAt( charIndex ) === ' ' || line.charAt( charIndex ) === '\t' ) && charIndex < lineLength ) { charIndex ++; } } var subobjectFileName = line.substring( charIndex ).trim().replace( "\\", "/" ); if ( subobjectFileName ) { // Find name in path cache var subobjectPath = pathMap[ subobjectFileName ]; if ( ! subobjectPath ) { // Add new object subobjectPath = parseObject( subobjectFileName ); } pathMap[ subobjectFileName ] = subobjectPath ? subobjectPath : subobjectFileName; processedObjectContent += line.substring( 0, charIndex ) + pathMap[ subobjectFileName ] + "\n"; } } else { processedObjectContent += line + "\n"; } } if ( objectsPaths.indexOf( objectPath ) < 0 ) { objectsPaths.push( objectPath ); objectsContents.push( processedObjectContent ); } return objectPath; }