@@ -140,7 +140,7 @@ class JsPolyHavenAPILoader {
140140 throw new Error ( `Failed to download texture from ${ url } ` ) ;
141141 }
142142 else {
143- console . log ( `> Successfully downloaded texture from ${ url } ` ) ;
143+ // console.log(`> Successfully downloaded texture from ${url}`);
144144 }
145145
146146 return await response . blob ( ) ;
@@ -176,93 +176,87 @@ class JsPolyHavenAPILoader {
176176 * Create a complete MaterialX package with all textures
177177 * @param material Material object
178178 * @param resolution Resolution (1k, 2k, 4k, 8k)
179+ * @param preFetchedData Data if already fetched.
179180 * @returns ZIP file blob containing the complete package
180181 */
181- async createMaterialXPackage ( material , resolution ) {
182- try {
183- // Fetch MaterialX files data
184- const filesData = await this . fetchMaterialFiles ( material . id ) ;
185- const mtlxData = filesData . mtlx ?. [ resolution ] ?. mtlx ;
186- console . log ( '> createMaterialXPackage - fetched MaterialX data:' , mtlxData ) ;
182+ async createMaterialXPackage ( material , resolution , preFetchedData = null ) {
183+
184+ async function downloadWithConcurrency ( tasks , concurrency = 3 ) {
185+ const results = [ ] ;
186+ const queue = tasks . slice ( ) ;
187+ async function worker ( ) {
188+ while ( queue . length ) {
189+ const task = queue . shift ( ) ;
190+ results . push ( await task ( ) ) ;
191+ }
192+ }
193+ await Promise . all ( Array ( concurrency ) . fill ( ) . map ( worker ) ) ;
194+ return results ;
195+ }
187196
188- if ( ! mtlxData ) {
189- throw new Error ( `No MaterialX files found for ${ resolution } resolution` ) ;
197+ async function blobToUint8Array ( blob ) {
198+ return new Uint8Array ( await blob . arrayBuffer ( ) ) ;
199+ }
200+
201+ try {
202+ let filesData , mtlxData , mtlxContent , textureFiles ;
203+ if ( preFetchedData ) {
204+ mtlxContent = preFetchedData . mtlxContent ;
205+ textureFiles = preFetchedData . textureFiles ;
206+ } else {
207+ filesData = await this . fetchMaterialFiles ( material . id ) ;
208+ mtlxData = filesData . mtlx ?. [ resolution ] ?. mtlx ;
209+ if ( ! mtlxData ) throw new Error ( `No MaterialX files for ${ resolution } ` ) ;
210+ mtlxContent = await this . downloadMaterialXContent ( mtlxData . url ) ;
211+ textureFiles = mtlxData . include || { } ;
190212 }
191213
192- // Create ZIP file
193- const zip = new JSZip ( ) ;
214+ // Fetch MaterialX files data
215+ console . log ( '> createMaterialXPackage - fetched MaterialX data:' , mtlxData ) ;
194216
195- // 1. Download and add the main MaterialX file
196- let mtlxContent = await this . downloadMaterialXContent ( mtlxData . url ) ;
217+ // Conatiner for Zip contents
218+ const zip = { }
197219
198- // 2. Download and add all included texture files
199- const textureFiles = mtlxData . include || { } ;
220+ // Download all referenced textures
200221 let texturePaths = [ ] ;
222+ let blobs = { } ;
201223
202- const texturePromises = Object . entries ( textureFiles ) . map ( async ( [ path , fileData ] ) => {
224+ const texturePromises = Object . entries ( textureFiles ) . map ( ( [ path , fileData ] ) => async ( ) =>
225+ {
203226 try {
204227 console . log ( `Processing texture: ${ path } from URL: ${ fileData . url } ` ) ;
205-
206- let isEXR = path . toLowerCase ( ) . endsWith ( '.exr' )
207- // Try changing extension to .png.
208- if ( isEXR ) {
209- const prevPath = path ;
210- path = path . replace ( / \. e x r $ / i, '.png' ) ;
211- // Replace /exr/ with /png/
212- fileData . url = fileData . url . replace ( / \/ e x r \/ / i, '/png/' ) ;
213- // Replace .exr with .png extension
214- fileData . url = fileData . url . replace ( '.exr' , '.png' ) ;
215- // Replace .exr with .png in mtlxContent if present
216- const previousMtlxContent = mtlxContent ;
217- mtlxContent = previousMtlxContent . replace ( prevPath , path ) ;
218-
219- console . log ( `EXR file detected. Path: ${ prevPath } -> ${ path } , URL: ${ fileData . url } ` ) ;
220- if ( previousMtlxContent !== mtlxContent ) {
221- console . log ( `Updated MaterialX content to replace .exr with .png for texture: ${ path } ` ) ;
222- }
223-
224- }
225-
226228 const textureBlob = await this . downloadTexture ( fileData . url ) ;
227-
228- // Maintain the folder structure from the include paths
229- const pathParts = path . split ( '/' ) ;
230- let currentFolder = zip ;
231-
232- // Handle nested folder structure
233- for ( let i = 0 ; i < pathParts . length - 1 ; i ++ ) {
234- const folderName = pathParts [ i ] ;
235- currentFolder = currentFolder . folder ( folderName ) ;
236- }
237-
238- currentFolder . file ( pathParts [ pathParts . length - 1 ] , textureBlob ) ;
229+ texturePaths . push ( path ) ;
239230
240- if ( path . toLowerCase ( ) . endsWith ( '.exr' ) ) {
241- console . warn ( `EXR file present which may not be supported by MaterialX texture loader: ${ path } ` ) ;
242- }
243- else {
244- console . log ( `Added texture ${ path } to ZIP from URL: ${ fileData . url } ` ) ;
245- zip . file ( path , textureBlob ) ;
246- }
231+ // Build the local path for the zip (preserving folder structure)
232+ const pathParts = path . split ( '/' ) ;
233+ const localPath = pathParts . join ( '/' ) ;
247234
248- texturePaths . push ( path ) ;
235+ blobs [ localPath ] = await blobToUint8Array ( textureBlob ) ;
249236
250237 } catch ( error ) {
251- console . error ( `Error downloading texture ${ path } :` , error ) ;
252- // Add placeholder file if download fails
253- zip . file ( path , `Failed to download: ${ fileData . url } ` ) ;
238+ console . error ( `Error downloading texture from URI ${ path } :` , error ) ;
239+ const pathParts = path . split ( '/' ) ;
240+ const localPath = pathParts . join ( '/' ) ;
241+ blobs [ localPath ] = fflate . strToU8 ( `Error downloading texture: ${ error . message } ` ) ;
254242 }
255243 } ) ;
256-
244+ await downloadWithConcurrency ( texturePromises , 3 ) ;
257245
258- // 3. Download and add thumbnail
246+ for ( const [ localPath , blobData ] of Object . entries ( blobs ) ) {
247+ zip [ localPath ] = blobData ;
248+ //console.log(`Added texture to ZIP: ${localPath}`);
249+ }
250+
251+ // Download and add thumbnail image
259252 if ( material . thumb_url ) {
260253 try {
261254 const thumbBlob = await this . downloadThumbnail ( material . thumb_url ) ;
262255 const thumbUrl = new URL ( material . thumb_url ) ;
263256 const thumbPath = thumbUrl . pathname . split ( '/' ) . pop ( ) ;
264257 const thumbExt = thumbPath . split ( '.' ) . pop ( ) ;
265- zip . file ( `${ material . id } _thumbnail.${ thumbExt } ` , thumbBlob ) ;
258+ //console.log('Add thumbnail to ZIP:', thumbPath);
259+ zip [ `${ material . id } _thumbnail.${ thumbExt } ` ] = await blobToUint8Array ( thumbBlob ) ;
266260 } catch ( error ) {
267261 console . error ( 'Error downloading thumbnail:' , error ) ;
268262 }
@@ -271,24 +265,13 @@ class JsPolyHavenAPILoader {
271265 // Wait for all downloads to complete
272266 await Promise . all ( texturePromises ) ;
273267
274- for ( const textureName of texturePaths ) {
275- // Patch bad MTLX references in original file
276- const extenson = textureName . split ( '.' ) . pop ( ) ;
277- const exrName = textureName . replace ( `.${ extenson } ` , `.exr` ) ;
278- if ( mtlxContent . includes ( exrName ) ) {
279- console . log ( `Replace ${ exrName } with ${ textureName } in MaterialX content for preview` ) ;
280- mtlxContent = mtlxContent . replace ( exrName , textureName ) ;
281- }
282- }
283-
284268 // Add Materialx document to ZIP.
285- // This must be done after texture processing which may
286- // modify the MTLX image references.
287- zip . file ( `${ material . id } .mtlx` , mtlxContent ) ;
288- console . log ( `Added MaterialX file to ZIP: ${ material . id } .mtlx` ) ; //, ${mtlxContent}`);
269+ //console.log(`Adding MaterialX file to ZIP: ${material.id}.mtlx`);
270+ zip [ `${ material . id } .mtlx` ] = fflate . strToU8 ( mtlxContent ) ;
289271
290- // Add README file, and thumbnail to root of ZIP
291- zip . file ( "README.txt" ,
272+ // Add README file to root of ZIP
273+ //console.log('Adding README.txt to ZIP with material metadata and file list');
274+ zip [ "README.txt" ] = fflate . strToU8 (
292275 `Material: ${ material . name } \n` +
293276 `Resolution: ${ resolution } \n` +
294277 `Source: https://polyhaven.com/a/${ material . id } \n` +
@@ -299,8 +282,20 @@ class JsPolyHavenAPILoader {
299282 ( texturePaths . length > 0 ? texturePaths . map ( t => `- ${ t } ` ) . join ( '\n' ) + '\n' : '' )
300283 ) ;
301284
302- // Generate the ZIP file
303- return await zip . generateAsync ( { type : 'blob' } ) ;
285+ for ( const [ k , v ] of Object . entries ( zip ) ) {
286+ console . log ( `ZIP entry: ${ k } , size: ${ v . length || v . size || 'unknown' } ` ) ;
287+ }
288+
289+ // Compress the ZIP file
290+ //console.log('Compressing ZIP file with fflate...');
291+ const zipped = fflate . zipSync ( zip ) ;
292+
293+ // Create a Blob from the zipped data
294+ //console.log('Creating Blob from zipped data...');
295+ const blob = new Blob ( [ zipped ] , { type : 'application/zip' } ) ;
296+
297+ console . log ( 'MaterialX package created successfully:' , blob ) ;
298+ return blob ;
304299
305300 } catch ( error ) {
306301 console . error ( 'Error creating MaterialX package:' , error ) ;
@@ -312,9 +307,10 @@ class JsPolyHavenAPILoader {
312307 * Get MaterialX content and texture files for preview
313308 * @param materialId Material ID
314309 * @param resolution Resolution (1k, 2k, 4k, 8k)
310+ * @param textureFormat Optional texture format to remap references to (e.g. 'png'). If empty, original formats are used.
315311 * @returns Object containing MaterialX content and texture files
316312 */
317- async getMaterialContent ( materialId , resolution ) {
313+ async getMaterialContent ( materialId , resolution , textureFormat = '' ) {
318314 try {
319315 const filesData = await this . fetchMaterialFiles ( materialId ) ;
320316 const mtlxData = filesData . mtlx ?. [ resolution ] ?. mtlx ;
@@ -324,11 +320,57 @@ class JsPolyHavenAPILoader {
324320 }
325321
326322 // Get MaterialX content
327- const mtlxContent = await this . downloadMaterialXContent ( mtlxData . url ) ;
323+ let mtlxContent = await this . downloadMaterialXContent ( mtlxData . url ) ;
324+
325+ let textureFileData = mtlxData . include || { } ;
326+
327+ // Preprocess MTLX content and file data to remap texture references to .<textureFormat>.
328+ if ( textureFormat . length > 0 )
329+ {
330+ if ( textureFileData && Object . keys ( textureFileData ) . length > 0 ) {
331+ const textureExtension = "." + textureFormat . toLowerCase ( ) ;
332+
333+ for ( const [ path , fileData ] of Object . entries ( textureFileData ) ) {
334+ const baseName = path . replace ( / \\ / g, '/' ) . split ( '/' ) . pop ( ) . replace ( / \. [ ^ . ] + $ / , '' ) ;
335+
336+ // Replace all references to baseName.<ext> with baseName.<textureFormat>
337+ const extRegex = new RegExp ( baseName + '\\.[a-zA-Z0-9]+' , 'g' ) ;
338+ console . log ( `Remapping texture references in MTLX content for ${ baseName } : ${ extRegex } ` ) ;
339+ let prevMtlxContent = mtlxContent ;
340+ mtlxContent = mtlxContent . replace ( extRegex , baseName + textureExtension ) ;
341+ if ( prevMtlxContent == mtlxContent ) {
342+ console . warn ( `No references found in MTLX content for texture ${ baseName } . There is a mismatch between the MTLX content and the texture file data !` ) ;
343+ }
344+
345+ // Remap path and url to .<textureFormat>
346+ let extension = path . split ( '.' ) . pop ( ) . toLowerCase ( ) ;
347+ if ( extension !== textureFormat ) {
348+ const prevPath = path ;
349+ const newPath = path . replace ( / \. [ ^ . ] + $ / i, textureExtension ) ;
350+
351+ // Remap fileData as well
352+ fileData . url = fileData . url . replace ( / \. [ ^ . ] + $ / i, textureExtension ) ;
353+ // Split the path and replace any extension folder with textureExtension
354+ const urlParts = fileData . url . split ( '/' ) ;
355+ const extFromPath = extension ;
356+ fileData . url = urlParts . map ( part => part . toLowerCase ( ) === extFromPath ? textureFormat : part ) . join ( '/' ) ;
357+
358+ delete textureFileData [ path ] ;
359+ textureFileData [ newPath ] = fileData ;
360+ console . log ( `>> Remapped texture path: ${ prevPath } -> ${ newPath } . File data: ${ fileData . url } ` ) ;
361+ } else {
362+ console . log ( `>> Texture path is already ${ textureFormat } : ${ path } . File data: ${ fileData . url } ` ) ;
363+ }
364+ }
365+
366+ //console.log('Final remapped MTLX content:', mtlxContent);
367+ //console.log('Final remapped texture file data:', textureFileData);
368+ }
369+ }
328370
329371 return {
330372 mtlxContent,
331- textureFiles : mtlxData . include || { }
373+ textureFiles : textureFileData
332374 } ;
333375 } catch ( error ) {
334376 console . error ( 'Error getting material content:' , error ) ;
0 commit comments