packLDrawModel.mjs 6.2 KB

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