2@brief Utilities to extract materials from the GPUOpen material database. This is not a complete set of calls to extract out all material information but instead enough to find materials
3and extract out specific packages from the list of available materials.
5See: https://api.matlib.gpuopen.com/api/swagger/ for information on available API calls.
8import requests, json, os, io, re, zipfile, logging
9from http
import HTTPStatus
16from PIL
import Image
as PILImage
21 This class is used to load materials from the GPUOpen material database.
22 See: https://api.matlib.gpuopen.com/api/swagger/ for API information.
26 Initialize the GPUOpen material loader.
29 self.
root_url =
'https://api.matlib.gpuopen.com/api'
46 self.
logger = logging.getLogger(
'GPUO')
47 logging.basicConfig(level=logging.INFO)
51 Write a package data to a file.
52 @param data: The data to write.
53 @param outputFolder: The output folder to write the file to.
54 @param title: The title of the file.
55 @param url: The URL for the material preview image.
56 @param unzipFile: If true, the file is unzipped to a folder with the same name
58 @return: True if the package was written.
63 if not os.path.exists(outputFolder):
64 os.makedirs(outputFolder)
68 unzipFolder = os.path.join(outputFolder, title)
71 with io.BytesIO(data)
as data_io:
72 with zipfile.ZipFile(data_io,
'r')
as zip_ref:
73 zip_ref.extractall(unzipFolder)
76 urlFile = os.path.join(unzipFolder,
'url.txt')
77 with open(urlFile,
'w')
as f:
80 self.
logger.info(f
'Unzipped to folder: "{unzipFolder}"')
84 zip_buffer = io.BytesIO()
86 with zipfile.ZipFile(io.BytesIO(data),
'a', zipfile.ZIP_DEFLATED)
as zip_in:
88 with zipfile.ZipFile(zip_buffer,
'w', zipfile.ZIP_DEFLATED)
as zip_out:
89 for item
in zip_in.infolist():
90 zip_out.writestr(item, zip_in.read(item.filename))
92 zip_out.writestr(
'url.txt', url)
93 data = zip_buffer.getvalue()
95 outputFile = os.path.join(outputFolder, f
"{title}.zip")
96 with open(outputFile,
"wb")
as f:
97 self.
logger.info(f
'Write package to file: "{outputFile}"')
105 Convert a PIL image to a Base64 string.
106 @param image: An instance of PIL.Image
107 @return: Base64-encoded string of the image
111 self.
logger.debug(
'Pillow (PIL) image module not provided. Image data will not be converted to Base64.')
114 self.
logger.debug(
'No image data. Image data will not be converted to Base64.')
122 image.save(buffer, format=
"PNG")
123 binary_data = buffer.getvalue()
124 base64_encoded_data = base64.b64encode(binary_data).decode(
'utf-8')
127 return base64_encoded_data
131 Extract the package data from a zip file.
132 @param data: The data to extract.
133 @param pilImage: The PIL image module.
134 @return: A list of extracted data of the form:
135 [ { 'file_name': file_name, 'data': data, 'type': type } ]
140 self.
logger.debug(
'Pillow (PIL) image module provided. Image data will not be extracted.')
142 zip_object = io.BytesIO(data)
144 extracted_data_list = []
145 with zipfile.ZipFile(zip_object,
'r')
as zip_file:
147 for file_name
in zip_file.namelist():
149 extracted_data = zip_file.read(file_name)
150 if file_name.endswith(
'.mtlx'):
151 mtlx_string = extracted_data.decode(
'utf-8')
152 extracted_data_list.append( {
'file_name': file_name,
'data': mtlx_string,
'type':
'mtlx'} )
155 elif file_name.endswith(
'.png'):
157 image = pilImage.open(io.BytesIO(extracted_data))
160 extracted_data_list.append( {
'file_name': file_name,
'data': image,
'type':
'image'} )
162 return extracted_data_list
166 Download a package for a given material from the GPUOpen material database.
167 @param listNumber: The list number of the material to download.
168 @param materialNumber: The material number to download.
169 @param packageId: The package ID to download.
170 Packages are numbered starting at 0. Default is 0.
171 with index 0 containing the smallest package (smallest resolution referenced textures).
174 self.
logger.info(
'No material loaded.')
179 self.
logger.info(f
'No material for list {listNumber}.')
184 if "results" in json_data:
185 jsonResults = json_data[
"results"]
186 if len(jsonResults) <= materialNumber:
187 self.
logger.info(f
'No material for index {materialNumber}.')
190 jsonResult = jsonResults[materialNumber]
197 if "packages" in jsonResult:
198 jsonPackages = jsonResult[
"packages"]
200 self.
logger.info(f
'No packages for material {materialNumber}.')
203 if len(jsonPackages) <= packageId:
204 self.
logger.info(f
'No package for index {packageId}.')
206 package_id = jsonPackages[packageId]
209 self.
logger.info(f
'No package for index {packageId}.')
212 url = f
"{self.package_url}/{package_id}/download"
213 data = requests.get(url).content
215 title = jsonResult[
"title"]
218 return [data, title, preview_url]
222 Download a package for a given material from the GPUOpen material database.
223 @param searchExpr: The regular expression to match the material name.
224 @param exact_match: If true, the material name must match exactly. Default is false.
225 @param packageId: The package ID to download.
226 @return: A list of downloaded packages of the form:
231 if len(foundList) > 0:
232 for found
in foundList:
233 listNumber = found[
'listNumber']
234 materialNumber = found[
'materialNumber']
235 matName = found[
'title']
236 self.
logger.info(f
'> Download material: {matName} List: {listNumber}. Index: {materialNumber}')
237 result = [data, title, url] = self.
downloadPackage(listNumber, materialNumber, packageId)
238 downloadList.append(result)
243 Find materials by name.
244 @param materialName: Regular expression to match the material name.
245 @param exact_match: If true, the material name must match exactly. Default is false.
246 @return: A list of materials that match the regular expression of the form:
247 [ { 'listNumber': listNumber, 'materialNumber': materialNumber, 'title': title } ]
256 for material
in materialList[
'results']:
258 if materialName.lower() == material[
'title'].lower():
259 materialsList.append({
'listNumber': listNumber,
'materialNumber': materialNumber,
'title': material[
'title'] })
262 if re.match(materialName, material[
'title'], re.IGNORECASE):
263 materialsList.append({
'listNumber': listNumber,
'materialNumber': materialNumber,
'title': material[
'title'] })
271 Update the material names from the material lists.
272 @return: List of material names. If no materials are loaded, then an empty list is returned.
279 for material
in materialList[
'results']:
286 @breif Given the title of a material, return the URL for the material preview image.
287 @param title: The title of the material to get the preview URL for.
288 @return: The URL for the material preview image. Else empty string.
297 if item[
'title'] == title:
298 url = item[
'preview_url']
302 def getMaterialPreviews(self, force = False) -> list | None:
309 @brief Get the material preview URLs for the materials loaded from the GPUOpen material database.
310 @return list of items of the form: { 'title': material_title, 'preview_url': url }
311 If no materials or renders are loaded, then an empty list is returned.
318 render_urls = self.
renders[
"renders"]
321 for material
in materialList[
'results']:
322 renders_order_list = material[
'renders_order']
323 material_title = material[
'title']
325 if len(renders_order_list) > 0:
327 render_lookup = renders_order_list[0]
329 for render
in render_urls:
331 if render[
'id'] == render_lookup:
332 url = render[
'thumbnail_url']
333 item = {
'title': material_title,
'preview_url': url }
338 self.
logger.info(f
'No renderings specified for material: {material_title}')
343 Get the materials returned from the GPUOpen material database.
344 Will loop based on the linked-list of materials stored in the database.
345 Currently the batch size requested is 100 materials per batch.
346 @return: List of material lists
354 'accept':
'application/json'
364 haveMoreMaterials =
True
365 while (haveMoreMaterials):
367 response = requests.get(url, headers=headers, params=params)
369 if response.status_code == HTTPStatus.OK:
371 raw_response = response.text
374 json_strings = raw_response.split(
'}{')
376 json_result_string = json_strings[0]
377 jsonObject = json.loads(json_result_string)
381 nextQuery = jsonObject[
'next']
385 queryParts = nextQuery.split(
'?')
387 queryParts = queryParts[1].split(
'&')
389 limitParts = queryParts[0].split(
'=')
390 offsetParts = queryParts[1].split(
'=')
391 params[
'limit'] = int(limitParts[1])
392 params[
'offset'] = int(offsetParts[1])
393 self.
logger.info(f
'Fetch set of materials: limit: {params["limit"]} offset: {params["offset"]}')
395 haveMoreMaterials =
False
399 self.
logger.info(f
'Error: {response.status_code}, {response.text}')
405 Get the rendering information returned from the GPUOpen material database.
406 Will loop based on the linked-list of render info stored in the database.
407 Currently the batch size requested is 100 render infos per batch.
408 @param force: If true, forces a re-download of the render information. Default is false.
409 @return: List of material lists
414 self.
renders = {
"renders": [] }
418 'accept':
'application/json'
428 haveMoreMaterials =
True
429 while (haveMoreMaterials):
431 response = requests.get(url, headers=headers, params=params)
433 if response.status_code == HTTPStatus.OK:
435 raw_response = response.text
438 json_strings = raw_response.split(
'}{')
440 json_result_string = json_strings[0]
441 jsonObject = json.loads(json_result_string)
444 results_list = jsonObject[
'results']
445 for result
in results_list:
446 self.
renders[
"renders"].append(result)
449 nextQuery = jsonObject[
'next']
453 queryParts = nextQuery.split(
'?')
455 queryParts = queryParts[1].split(
'&')
457 limitParts = queryParts[0].split(
'=')
458 offsetParts = queryParts[1].split(
'=')
459 params[
'limit'] = int(limitParts[1])
460 params[
'offset'] = int(offsetParts[1])
461 self.
logger.info(f
'Fetch set of render infos: limit: {params["limit"]} offset: {params["offset"]}')
463 haveMoreMaterials =
False
467 self.
logger.info(f
'Error: {response.status_code}, {response.text}')
473 Get the JSON strings for the materials
474 @return: List of JSON strings for the materials. One string per material batch.
481 results.append(json.dumps(material, indent=4, sort_keys=
True))
486 Get list of material file names based on root file name.
487 @param rootName: The root name of the files to load. The files are assumed to be named: rootName_#.json
490 rootName = os.path.basename(rootName)
491 rootDir = os.path.dirname(rootName)
496 for root, dirs, files
in os.walk(rootDir):
500 if file.startswith(rootName)
and file.endswith(
'.json')
and file[len(rootName):-5].isdigit():
501 filePath = os.path.join(root, file)
502 filePaths.append(filePath)
507 @brief Read the material files from the "data/GPUOpenMaterialX" folder in the install Python package.
508 The files are expected to be named:
509 - "GPUOpenMaterialX_#.json" for material files,
510 - "GPUOpenMaterialX_Previews_.json" for material preview information, and
511 - "GPUOpenMaterialX_Names.json" for material names.
520 packageFolder = os.path.join(os.path.dirname(__file__),
'data/GPUOpenMaterialX')
521 for fileName
in os.listdir(packageFolder):
522 filePath = os.path.join(packageFolder, fileName)
523 self.
logger.debug(f
'> SCAN package file: "{filePath}"')
525 if re.match(
r'GPUOpenMaterialX_\d+\.json', fileName):
526 self.
logger.debug(f
'> Read package file: "{filePath}"')
527 with open(filePath)
as f:
529 results = data[
'results']
530 results_count = len(results)
532 elif fileName ==
'GPUOpenMaterialX_Previews_.json':
533 self.
logger.debug(f
'> Read package file: "{filePath}"')
534 with open(filePath)
as f:
542 elif fileName ==
'GPUOpenMaterialX_Renders_.json':
543 self.
logger.debug(f
'> Read package file: "{filePath}"')
544 with open(filePath)
as f:
552 self.
logger.debug(f
'Loaded {len(self.materials)} material files, '
553 f
'{len(self.materialPreviews)} material previews, and '
554 f
'{len(self.materialNames)} material names, '
555 f
'{len(self.renders)} render files from package.')
560 Load the materials from a set of JSON files downloaded from
561 the GPUOpen material database.
564 for fileName
in fileNames:
565 with open(fileName)
as f:
567 results = data[
'results']
568 results_count = len(results)
574 Write the render information to disk files.
575 @param folder: The folder to write the files to.
576 @param rootFileName: The root file name to use for the files.
577 @return: The number of files written.
582 os.makedirs(folder, exist_ok=
True)
584 fileName = rootFileName +
'_.json'
585 rendersFileName = os.path.join(folder, fileName)
586 self.
logger.info(f
'> Write render info to file: "{rendersFileName}"')
587 with open(rendersFileName,
'w')
as f:
588 json.dump(self.
renders, f, indent=4)
592 Write the material preview information to disk files.
593 @param folder: The folder to write the files to.
594 @param rootFileName: The root file name to use for the files.
599 os.makedirs(folder, exist_ok=
True)
601 fileName = rootFileName +
'_.json'
602 previewsFileName = os.path.join(folder, fileName)
603 self.
logger.info(f
'> Write material preview info to file: "{previewsFileName}"')
604 with open(previewsFileName,
'w')
as f:
610 Write the materials to disk.
611 @param folder: The folder to write the files to.
612 @param rootFileName: The root file name to use for the files.
613 @return: The number of files written.
620 os.makedirs(folder, exist_ok=
True)
623 fileName = rootFileName +
'_' + str(i) +
'.json'
624 materialFileName = os.path.join(folder, fileName)
625 self.
logger.info(f
'> Write material to file: "{materialFileName}"')
626 with open(materialFileName,
'w')
as f:
627 json.dump(material, f, indent=4, sort_keys=
True)
634 Write sorted list of the material names to a file in JSON format
635 @param fileName: The file name to write the material names to.
636 @param sort: If true, sort the material names.
641 with open(fileName,
'w')
as f:
This class is used to load materials from the GPUOpen material database.
writeMaterialNamesToFile(self, fileName, sort=True)
Write sorted list of the material names to a file in JSON format.
str render_url
URL for getting rendering information.
list getMaterialFileNames(self, rootName)
Get list of material file names based on root file name.
list getMaterials(self)
Get the materials returned from the GPUOpen material database.
dict renders
List of render information.
writeMaterialPreviewFile(self, folder, rootFileName)
Write the material preview information to disk files.
str getMaterialPreviewURL(self, title)
@breif Given the title of a material, return the URL for the material preview image.
int writeMaterialFiles(self, folder, rootFileName)
Write the materials to disk.
str package_url
URL for the package information.
convertPilImageToBase64(self, image)
Convert a PIL image to a Base64 string.
list materialPreviews
List of title, preview url pairs for the materials.
str root_url
Root URL for the GPUOpen material database.
downloadPackage(self, listNumber, materialNumber, packageId=0)
Download a package for a given material from the GPUOpen material database.
None readPackageFiles(self)
Read the material files from the "data/GPUOpenMaterialX" folder in the install Python package.
str url
URL for the materials.
extractPackageData(self, data, pilImage)
Extract the package data from a zip file.
bool writePackageDataToFile(self, data, outputFolder, title, url, unzipFile=True)
Write a package data to a file.
list getMaterialsAsJsonString(self)
Get the JSON strings for the materials.
int writeRenderFiles(self, folder, rootFileName)
Write the render information to disk files.
list computeMaterialPreviews(self)
Get the material preview URLs for the materials loaded from the GPUOpen material database.
list findMaterialsByName(self, materialName, exact_match=False)
Find materials by name.
list getRenders(self, force=False)
Get the rendering information returned from the GPUOpen material database.
int materials
List of materials.
list getMaterialNames(self)
Update the material names from the material lists.
list materialNames
List of material names.
downloadPackageByExpression(self, searchExpr, exact_match=False, packageId=0)
Download a package for a given material from the GPUOpen material database.
list readMaterialFiles(self, fileNames)
Load the materials from a set of JSON files downloaded from the GPUOpen material database.
__init__(self)
Initialize the GPUOpen material loader.