MaterialXMaterials 0.0.1
Utilities for retrieving materials from remote servers
Loading...
Searching...
No Matches
GPUOpenLoader.py
1'''
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.
4
5See: https://api.matlib.gpuopen.com/api/swagger/ for information on available API calls.
6'''
7
8import requests, json, os, io, re, zipfile, logging # type: ignore
9from http import HTTPStatus
10# Note: MaterialX is not currently a dependency since no MaterialX processing is required.
11#import MaterialX as mx
12
13import io
14import zipfile
15from PIL import Image as pilImage
16
18 '''
19 This class is used to load materials from the GPUOpen material database.
20 See: https://api.matlib.gpuopen.com/api/swagger/ for API information.
21 '''
22 def __init__(self):
23 self.root_url = 'https://api.matlib.gpuopen.com/api'
24 self.url = self.root_url + '/materials'
25 self.package_url = self.root_url + '/packages'
27
28 self.logger = logging.getLogger('GPUO')
29 logging.basicConfig(level=logging.INFO)
30
31 def writePackageDataToFile(self, data, outputFolder, title, unzipFile=True) -> bool:
32 '''
33 Write a package data to a file.
34 @param data: The data to write.
35 @param outputFolder: The output folder to write the file to.
36 @param title: The title of the file.
37 @param unzipFile: If true, the file is unzipped to a folder with the same name
38 as the title.
39 @return: True if the package was written.
40 '''
41 if not data:
42 return False
43
44 if not os.path.exists(outputFolder):
45 os.makedirs(outputFolder)
46
47 if unzipFile:
48 # Assuming `data` is the binary data of the zip file and `title` and `outputFolder` are defined
49 unzipFolder = os.path.join(outputFolder, title)
50
51 # Use BytesIO to handle the data in memory
52 with io.BytesIO(data) as data_io:
53 with zipfile.ZipFile(data_io, 'r') as zip_ref:
54 zip_ref.extractall(unzipFolder)
55
56 self.logger.info(f'Unzipped to folder: "{unzipFolder}"')
57
58 else:
59 outputFile = os.path.join(outputFolder, f"{title}.zip")
60 with open(outputFile, "wb") as f:
61 self.logger.info(f'Write package to file: "{outputFile}"')
62 f.write(data)
63
64 return True
65
66 def extractPackageData(self, data, pilImage):
67 '''
68 Extract the package data from a zip file.
69 @param data: The data to extract.
70 @param pilImage: The PIL image module.
71 @return: A list of extracted data of the form:
72 [ { 'file_name': file_name, 'data': data, 'type': type } ]
73 '''
74 if not pilImage:
75 self.logger.debug('Pillow (PIL) image module provided. Image data will not be extracted.')
76
77 zip_object = io.BytesIO(data)
78
79 extracted_data_list = []
80 with zipfile.ZipFile(zip_object, 'r') as zip_file:
81 # Iterate through the files in the zip archive
82 for file_name in zip_file.namelist():
83 # Extract each file into memory
84 extracted_data = zip_file.read(file_name)
85 if file_name.endswith('.mtlx'):
86 mtlx_string = extracted_data.decode('utf-8')
87 extracted_data_list.append( {'file_name': file_name, 'data': mtlx_string, 'type': 'mtlx'} )
88
89 # If the data is a image, create a image in Python
90 elif file_name.endswith('.png'):
91 if pilImage:
92 image = pilImage.open(io.BytesIO(extracted_data))
93 else:
94 image = None
95 extracted_data_list.append( {'file_name': file_name, 'data': image, 'type': 'image'} )
96
97 return extracted_data_list
98
99 def downloadPackage(self, listNumber, materialNumber, packageId=0):
100 '''
101 Download a package for a given material from the GPUOpen material database.
102 @param listNumber: The list number of the material to download.
103 @param materialNumber: The material number to download.
104 @param packageId: The package ID to download.
105 Packages are numbered starting at 0. Default is 0.
106 with index 0 containing the smallest package (smallest resolution referenced textures).
107 '''
108 if self.materialsmaterialsmaterials == None or len(self.materialsmaterialsmaterials) == 0:
109 return [None, None]
110
111 json_data = self.materialsmaterialsmaterials[listNumber]
112 if not json_data:
113 return [None, None]
114
115 jsonResults = None
116 jsonResult = None
117 if "results" in json_data:
118 jsonResults = json_data["results"]
119 if len(jsonResults) <= materialNumber:
120 return [None, None]
121 else:
122 jsonResult = jsonResults[materialNumber]
123
124 if not jsonResult:
125 return [None, None]
126
127 # Get the package ID
128 jsonPackages = None
129 if "packages" in jsonResult:
130 jsonPackages = jsonResult["packages"]
131 if not jsonPackages:
132 return [None, None]
133
134 if len(jsonPackages) <= packageId:
135 return [None, None]
136 package_id = jsonPackages[packageId]
137
138 if not package_id:
139 return [None, None]
140
141 url = f"{self.package_url}/{package_id}/download"
142 data = requests.get(url).content
143
144 title = jsonResult["title"]
145 return [data, title]
146
147 def downloadPackageByExpression(self, searchExpr, packageId=0):
148 '''
149 Download a package for a given material from the GPUOpen material database.
150 @param searchExpr: The regular expression to match the material name.
151 @param packageId: The package ID to download.
152 @return: A list of downloaded packages of the form:
153 '''
154 downloadList = []
155
156 foundList = self.findMaterialsByName(searchExpr)
157 if len(foundList) > 0:
158 for found in foundList:
159 listNumber = found['listNumber']
160 materialNumber = found['materialNumber']
161 matName = found['title']
162 self.logger.info(f'> Download material: {matName} List: {listNumber}. Index: {materialNumber}')
163 result = [data, title] = self.downloadPackage(listNumber, materialNumber, packageId)
164 downloadList.append(result)
165 return downloadList
166
167 def findMaterialsByName(self, materialName) -> list:
168 '''
169 Find materials by name.
170 @param materialName: Regular expression to match the material name.
171 @return: A list of materials that match the regular expression of the form:
172 [ { 'listNumber': listNumber, 'materialNumber': materialNumber, 'title': title } ]
173 '''
174 if (self.materialsmaterialsmaterials == None):
175 return []
176
177 materialsList = []
178 listNumber = 0
179 materialNumber = 0
180 for materialList in self.materialsmaterialsmaterials:
181 for material in materialList['results']:
182 if re.match(materialName, material['title'], re.IGNORECASE):
183 materialsList.append({ 'listNumber': listNumber, 'materialNumber': materialNumber, 'title': material['title'] })
184 materialNumber += 1
185 listNumber += 1
186
187 return materialsList
188
189 def getMaterialNames(self) -> list:
190 '''
191 Update the material names from the material lists.
192 @return: List of material names. If no materials are loaded, then an empty list is returned.
193 '''
194 self.materialNames = []
195 if (self.materialsmaterialsmaterials == None):
196 return []
197
198 for materialList in self.materialsmaterialsmaterials:
199 for material in materialList['results']:
200 self.materialNames.append(material['title'])
201
202 return self.materialNames
203
204 def getMaterials(self) -> list:
205 '''
206 Get the materials returned from the GPUOpen material database.
207 Will loop based on the linked-list of materials stored in the database.
208 Currently the batch size requested is 100 materials per batch.
209 @return: List of material lists
210 '''
211
213 self.materialNames = []
214
215 url = self.url
216 headers = {
217 'accept': 'application/json'
218 }
219
220 # Get batches of materials. Start with the first 100.
221 # Can apply more filters to this as needed in the future.
222 # This will get every material in the database.
223 params = {
224 'limit': 100,
225 'offset': 0
226 }
227 haveMoreMaterials = True
228 while (haveMoreMaterials):
229
230 response = requests.get(url, headers=headers, params=params)
231
232 if response.status_code == HTTPStatus.OK:
233
234 raw_response = response.text
235
236 # Split the response text assuming the JSON objects are concatenated
237 json_strings = raw_response.split('}{')
238 #self.logger.info('Number of JSON strings:', len(json_strings))
239 json_result_string = json_strings[0]
240 jsonObject = json.loads(json_result_string)
241 self.materialsmaterialsmaterials.append(jsonObject)
242
243 # Scan for next batch of materials
244 nextQuery = jsonObject['next']
245 if (nextQuery):
246 # Get limit and offset from this: 'https://api.matlib.gpuopen.com/api/materials/?limit=100&offset=100"'
247 # Split the string by '?'
248 queryParts = nextQuery.split('?')
249 # Split the string by '&'
250 queryParts = queryParts[1].split('&')
251 # Split the string by '='
252 limitParts = queryParts[0].split('=')
253 offsetParts = queryParts[1].split('=')
254 params['limit'] = int(limitParts[1])
255 params['offset'] = int(offsetParts[1])
256 self.logger.info(f'Fetch set of materials: limit: {params["limit"]} offset: {params["offset"]}')
257 else:
258 haveMoreMaterials = False
259 break
260
261 else:
262 self.logger.info(f'Error: {response.status_code}, {response.text}')
263
264 return self.materialsmaterialsmaterials
265
266 def getMaterialsAsJsonString(self) -> list:
267 '''
268 Get the JSON strings for the materials
269 @return: List of JSON strings for the materials. One string per material batch.
270 '''
271 results : list = []
272
273 if (self.materialsmaterialsmaterials == None):
274 return results
275 for material in self.materialsmaterialsmaterials:
276 results.append(json.dumps(material, indent=4, sort_keys=True))
277 return results
278
279 def readMaterialFiles(self, fileNames) -> list:
280 '''
281 Load the materials from a set of JSON files downloaded from
282 the GPUOpen material database.
283 '''
285 for fileName in fileNames:
286 with open(fileName) as f:
287 data = json.load(f)
288 self.materialsmaterialsmaterials.append(data)
290
291 def writeMaterialFiles(self, folder, rootFileName) -> int:
292 '''
293 Write the materials to a set of MaterialX files.
294 @param folder: The folder to write the files to.
295 @param rootFileName: The root file name to use for the files.
296 @return: The number of files written.
297 '''
298 if (self.materialsmaterialsmaterials == None):
299 return 0
300
301 i = 0
302 if (len(self.materialsmaterialsmaterials) > 0):
303 os.makedirs(folder, exist_ok=True)
304 for material in self.materialsmaterialsmaterials:
305 # Write JSON to file
306 fileName = rootFileName + '_' + str(i) + '.json'
307 materialFileName = os.path.join(folder, fileName)
308 self.logger.info(f'> Write material to file: "{materialFileName}"')
309 with open(materialFileName, 'w') as f:
310 json.dump(material, f, indent=4, sort_keys=True)
311 i += 1
312
313 return i
314
315 def writeMaterialNamesToFile(self, fileName, sort=True):
316 '''
317 Write sorted list of the material names to a file in JSON format
318 @param fileName: The file name to write the material names to.
319 @param sort: If true, sort the material names.
320 '''
321 if (self.materialNames == None):
322 return
323
324 with open(fileName, 'w') as f:
325 json.dump(self.materialNames, f, indent=2, sort_keys=sort)
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 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.
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.
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.
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.
list readMaterialFiles(self, fileNames)
Load the materials from a set of JSON files downloaded from the GPUOpen material database.