Skip to content

Commit 4fb5c6d

Browse files
authored
GPUOpen metadata (#31)
* Add GPUOpen metadata querys for preview image. Update save to ad url to zip file. * Update docs.
1 parent 5de8ebb commit 4fb5c6d

12 files changed

Lines changed: 21450 additions & 797 deletions

File tree

documents/doxygen_warnings.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
D:/Work/materialx/materialxMaterials/src/materialxMaterials/GPUOpenLoader.py:277: warning: Found unknown command '@breif'
12
D:/Work/materialx/materialxMaterials/javascript/JsMaterialXPhysicallyBased.js:167: warning: Member get_physlib() (function) of class JsPhysicallyBasedMaterialLoader is not documented.
23
D:/Work/materialx/materialxMaterials/javascript/JsMaterialXPhysicallyBased.js:172: warning: Member get_physlib_definition() (function) of class JsPhysicallyBasedMaterialLoader is not documented.
34
D:/Work/materialx/materialxMaterials/javascript/JsMaterialXPhysicallyBased.js:180: warning: Member get_physlib_category() (function) of class JsPhysicallyBasedMaterialLoader is not documented.
@@ -46,6 +47,8 @@ D:/Work/materialx/materialxMaterials/javascript/JsMaterialXPhysicallyBased.js:46
4647
parameter 'source_bxdf'
4748
parameter 'target_bxdf'
4849
parameter 'node'
50+
D:/Work/materialx/materialxMaterials/src/materialxMaterials/GPUOpenLoader.py:293: warning: Member getMaterialPreviews(self, force=False) (function) of class materialxMaterials.GPUOpenLoader.GPUOpenMaterialLoader is not documented.
51+
D:/Work/materialx/materialxMaterials/src/materialxMaterials/GPUOpenLoader.py:307: warning: Member renders (variable) of class materialxMaterials.GPUOpenLoader.GPUOpenMaterialLoader is not documented.
4952
D:/Work/materialx/materialxMaterials/src/materialxMaterials/physicallyBasedMaterialX.py:570: warning: Member derive_translator_name_from_targets(self, str source, str target) (function) of class materialxMaterials.physicallyBasedMaterialX.PhysicallyBasedMaterialLoader is not documented.
5053
D:/Work/materialx/materialxMaterials/src/materialxMaterials/physicallyBasedMaterialX.py:971: warning: Member add_copyright_comment(self, doc, shaderCategory, embedDate=False) (function) of class materialxMaterials.physicallyBasedMaterialX.PhysicallyBasedMaterialLoader is not documented.
5154
D:/Work/materialx/materialxMaterials/src/materialxMaterials/physicallyBasedMaterialX.py:1151: warning: Member create_working_document() (function) of class materialxMaterials.physicallyBasedMaterialX.PhysicallyBasedMaterialLoader is not documented.

src/materialxMaterials.egg-info/SOURCES.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ src/materialxMaterials/data/GPUOpenMaterialX/GPUOpenMaterialX_2.json
2323
src/materialxMaterials/data/GPUOpenMaterialX/GPUOpenMaterialX_3.json
2424
src/materialxMaterials/data/GPUOpenMaterialX/GPUOpenMaterialX_4.json
2525
src/materialxMaterials/data/GPUOpenMaterialX/GPUOpenMaterialX_Names.json
26+
src/materialxMaterials/data/GPUOpenMaterialX/GPUOpenMaterialX_Previews_.json
27+
src/materialxMaterials/data/GPUOpenMaterialX/GPUOpenMaterialX_Renders_.json
2628
src/materialxMaterials/data/GPUOpenMaterialX/Oliana Blue Painted Wood.zip
2729
src/materialxMaterials/data/GPUOpenMaterialX/Emerald Peaks Wallpaper/Emerald_Peaks_Wallpaper.mtlx
2830
src/materialxMaterials/data/GPUOpenMaterialX/Emerald Peaks Wallpaper/Emerald_Peaks_Wallpaper.png
@@ -40,6 +42,7 @@ src/materialxMaterials/data/GPUOpenMaterialX/Indigo Palm Wallpaper/textures/Indi
4042
src/materialxMaterials/data/GPUOpenMaterialX/Indigo Palm Wallpaper/textures/Indigo_Palm_Wallpaper_normal.png
4143
src/materialxMaterials/data/GPUOpenMaterialX/Oliana Blue Painted Wood/Oliana_Blue_Painted_Wood.mtlx
4244
src/materialxMaterials/data/GPUOpenMaterialX/Oliana Blue Painted Wood/Oliana_Blue_Painted_Wood.png
45+
src/materialxMaterials/data/GPUOpenMaterialX/Oliana Blue Painted Wood/url.txt
4346
src/materialxMaterials/data/GPUOpenMaterialX/Oliana Blue Painted Wood/textures/Oliana_Blue_Painted_Wood_Mask.png
4447
src/materialxMaterials/data/GPUOpenMaterialX/Oliana Blue Painted Wood/textures/Oliana_Blue_Painted_Wood_Normal.png
4548
src/materialxMaterials/data/GPUOpenMaterialX/Oliana Blue Painted Wood/textures/Oliana_Blue_Painted_Wood_baseColor.png

src/materialxMaterials/GPUOpenLoader.py

Lines changed: 191 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -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.

src/materialxMaterials/GPUOpenLoaderCmd.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ def GPUOpenLoaderCmd():
5050
# Download materials
5151
logger.info(f'> Download materials from GPUOpen')
5252
materials = loader.getMaterials()
53+
# Download renders
54+
renders = loader.getRenders()
55+
loader.getMaterialPreviews()
5356

5457
outputFolder = 'GPUOpenMaterialX'
5558
if opts.output:
@@ -64,6 +67,8 @@ def GPUOpenLoaderCmd():
6467
logger.info(f'Available number of materials: {materialCount}')
6568
if opts.saveMaterials:
6669
loader.writeMaterialFiles(outputFolder, 'GPUOpenMaterialX')
70+
loader.writeRenderFiles(outputFolder, 'GPUOpenMaterialX_Renders')
71+
loader.writeMaterialPreviewFile(outputFolder, 'GPUOpenMaterialX_Previews')
6772

6873
# Create a test expression
6974
searchExpr = ''
@@ -78,8 +83,9 @@ def GPUOpenLoaderCmd():
7883
for dataItem in dataItems:
7984
data = dataItem[0]
8085
title = dataItem[1]
86+
url = dataItem[2]
8187
logger.info(f'Write package data to file: {title}, Data size: {len(data)*toMB:.2f} MB')
82-
loader.writePackageDataToFile(data, outputFolder, title, unzipFile=unzipFile)
88+
loader.writePackageDataToFile(data, outputFolder, title, url, unzipFile=unzipFile)
8389

8490
extractIndices = opts.extractIndices
8591
if len(extractIndices) > 0:
@@ -91,10 +97,10 @@ def GPUOpenLoaderCmd():
9197
materialList = int(indices[0])
9298
materialIndex = int(indices[1])
9399
materialPackage = int(indices[2])
94-
[data, title] = loader.downloadPackage(materialList, materialIndex, materialPackage)
95-
logger.info(f'> Download material: {title} List: {materialList}. Index: {materialIndex}. Package: {materialPackage}')
100+
[data, title, url] = loader.downloadPackage(materialList, materialIndex, materialPackage)
101+
logger.info(f'> Download material: {title} List: {materialList}. Index: {materialIndex}. Package: {materialPackage}. Preview URL:{url}')
96102
if data:
97-
loader.writePackageDataToFile(data, outputFolder, title)
103+
loader.writePackageDataToFile(data, outputFolder, title, url)
98104

99105
if opts.materialNames:
100106
materialNamesFile = os.path.join(outputFolder, 'GPUOpenMaterialX_Names.json')

0 commit comments

Comments
 (0)