packLDrawModel.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  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. }
  78. else if ( fileName.startsWith( 's/' ) ) {
  79. prefix = "parts/";
  80. }
  81. var absoluteObjectPath = path.join( ldrawPath, fileName );
  82. try {
  83. objectContent = fs.readFileSync( absoluteObjectPath, { encoding: "utf8" } );
  84. break;
  85. }
  86. catch ( e ) {
  87. prefix = "parts/";
  88. absoluteObjectPath = path.join( ldrawPath, prefix, fileName );
  89. try {
  90. objectContent = fs.readFileSync( absoluteObjectPath, { encoding: "utf8" } );
  91. break;
  92. }
  93. catch ( e ) {
  94. prefix = "p/";
  95. absoluteObjectPath = path.join( ldrawPath, prefix, fileName );
  96. try {
  97. objectContent = fs.readFileSync( absoluteObjectPath, { encoding: "utf8" } );
  98. break;
  99. }
  100. catch ( e ) {
  101. try {
  102. prefix = "models/";
  103. absoluteObjectPath = path.join( ldrawPath, prefix, fileName );
  104. objectContent = fs.readFileSync( absoluteObjectPath, { encoding: "utf8" } );
  105. break;
  106. }
  107. catch ( e ) {
  108. if ( attempt === 1 ) {
  109. // The file has not been found, add to list of not found
  110. listOfNotFound.push( originalFileName );
  111. }
  112. }
  113. }
  114. }
  115. }
  116. }
  117. var objectPath = path.join( prefix, fileName );
  118. if ( ! objectContent ) {
  119. // File was not found, but could be a referenced embedded file.
  120. return null;
  121. }
  122. if ( objectContent.indexOf( '\r\n' ) !== - 1 ) {
  123. // This is faster than String.split with regex that splits on both
  124. objectContent= objectContent.replace( /\r\n/g, '\n' );
  125. }
  126. var processedObjectContent = isRoot ? "" : "0 FILE " + objectPath + "\n";
  127. var lines = objectContent.split( "\n" );
  128. for ( var i = 0, n = lines.length; i < n; i ++ ) {
  129. var line = lines[ i ];
  130. var lineLength = line.length;
  131. // Skip spaces/tabs
  132. var charIndex = 0;
  133. while ( ( line.charAt( charIndex ) === ' ' || line.charAt( charIndex ) === '\t' ) && charIndex < lineLength ) {
  134. charIndex++;
  135. }
  136. line = line.substring( charIndex );
  137. lineLength = line.length;
  138. charIndex = 0;
  139. if ( line.startsWith( '0 FILE ') ) {
  140. if ( i === 0 ) {
  141. // Ignore first line FILE meta directive
  142. continue;
  143. }
  144. // Embedded object was found, add to path map
  145. var subobjectFileName = line.substring( charIndex ).trim().replace( "\\", "/" );
  146. if ( subobjectFileName ) {
  147. // Find name in path cache
  148. var subobjectPath = pathMap[ subobjectFileName ];
  149. if ( ! subobjectPath ) {
  150. pathMap[ subobjectFileName ] = subobjectFileName;
  151. }
  152. }
  153. }
  154. if ( line.startsWith( '1 ' ) ) {
  155. // Subobject, add it
  156. charIndex = 2;
  157. // Skip material, position and transform
  158. for ( var token = 0; token < 13 && charIndex < lineLength; token ++ ) {
  159. // Skip token
  160. while ( line.charAt( charIndex ) !== ' ' && line.charAt( charIndex ) !== '\t' && charIndex < lineLength ) {
  161. charIndex++;
  162. }
  163. // Skip spaces/tabs
  164. while ( ( line.charAt( charIndex ) === ' ' || line.charAt( charIndex ) === '\t' ) && charIndex < lineLength ) {
  165. charIndex++;
  166. }
  167. }
  168. var subobjectFileName = line.substring( charIndex ).trim().replace( "\\", "/" );
  169. if ( subobjectFileName ) {
  170. // Find name in path cache
  171. var subobjectPath = pathMap[ subobjectFileName ];
  172. if ( ! subobjectPath ) {
  173. // Add new object
  174. subobjectPath = parseObject( subobjectFileName );
  175. }
  176. pathMap[ subobjectFileName ] = subobjectPath ? subobjectPath : subobjectFileName;
  177. processedObjectContent += line.substring( 0, charIndex ) + pathMap[ subobjectFileName ] + "\n";
  178. }
  179. }
  180. else {
  181. processedObjectContent += line + "\n";
  182. }
  183. }
  184. if ( objectsPaths.indexOf( objectPath ) < 0 ) {
  185. objectsPaths.push( objectPath );
  186. objectsContents.push( processedObjectContent );
  187. }
  188. return objectPath;
  189. }