packLDrawModel.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  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. const ldrawPath = './';
  19. const materialsFileName = 'LDConfig.ldr';
  20. import fs from 'fs';
  21. import path from 'path';
  22. if ( process.argv.length !== 3 ) {
  23. console.log( 'Usage: node packLDrawModel <modelFilePath>' );
  24. process.exit( 0 );
  25. }
  26. const fileName = process.argv[ 2 ];
  27. const materialsFilePath = path.join( ldrawPath, materialsFileName );
  28. console.log( 'Loading materials file "' + materialsFilePath + '"...' );
  29. const materialsContent = fs.readFileSync( materialsFilePath, { encoding: 'utf8' } );
  30. console.log( 'Packing "' + fileName + '"...' );
  31. const objectsPaths = [];
  32. const objectsContents = [];
  33. const pathMap = {};
  34. const 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. let someNotFound = false;
  40. for ( let 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. let packedContent = materialsContent + '\n';
  52. for ( let i = objectsPaths.length - 1; i >= 0; i -- ) {
  53. packedContent += objectsContents[ i ];
  54. }
  55. packedContent += '\n';
  56. // Save output file
  57. const 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. const originalFileName = fileName;
  66. let prefix = '';
  67. let objectContent = null;
  68. for ( let 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. let 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. const 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. let processedObjectContent = isRoot ? '' : '0 FILE ' + objectPath + '\n';
  120. const lines = objectContent.split( '\n' );
  121. for ( let i = 0, n = lines.length; i < n; i ++ ) {
  122. let line = lines[ i ];
  123. let lineLength = line.length;
  124. // Skip spaces/tabs
  125. let 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. const subobjectFileName = line.substring( charIndex ).trim().replace( /\\/g, '/' );
  139. if ( subobjectFileName ) {
  140. // Find name in path cache
  141. const 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 ( let 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. const subobjectFileName = line.substring( charIndex ).trim().replace( /\\/g, '/' );
  162. if ( subobjectFileName ) {
  163. // Find name in path cache
  164. let 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. }