packLDrawModel.js 6.0 KB

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