@@ -29,8 +29,12 @@ def __init__(self):
2929 self .root_url = 'https://api.matlib.gpuopen.com/api'
3030 ### URL for the materials
3131 self .url = self .root_url + '/materials'
32- ### URL for the packages
32+ ### URL for the package information
3333 self .package_url = self .root_url + '/packages'
34+ ### URL for getting rendering information
35+ self .render_url = self .root_url + '/renders'
36+ ### List of title, preview url pairs for the materials
37+ self .materialPreviews = None
3438 ### List of materials
3539 self .materials = None
3640 ### List of material names
@@ -40,12 +44,13 @@ def __init__(self):
4044 self .logger = logging .getLogger ('GPUO' )
4145 logging .basicConfig (level = logging .INFO )
4246
43- def writePackageDataToFile (self , data , outputFolder , title , unzipFile = True ) -> bool :
47+ def writePackageDataToFile (self , data , outputFolder , title , url , unzipFile = True ) -> bool :
4448 '''
4549 Write a package data to a file.
4650 @param data: The data to write.
4751 @param outputFolder: The output folder to write the file to.
4852 @param title: The title of the file.
53+ @param url: The URL for the material preview image.
4954 @param unzipFile: If true, the file is unzipped to a folder with the same name
5055 as the title.
5156 @return: True if the package was written.
@@ -65,14 +70,32 @@ def writePackageDataToFile(self, data, outputFolder, title, unzipFile=True) -> b
6570 with zipfile .ZipFile (data_io , 'r' ) as zip_ref :
6671 zip_ref .extractall (unzipFolder )
6772
73+ # Write a url.txt file with url information for the material preview
74+ urlFile = os .path .join (unzipFolder , 'url.txt' )
75+ with open (urlFile , 'w' ) as f :
76+ f .write (url )
77+
6878 self .logger .info (f'Unzipped to folder: "{ unzipFolder } "' )
6979
70- else :
80+ else :
81+ # Add the url.txt into the existing zip data
82+ zip_buffer = io .BytesIO ()
83+ # Open the original zip data in append mode
84+ with zipfile .ZipFile (io .BytesIO (data ), 'a' , zipfile .ZIP_DEFLATED ) as zip_in :
85+ # Copy all files to a new zip buffer
86+ with zipfile .ZipFile (zip_buffer , 'w' , zipfile .ZIP_DEFLATED ) as zip_out :
87+ for item in zip_in .infolist ():
88+ zip_out .writestr (item , zip_in .read (item .filename ))
89+ # Add url.txt
90+ zip_out .writestr ('url.txt' , url )
91+ data = zip_buffer .getvalue ()
92+
7193 outputFile = os .path .join (outputFolder , f"{ title } .zip" )
7294 with open (outputFile , "wb" ) as f :
7395 self .logger .info (f'Write package to file: "{ outputFile } "' )
7496 f .write (data )
7597
98+
7699 return True
77100
78101 def convertPilImageToBase64 (self , image ):
@@ -188,7 +211,9 @@ def downloadPackage(self, listNumber, materialNumber, packageId=0):
188211 data = requests .get (url ).content
189212
190213 title = jsonResult ["title" ]
191- return [data , title ]
214+
215+ preview_url = self .getMaterialPreviewURL (title )
216+ return [data , title , preview_url ]
192217
193218 def downloadPackageByExpression (self , searchExpr , packageId = 0 ):
194219 '''
@@ -206,7 +231,7 @@ def downloadPackageByExpression(self, searchExpr, packageId=0):
206231 materialNumber = found ['materialNumber' ]
207232 matName = found ['title' ]
208233 self .logger .info (f'> Download material: { matName } List: { listNumber } . Index: { materialNumber } ' )
209- result = [data , title ] = self .downloadPackage (listNumber , materialNumber , packageId )
234+ result = [data , title , url ] = self .downloadPackage (listNumber , materialNumber , packageId )
210235 downloadList .append (result )
211236 return downloadList
212237
@@ -246,6 +271,63 @@ def getMaterialNames(self) -> list:
246271 self .materialNames .append (material ['title' ])
247272
248273 return self .materialNames
274+
275+ def getMaterialPreviewURL (self , title ) -> str :
276+ '''
277+ @breif Given the title of a material, return the URL for the material preview image.
278+ @param title: The title of the material to get the preview URL for.
279+ @return: The URL for the material preview image. Else empty string.
280+ '''
281+ url = ''
282+ if (self .materialPreviews == None ):
283+ self .computeMaterialPreviews ()
284+ if (not self .materialPreviews ):
285+ return url
286+
287+ for item in self .materialPreviews :
288+ if item ['title' ] == title :
289+ url = item ['preview_url' ]
290+ break
291+ return url
292+
293+ def getMaterialPreviews (self , force = False ) -> list | None :
294+ if not self .materialPreviews or force :
295+ self .computeMaterialPreviews ()
296+ return self .materialPreviews
297+
298+ def computeMaterialPreviews (self ) -> list :
299+ '''
300+ @brief Get the material preview URLs for the materials loaded from the GPUOpen material database.
301+ @return list of items of the form: { 'title': material_title, 'preview_url': url }
302+ If no materials or renders are loaded, then an empty list is returned.
303+ '''
304+ self .materialPreviews = []
305+ if (self .materials == None ):
306+ return []
307+ if (self .renders == None ):
308+ return []
309+ render_urls = self .renders ["renders" ]
310+
311+ for materialList in self .materials :
312+ for material in materialList ['results' ]:
313+ renders_order_list = material ['renders_order' ]
314+ material_title = material ['title' ]
315+
316+ if len (renders_order_list ) > 0 :
317+ #print('renders_order_list:', renders_order_list)
318+ render_lookup = renders_order_list [0 ]
319+ # Look for item in "renders" list with "id" == render_lookup
320+ for render in render_urls :
321+ #print('render id:', render['id'], 'render_lookup:', render_lookup )
322+ if render ['id' ] == render_lookup :
323+ url = render ['thumbnail_url' ]
324+ item = { 'title' : material_title , 'preview_url' : url }
325+ self .materialPreviews .append (item )
326+ #print(f'Found render for material: {item}')
327+ break
328+ else :
329+ self .logger .info (f'No renderings specified for material: { material_title } ' )
330+ return self .materialPreviews
249331
250332 def getMaterials (self ) -> list :
251333 '''
@@ -308,6 +390,71 @@ def getMaterials(self) -> list:
308390 self .logger .info (f'Error: { response .status_code } , { response .text } ' )
309391
310392 return self .materials
393+
394+ def getRenders (self ) -> list :
395+ '''
396+ Get the rendering information returned from the GPUOpen material database.
397+ Will loop based on the linked-list of render info stored in the database.
398+ Currently the batch size requested is 100 render infos per batch.
399+ @return: List of material lists
400+ '''
401+
402+ self .renders = { "renders" : [] }
403+
404+ url = self .render_url
405+ headers = {
406+ 'accept' : 'application/json'
407+ }
408+
409+ # Get batches of materials. Start with the first 100.
410+ # Can apply more filters to this as needed in the future.
411+ # This will get every material in the database.
412+ params = {
413+ 'limit' : 100 ,
414+ 'offset' : 0
415+ }
416+ haveMoreMaterials = True
417+ while (haveMoreMaterials ):
418+
419+ response = requests .get (url , headers = headers , params = params )
420+
421+ if response .status_code == HTTPStatus .OK :
422+
423+ raw_response = response .text
424+
425+ # Split the response text assuming the JSON objects are concatenated
426+ json_strings = raw_response .split ('}{' )
427+ #self.logger.info('Number of JSON strings:', len(json_strings))
428+ json_result_string = json_strings [0 ]
429+ jsonObject = json .loads (json_result_string )
430+
431+ # Extrac out the "results": [] list
432+ results_list = jsonObject ['results' ]
433+ for result in results_list :
434+ self .renders ["renders" ].append (result )
435+
436+ # Scan for next batch of materials
437+ nextQuery = jsonObject ['next' ]
438+ if (nextQuery ):
439+ # Get limit and offset from this: 'https://api.matlib.gpuopen.com/api/renders/?limit=100&offset=100"'
440+ # Split the string by '?'
441+ queryParts = nextQuery .split ('?' )
442+ # Split the string by '&'
443+ queryParts = queryParts [1 ].split ('&' )
444+ # Split the string by '='
445+ limitParts = queryParts [0 ].split ('=' )
446+ offsetParts = queryParts [1 ].split ('=' )
447+ params ['limit' ] = int (limitParts [1 ])
448+ params ['offset' ] = int (offsetParts [1 ])
449+ self .logger .info (f'Fetch set of render infos: limit: { params ["limit" ]} offset: { params ["offset" ]} ' )
450+ else :
451+ haveMoreMaterials = False
452+ break
453+
454+ else :
455+ self .logger .info (f'Error: { response .status_code } , { response .text } ' )
456+
457+ return self .renders
311458
312459 def getMaterialsAsJsonString (self ) -> list :
313460 '''
@@ -332,8 +479,8 @@ def getMaterialFileNames(self, rootName) -> list:
332479 rootDir = os .path .dirname (rootName )
333480 if not rootDir :
334481 rootDir = '.'
335- print ('RootDir:' , rootDir )
336- print ('RootName:' , rootName )
482+ # print('RootDir:', rootDir)
483+ # print('RootName:', rootName)
337484 for root , dirs , files in os .walk (rootDir ):
338485 for file in files :
339486 # Check that it ends with a number + ".json". e.g.
@@ -357,9 +504,45 @@ def readMaterialFiles(self, fileNames) -> list:
357504 self .materials .append (data )
358505 return self .materials
359506
507+ def writeRenderFiles (self , folder , rootFileName ) -> int :
508+ '''
509+ Write the render information to disk files.
510+ @param folder: The folder to write the files to.
511+ @param rootFileName: The root file name to use for the files.
512+ @return: The number of files written.
513+ '''
514+ if (self .renders == None ):
515+ return 0
516+
517+ os .makedirs (folder , exist_ok = True )
518+ # Write JSON to file
519+ fileName = rootFileName + '_.json'
520+ rendersFileName = os .path .join (folder , fileName )
521+ self .logger .info (f'> Write render info to file: "{ rendersFileName } "' )
522+ with open (rendersFileName , 'w' ) as f :
523+ json .dump (self .renders , f , indent = 4 )
524+
525+ def writeMaterialPreviewFile (self , folder , rootFileName ):
526+ '''
527+ Write the material preview information to disk files.
528+ @param folder: The folder to write the files to.
529+ @param rootFileName: The root file name to use for the files.
530+ '''
531+ if (self .materialPreviews == None ):
532+ return 0
533+
534+ os .makedirs (folder , exist_ok = True )
535+ # Write JSON to file
536+ fileName = rootFileName + '_.json'
537+ previewsFileName = os .path .join (folder , fileName )
538+ self .logger .info (f'> Write material preview info to file: "{ previewsFileName } "' )
539+ with open (previewsFileName , 'w' ) as f :
540+ json .dump (self .materialPreviews , f , indent = 4 )
541+
542+
360543 def writeMaterialFiles (self , folder , rootFileName ) -> int :
361544 '''
362- Write the materials to a set of MaterialX files .
545+ Write the materials to disk .
363546 @param folder: The folder to write the files to.
364547 @param rootFileName: The root file name to use for the files.
365548 @return: The number of files written.
0 commit comments