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'
40 self.
logger = logging.getLogger(
'GPUO')
41 logging.basicConfig(level=logging.INFO)
45 Write a package data to a file.
46 @param data: The data to write.
47 @param outputFolder: The output folder to write the file to.
48 @param title: The title of the file.
49 @param unzipFile: If true, the file is unzipped to a folder with the same name
51 @return: True if the package was written.
56 if not os.path.exists(outputFolder):
57 os.makedirs(outputFolder)
61 unzipFolder = os.path.join(outputFolder, title)
64 with io.BytesIO(data)
as data_io:
65 with zipfile.ZipFile(data_io,
'r')
as zip_ref:
66 zip_ref.extractall(unzipFolder)
68 self.
logger.info(f
'Unzipped to folder: "{unzipFolder}"')
71 outputFile = os.path.join(outputFolder, f
"{title}.zip")
72 with open(outputFile,
"wb")
as f:
73 self.
logger.info(f
'Write package to file: "{outputFile}"')
80 Convert a PIL image to a Base64 string.
81 @param image: An instance of PIL.Image
82 @return: Base64-encoded string of the image
86 self.
logger.debug(
'Pillow (PIL) image module not provided. Image data will not be converted to Base64.')
89 self.
logger.debug(
'No image data. Image data will not be converted to Base64.')
97 image.save(buffer, format=
"PNG")
98 binary_data = buffer.getvalue()
99 base64_encoded_data = base64.b64encode(binary_data).decode(
'utf-8')
102 return base64_encoded_data
106 Extract the package data from a zip file.
107 @param data: The data to extract.
108 @param pilImage: The PIL image module.
109 @return: A list of extracted data of the form:
110 [ { 'file_name': file_name, 'data': data, 'type': type } ]
115 self.
logger.debug(
'Pillow (PIL) image module provided. Image data will not be extracted.')
117 zip_object = io.BytesIO(data)
119 extracted_data_list = []
120 with zipfile.ZipFile(zip_object,
'r')
as zip_file:
122 for file_name
in zip_file.namelist():
124 extracted_data = zip_file.read(file_name)
125 if file_name.endswith(
'.mtlx'):
126 mtlx_string = extracted_data.decode(
'utf-8')
127 extracted_data_list.append( {
'file_name': file_name,
'data': mtlx_string,
'type':
'mtlx'} )
130 elif file_name.endswith(
'.png'):
132 image = pilImage.open(io.BytesIO(extracted_data))
135 extracted_data_list.append( {
'file_name': file_name,
'data': image,
'type':
'image'} )
137 return extracted_data_list
141 Download a package for a given material from the GPUOpen material database.
142 @param listNumber: The list number of the material to download.
143 @param materialNumber: The material number to download.
144 @param packageId: The package ID to download.
145 Packages are numbered starting at 0. Default is 0.
146 with index 0 containing the smallest package (smallest resolution referenced textures).
149 self.
logger.info(
'No material loaded.')
154 self.
logger.info(f
'No material for list {listNumber}.')
159 if "results" in json_data:
160 jsonResults = json_data[
"results"]
161 if len(jsonResults) <= materialNumber:
162 self.
logger.info(f
'No material for index {materialNumber}.')
165 jsonResult = jsonResults[materialNumber]
172 if "packages" in jsonResult:
173 jsonPackages = jsonResult[
"packages"]
175 self.
logger.info(f
'No packages for material {materialNumber}.')
178 if len(jsonPackages) <= packageId:
179 self.
logger.info(f
'No package for index {packageId}.')
181 package_id = jsonPackages[packageId]
184 self.
logger.info(f
'No package for index {packageId}.')
187 url = f
"{self.package_url}/{package_id}/download"
188 data = requests.get(url).content
190 title = jsonResult[
"title"]
195 Download a package for a given material from the GPUOpen material database.
196 @param searchExpr: The regular expression to match the material name.
197 @param packageId: The package ID to download.
198 @return: A list of downloaded packages of the form:
203 if len(foundList) > 0:
204 for found
in foundList:
205 listNumber = found[
'listNumber']
206 materialNumber = found[
'materialNumber']
207 matName = found[
'title']
208 self.
logger.info(f
'> Download material: {matName} List: {listNumber}. Index: {materialNumber}')
209 result = [data, title] = self.
downloadPackage(listNumber, materialNumber, packageId)
210 downloadList.append(result)
215 Find materials by name.
216 @param materialName: Regular expression to match the material name.
217 @return: A list of materials that match the regular expression of the form:
218 [ { 'listNumber': listNumber, 'materialNumber': materialNumber, 'title': title } ]
227 for material
in materialList[
'results']:
228 if re.match(materialName, material[
'title'], re.IGNORECASE):
229 materialsList.append({
'listNumber': listNumber,
'materialNumber': materialNumber,
'title': material[
'title'] })
237 Update the material names from the material lists.
238 @return: List of material names. If no materials are loaded, then an empty list is returned.
245 for material
in materialList[
'results']:
252 Get the materials returned from the GPUOpen material database.
253 Will loop based on the linked-list of materials stored in the database.
254 Currently the batch size requested is 100 materials per batch.
255 @return: List of material lists
263 'accept':
'application/json'
273 haveMoreMaterials =
True
274 while (haveMoreMaterials):
276 response = requests.get(url, headers=headers, params=params)
278 if response.status_code == HTTPStatus.OK:
280 raw_response = response.text
283 json_strings = raw_response.split(
'}{')
285 json_result_string = json_strings[0]
286 jsonObject = json.loads(json_result_string)
290 nextQuery = jsonObject[
'next']
294 queryParts = nextQuery.split(
'?')
296 queryParts = queryParts[1].split(
'&')
298 limitParts = queryParts[0].split(
'=')
299 offsetParts = queryParts[1].split(
'=')
300 params[
'limit'] = int(limitParts[1])
301 params[
'offset'] = int(offsetParts[1])
302 self.
logger.info(f
'Fetch set of materials: limit: {params["limit"]} offset: {params["offset"]}')
304 haveMoreMaterials =
False
308 self.
logger.info(f
'Error: {response.status_code}, {response.text}')
314 Get the JSON strings for the materials
315 @return: List of JSON strings for the materials. One string per material batch.
322 results.append(json.dumps(material, indent=4, sort_keys=
True))
327 Get list of material file names based on root file name.
328 @param rootName: The root name of the files to load. The files are assumed to be named: rootName_#.json
331 rootName = os.path.basename(rootName)
332 rootDir = os.path.dirname(rootName)
335 print(
'RootDir:', rootDir)
336 print(
'RootName:', rootName)
337 for root, dirs, files
in os.walk(rootDir):
341 if file.startswith(rootName)
and file.endswith(
'.json')
and file[len(rootName):-5].isdigit():
342 filePath = os.path.join(root, file)
343 filePaths.append(filePath)
348 Load the materials from a set of JSON files downloaded from
349 the GPUOpen material database.
352 for fileName
in fileNames:
353 with open(fileName)
as f:
355 results = data[
'results']
356 results_count = len(results)
362 Write the materials to a set of MaterialX files.
363 @param folder: The folder to write the files to.
364 @param rootFileName: The root file name to use for the files.
365 @return: The number of files written.
372 os.makedirs(folder, exist_ok=
True)
375 fileName = rootFileName +
'_' + str(i) +
'.json'
376 materialFileName = os.path.join(folder, fileName)
377 self.
logger.info(f
'> Write material to file: "{materialFileName}"')
378 with open(materialFileName,
'w')
as f:
379 json.dump(material, f, indent=4, sort_keys=
True)
386 Write sorted list of the material names to a file in JSON format
387 @param fileName: The file name to write the material names to.
388 @param sort: If true, sort the material names.
393 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.
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.
int writeMaterialFiles(self, folder, rootFileName)
Write the materials to a set of MaterialX files.
list materialNames
List of material names.
str package_url
URL for the packages.
convertPilImageToBase64(self, image)
Convert a PIL image to a Base64 string.
bool writePackageDataToFile(self, data, outputFolder, title, unzipFile=True)
Write a package data to a file.
downloadPackageByExpression(self, searchExpr, packageId=0)
Download a package for a given material from the GPUOpen material database.
str root_url
Root URL for the GPUOpen material database.
list findMaterialsByName(self, materialName)
Find materials by name.
downloadPackage(self, listNumber, materialNumber, packageId=0)
Download a package for a given material from the GPUOpen material database.
str url
URL for the materials.
extractPackageData(self, data, pilImage)
Extract the package data from a zip file.
list getMaterialsAsJsonString(self)
Get the JSON strings for the materials.
list getMaterialNames(self)
Update the material names from the material lists.
int materials
List of materials.
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.