packLDrawModel.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. /**
  2. * LDraw object packer
  3. *
  4. * Usage:
  5. *
  6. * - Download official parts library from LDraw.org and unzip in a directory (e.g. ldraw/)
  7. *
  8. * - Download your desired model file and place in the ldraw/models/ subfolder.
  9. *
  10. * - Place this script also in ldraw/
  11. *
  12. * - Issue command 'node packLDrawModel models/<modelFileName>'
  13. *
  14. * The packed object will be in ldraw/models/<modelFileName>_Packed.mpd and will contain all the object subtree as embedded files.
  15. *
  16. *
  17. */
  18. var ldrawPath = './';
  19. var materialsFileName = 'LDConfig.ldr';
  20. var fs = require( 'fs' );
  21. var path = require( 'path' );
  22. if ( process.argv.length !== 3 ) {
  23. console.log( "Usage: node packLDrawModel <modelFilePath>" );
  24. exit( 0 );
  25. }
  26. var fileName = process.argv[ 2 ];
  27. var materialsFilePath = path.join( ldrawPath, materialsFileName );
  28. console.log( 'Loading materials file "' + materialsFilePath + '"...' );
  29. var materialsContent = fs.readFileSync( materialsFilePath, { encoding: "utf8" } );
  30. console.log( 'Packing "' + fileName + '"...' );
  31. var objectsPaths = [];
  32. var objectsContents = [];
  33. var pathMap = {};
  34. var listOfNotFound = [];
  35. // Parse object tree
  36. parseObject( fileName, true );
  37. // Check if previously files not found are found now
  38. // (if so, probably they were already embedded)
  39. var someNotFound = false;
  40. for ( var i = 0; i < listOfNotFound.length; i ++ ) {
  41. if ( ! pathMap[ listOfNotFound[ i ] ] ) {
  42. someNotFound = true;
  43. console.log( 'Error: File object not found: "' + fileName + '".' );
  44. }
  45. }
  46. if ( someNotFound ) {
  47. console.log( "Some files were not found, aborting." );
  48. process.exit( - 1 );
  49. }
  50. // Obtain packed content
  51. var packedContent = materialsContent + "\n";
  52. for ( var i = objectsPaths.length - 1; i >= 0; i -- ) {
  53. packedContent += objectsContents[ i ];
  54. }
  55. packedContent += "\n";
  56. // Save output file
  57. var outPath = fileName + "_Packed.mpd";
  58. console.log( 'Writing "' + outPath + '"...' );
  59. fs.writeFileSync( outPath, packedContent );
  60. console.log( 'Done.' );
  61. //
  62. function parseObject( fileName, isRoot ) {
  63. // Returns the located path for fileName or null if not found
  64. console.log( 'Adding "' + fileName + '".' );
  65. var originalFileName = fileName;
  66. var prefix = "";
  67. var objectContent = null;
  68. for ( var attempt = 0; attempt < 2; attempt ++ ) {
  69. prefix = "";
  70. if ( attempt === 1 ) {
  71. fileName = fileName.toLowerCase();
  72. }
  73. if ( fileName.startsWith( '48/' ) ) {
  74. prefix = "p/";
  75. } else if ( fileName.startsWith( 's/' ) ) {
  76. prefix = "parts/";
  77. }
  78. var absoluteObjectPath = path.join( ldrawPath, fileName );
  79. try {
  80. objectContent = fs.readFileSync( absoluteObjectPath, { encoding: "utf8" } );
  81. break;
  82. } catch ( e ) {
  83. prefix = "parts/";
  84. absoluteObjectPath = path.join( ldrawPath, prefix, fileName );
  85. try {
  86. objectContent = fs.readFileSync( absoluteObjectPath, { encoding: "utf8" } );
  87. break;
  88. } catch ( e ) {
  89. prefix = "p/";
  90. absoluteObjectPath = path.join( ldrawPath, prefix, fileName );
  91. try {
  92. objectContent = fs.readFileSync( absoluteObjectPath, { encoding: "utf8" } );
  93. break;
  94. } catch ( e ) {
  95. try {
  96. prefix = "models/";
  97. absoluteObjectPath = path.join( ldrawPath, prefix, fileName );
  98. objectContent = fs.readFileSync( absoluteObjectPath, { encoding: "utf8" } );
  99. break;
  100. } catch ( e ) {
  101. if ( attempt === 1 ) {
  102. // The file has not been found, add to list of not found
  103. listOfNotFound.push( originalFileName );
  104. }
  105. }
  106. }
  107. }
  108. }
  109. }
  110. var objectPath = path.join( prefix, fileName ).trim().replace( /\\/g, '/' );
  111. if ( ! objectContent ) {
  112. // File was not found, but could be a referenced embedded file.
  113. return null;
  114. }
  115. if ( objectContent.indexOf( '\r\n' ) !== - 1 ) {
  116. // This is faster than String.split with regex that splits on both
  117. objectContent = objectContent.replace( /\r\n/g, '\n' );
  118. }
  119. var processedObjectContent = isRoot ? "" : "0 FILE " + objectPath + "\n";
  120. var lines = objectContent.split( "\n" );
  121. for ( var i = 0, n = lines.length; i < n; i ++ ) {
  122. var line = lines[ i ];
  123. var lineLength = line.length;
  124. // Skip spaces/tabs
  125. var charIndex = 0;
  126. while ( ( line.charAt( charIndex ) === ' ' || line.charAt( charIndex ) === '\t' ) && charIndex < lineLength ) {
  127. charIndex ++;
  128. }
  129. line = line.substring( charIndex );
  130. lineLength = line.length;
  131. charIndex = 0;
  132. if ( line.startsWith( '0 FILE ' ) ) {
  133. if ( i === 0 ) {
  134. // Ignore first line FILE meta directive
  135. continue;
  136. }
  137. // Embedded object was found, add to path map
  138. var subobjectFileName = line.substring( charIndex ).trim().replace( /\\/g, '/' );
  139. if ( subobjectFileName ) {
  140. // Find name in path cache
  141. var subobjectPath = pathMap[ subobjectFileName ];
  142. if ( ! subobjectPath ) {
  143. pathMap[ subobjectFileName ] = subobjectFileName;
  144. }
  145. }
  146. }
  147. if ( line.startsWith( '1 ' ) ) {
  148. // Subobject, add it
  149. charIndex = 2;
  150. // Skip material, position and transform
  151. for ( var token = 0; token < 13 && charIndex < lineLength; token ++ ) {
  152. // Skip token
  153. while ( line.charAt( charIndex ) !== ' ' && line.charAt( charIndex ) !== '\t' && charIndex < lineLength ) {
  154. charIndex ++;
  155. }
  156. // Skip spaces/tabs
  157. while ( ( line.charAt( charIndex ) === ' ' || line.charAt( charIndex ) === '\t' ) && charIndex < lineLength ) {
  158. charIndex ++;
  159. }
  160. }
  161. var subobjectFileName = line.substring( charIndex ).trim().replace( /\\/g, '/' );
  162. if ( subobjectFileName ) {
  163. // Find name in path cache
  164. var subobjectPath = pathMap[ subobjectFileName ];
  165. if ( ! subobjectPath ) {
  166. // Add new object
  167. subobjectPath = parseObject( subobjectFileName );
  168. }
  169. pathMap[ subobjectFileName ] = subobjectPath ? subobjectPath : subobjectFileName;
  170. processedObjectContent += line.substring( 0, charIndex ) + pathMap[ subobjectFileName ] + "\n";
  171. }
  172. } else {
  173. processedObjectContent += line + "\n";
  174. }
  175. }
  176. if ( objectsPaths.indexOf( objectPath ) < 0 ) {
  177. objectsPaths.push( objectPath );
  178. objectsContents.push( processedObjectContent );
  179. }
  180. return objectPath;
  181. }