50 Fetch MaterialX assets from PolyHaven API and filter them by resolution.
52 @param max_items Maximum number of assets to fetch. If None, fetch all.
53 @param download_id If set, only fetch asset with this ID.
54 @param download_type Type of asset to download (e.g. 'mtlx', 'blend', 'gltf'). Default is None which means to fetch MaterialX
55 @return Tuple (materialx_assets, all_assets, filtered_polyhaven_assets):
56 - materialx_assets: Dictionary of MaterialX assets with URLs and texture files.
57 - all_assets: All assets returned by PolyHaven API.
58 - filtered_polyhaven_assets: Filtered assets containing only MaterialX files.
65 resp.raise_for_status()
66 all_assets = resp.json()
71 filtered_polyhaven_assets = {}
86 for id, data
in all_assets.items():
92 if download_id
and id != download_id:
97 thumbnail_url = data.get(
"thumbnail_url")
99 resp = requests.get(f
"{self.FILES_API}/{id}", headers=self.
HEADERS)
100 resp.raise_for_status()
101 files_data = resp.json()
105 blend_files = files_data.get(self.
BLEND_KEY, [])
109 for resolution_key
in resolutions.keys():
110 res = blend_files.get(resolution_key,
None)
117 include_files = n_k_mtlx.get(
"include", {})
118 for path, data
in include_files.items():
119 texture_url = data.get(
"url")
120 texture_struct[path] = texture_url
121 url = n_k_mtlx.get(
"url")
122 res_id = id +
'_' + resolution_key
124 blender_assets[res_id] = {
126 "texture_files": texture_struct,
127 "thumbnail_url": thumbnail_url
130 if download_id == id
and download_type == self.
BLEND_KEY:
133 gltf_files = files_data.get(self.
GLTF_KEY, [])
137 for resolution_key
in resolutions.keys():
138 res = gltf_files.get(resolution_key,
None)
145 include_files = n_k_mtlx.get(
"include", {})
146 for path, data
in include_files.items():
147 texture_url = data.get(
"url")
148 texture_struct[path] = texture_url
149 url = n_k_mtlx.get(
"url")
150 res_id = id +
'_' + resolution_key
152 gltf_assets[res_id] = {
154 "texture_files": texture_struct,
155 "thumbnail_url": thumbnail_url
159 if download_id == id
and download_type == self.
GLTF_KEY:
165 mtlx_files = files_data.get(self.
MTLX_KEY, [])
169 for resolution_key
in resolutions.keys():
170 res = mtlx_files.get(resolution_key,
None)
177 include_files = n_k_mtlx.get(
"include", {})
178 for path, data
in include_files.items():
179 texture_url = data.get(
"url")
180 texture_struct[path] = texture_url
181 mtlx_url = n_k_mtlx.get(
"url")
182 res_id = id +
'_' + resolution_key
184 materialx_assets[res_id] = {
186 "texture_files": texture_struct,
187 "thumbnail_url": thumbnail_url
190 filtered_polyhaven_assets[id] = files_data
192 self.
logger.info(f
"Id: '{id}' has blender: {found_blend}, glTF: {found_gltf}, mtlx: {found_mtlx}")
195 if download_id == id
and download_type == self.
MTLX_KEY:
199 if not download_id
and max_items:
201 if item_count >= max_items:
207 return materialx_assets, all_assets, filtered_polyhaven_assets, blender_assets, gltf_assets
211 Download glTF asset from PolyHaven.
213 @param asset_list Dictionary of glTF assets with URLs and texture files.
214 @return Tuple (id, gltf_ascii, texture_binaries):
215 - id: ID of the downloaded asset.
216 - gltf_ascii: The glTF file as ASCII string.
217 - texture_binaries: List of tuples (path, binary content) for textures and thumbnails
219 for id, asset
in asset_list.items():
220 url = asset.get(
"url")
222 self.
logger.info(f
"No glTF URL found for '{id}'")
224 if not url.endswith(
".gltf")
and not url.endswith(
".glb"):
225 self.
logger.info(f
"Invalid glTF URL for '{id}': {url}")
228 resp = requests.get(url, headers=self.
HEADERS)
229 resp.raise_for_status()
230 if url.endswith(
".glb"):
231 self.
logger.info(f
"Downloaded glTF binary file {url}, size: {len(resp.content)} bytes")
232 gltf_content = resp.content
233 elif url.endswith(
".gltf"):
234 gltf_content = resp.text
235 self.
logger.info(f
"Download glTF file {url}, length: {len(gltf_content)} characters")
237 texture_binaries = []
238 for path, texture_url
in asset.get(
"texture_files", {}).items():
239 self.
logger.info(f
"Download texture from {texture_url} ...")
240 texture_resp = requests.get(texture_url, headers=self.
HEADERS)
241 texture_resp.raise_for_status()
242 texture_content = texture_resp.content
243 texture_binaries.append((path, texture_content))
245 thumbnail_url = asset.get(
"thumbnail_url")
247 self.
logger.info(f
"Download thumbnail from {thumbnail_url} ...")
248 thumbnail_resp = requests.get(thumbnail_url, headers=self.
HEADERS)
249 thumbnail_resp.raise_for_status()
251 clean_url = thumbnail_url
253 clean_url = clean_url.split(
'?')[0].split(
'#')[0]
254 clean_url = clean_url.split(
'/')[-1]
255 extension = Path(clean_url).suffix.lower()
256 texture_binaries.append((f
"{id}_thumbnail{extension}", thumbnail_resp.content))
257 return id, gltf_content, texture_binaries
261 Download Blender asset from PolyHaven.
263 @param asset_list Dictionary of Blender assets with URLs and texture files.
264 @return Tuple (id, blend_binary, texture_binaries):
265 - id: ID of the downloaded asset.
266 - blend_binary: The Blender file as binary content.
267 - texture_binaries: List of tuples (path, binary content) for textures and thumbnails.
269 for id, asset
in asset_list.items():
270 url = asset.get(
"url")
272 self.
logger.info(f
"No Blender URL found for '{id}'")
275 resp = requests.get(url, headers=self.
HEADERS)
276 resp.raise_for_status()
277 blend_binary = resp.content
278 self.
logger.info(f
"Download Blender file {url}, size: {len(blend_binary)} bytes")
280 texture_binaries = []
281 for path, texture_url
in asset.get(
"texture_files", {}).items():
282 self.
logger.info(f
"Download texture from {texture_url} ...")
283 texture_resp = requests.get(texture_url, headers=self.
HEADERS)
284 texture_resp.raise_for_status()
285 texture_content = texture_resp.content
286 texture_binaries.append((path, texture_content))
288 thumbnail_url = asset.get(
"thumbnail_url")
290 self.
logger.info(f
"Download thumbnail from {thumbnail_url} ...")
291 thumbnail_resp = requests.get(thumbnail_url, headers=self.
HEADERS)
292 thumbnail_resp.raise_for_status()
294 clean_url = thumbnail_url
296 clean_url = clean_url.split(
'?')[0].split(
'#')[0]
297 clean_url = clean_url.split(
'/')[-1]
298 extension = Path(clean_url).suffix.lower()
299 texture_binaries.append((f
"{id}_thumbnail{extension}", thumbnail_resp.content))
300 return id, blend_binary, texture_binaries
304 Download MaterialX asset and its textures from PolyHaven.
306 @param asset_list Dictionary of MaterialX assets with URLs and texture files.
307 @param convert_exr_to_png If True, attempt to use PNG images instead of EXR if available other attempt
308 to convert using OpenImageIO if installed. Default is to use PNG if possible, and convert EXR to PNG if OpenImageIO is available.
309 @return Tuple (id, mtlx_string, texture_binaries):
310 - id: ID of the downloaded asset.
311 - mtlx_string: The MaterialX document as a string.
312 - texture_binaries: List of tuples (path, binary content) for textures and thumbnails.
314 for id, asset
in asset_list.items():
315 url = asset.get(
"url")
317 self.
logger.info(f
"No MaterialX URL found for '{id}'")
320 resp = requests.get(url, headers=self.
HEADERS)
321 resp.raise_for_status()
322 mtlx_string = resp.text
323 self.
logger.info(f
"Download MaterialX document {url}, length: {len(mtlx_string)} characters")
325 texture_binaries = []
326 download_texture_names = []
327 for path, texture_url
in asset.get(
"texture_files", {}).items():
329 ext = Path(path).suffix.lower()
330 texture_content =
None
332 if ext ==
".exr" and convert_exr_to_png:
334 old_texture_url = texture_url
335 texture_url = texture_url.replace(
"/exr/",
"/png/").replace(
".exr",
".png")
336 texture_resp = requests.get(texture_url, headers=self.
HEADERS)
337 texture_resp.raise_for_status()
338 texture_content = texture_resp.content
340 self.
logger.info(f
"Download {texture_url} instead of {old_texture_url} SUCCESSFUL")
344 path = str(Path(path).with_suffix(ext))
345 self.
logger.info(f
"- Updating texture path from {old_path} to {path}")
348 self.
logger.info(f
"Download {texture_url} instead of {old_texture_url} FAILED")
350 if not texture_content:
351 self.
logger.info(f
"Download texture from {texture_url} ...")
352 texture_resp = requests.get(texture_url, headers=self.
HEADERS)
353 texture_resp.raise_for_status()
354 texture_content = texture_resp.content
356 name = Path(path).stem
359 if HAVE_OPENIMAGEIO
and convert_exr_to_png:
360 self.
logger.info(f
"Converting EXR to PNG for texture: {path}")
362 with tempfile.NamedTemporaryFile(suffix=
".exr", delete=
False)
as tmp_exr:
363 tmp_exr.write(texture_content)
364 tmp_exr_path = tmp_exr.name
366 inbuf = oiio.ImageInput.open(tmp_exr_path)
369 pixels = inbuf.read_image(format=oiio.UINT8)
372 with tempfile.NamedTemporaryFile(suffix=
".png", delete=
False)
as tmp_png:
373 outbuf = oiio.ImageOutput.create(tmp_png.name)
375 outbuf.open(tmp_png.name, spec)
376 outbuf.write_image(pixels)
379 png_bytes = tmp_png.read()
380 png_name = f
"{name}.png"
381 texture_binaries.append((png_name, png_bytes))
385 self.
logger.info(
"Failed to read EXR with OpenImageIO")
387 os.remove(tmp_exr_path)
388 if 'tmp_png' in locals():
389 os.remove(tmp_png.name)
390 self.
logger.info(f
" WARNING: EXR file present which may not be supported by MaterialX texture loader: {path}")
393 download_texture_name = path.split(
'/')[-1]
394 download_texture_names.append(download_texture_name)
396 texture_binaries.append((path, texture_content))
398 thumbnail_url = asset.get(
"thumbnail_url")
400 self.
logger.info(f
"Download thumbnail from {thumbnail_url} ...")
401 thumbnail_resp = requests.get(thumbnail_url, headers=self.
HEADERS)
402 thumbnail_resp.raise_for_status()
404 clean_url = thumbnail_url
406 clean_url = clean_url.split(
'?')[0].split(
'#')[0]
407 clean_url = clean_url.split(
'/')[-1]
408 extension = Path(clean_url).suffix.lower()
409 texture_binaries.append((f
"{id}_thumbnail{extension}", thumbnail_resp.content))
412 for name
in download_texture_names:
413 extension = Path(name).suffix.lower()
414 exr_name = name.replace(extension,
".exr")
416 mtlx_string = mtlx_string.replace(exr_name, name)
418 before_mtlx_string = mtlx_string
419 mtlx_string = mtlx_string.replace(
".exr",
".png")
420 if (before_mtlx_string != mtlx_string):
421 self.
logger.info(f
"Updated MaterialX string to reference PNG texture instead of EXR for {path}")
424 return id, mtlx_string, texture_binaries
428 Save Blender file and texture binaries to a zip file.
430 @param id The ID of the Blender asset.
431 @param blend_binary The Blender file as binary content.
432 @param texture_binaries List of tuples (path, binary content) for textures and thumbnails.
433 @param data_folder Folder to save the zip file.
434 @param extract_zip If True, extract the zip file after saving zip.
438 filename = f
"{id}_blender.zip"
439 filename = Path(data_folder) / filename
440 with zipfile.ZipFile(filename,
"w")
as zipf:
442 zipf.writestr(f
"{id}.blend", blend_binary)
444 for path, content
in texture_binaries:
445 zipf.writestr(path, content)
446 self.
logger.info(f
"Saved zip: {filename}")
449 extract_folder = Path(data_folder) / f
"{id}_blender"
451 with zipfile.ZipFile(filename,
"r")
as zipf:
452 zipf.extractall(extract_folder)
453 self.
logger.info(f
"Extracted zip contents to folder: {extract_folder}")
457 Save glTF file and texture binaries to a zip file.
459 @param id The ID of the glTF asset.
460 @param gltf_ascii The glTF file as ASCII string.
461 @param texture_binaries List of tuples (path, binary content) for textures and thumbnails.
462 @param data_folder Folder to save the zip file.
463 @param extract_zip If True, extract the zip file after saving zip.
467 filename = f
"{id}_gltf.zip"
468 filename = Path(data_folder) / filename
469 with zipfile.ZipFile(filename,
"w")
as zipf:
471 zipf.writestr(f
"{id}.gltf", gltf_ascii)
473 for path, content
in texture_binaries:
474 zipf.writestr(path, content)
475 self.
logger.info(f
"Saved zip: {filename}")
478 extract_folder = Path(data_folder) / f
"{id}_gltf"
480 with zipfile.ZipFile(filename,
"r")
as zipf:
481 zipf.extractall(extract_folder)
482 self.
logger.info(f
"Extracted zip contents to folder: {extract_folder}")
486 Save MaterialX string and texture binaries to a zip file.
488 @param id The ID of the MaterialX asset.
489 @param mtlx_string The MaterialX string content.
490 @param texture_binaries List of tuples (path, binary content) for textures and thumbnails.
491 @param data_folder Folder to save the zip file.
492 @param extract_zip If True, extract the zip file after saving zip.
496 filename = f
"{id}_materialx.zip"
497 filename = Path(data_folder) / filename
498 with zipfile.ZipFile(filename,
"w")
as zipf:
500 zipf.writestr(f
"{id}.mtlx", mtlx_string)
502 for path, content
in texture_binaries:
503 zipf.writestr(path, content)
504 self.
logger.info(f
"Saved zip: {filename}")
507 extract_folder = Path(data_folder) / f
"{id}_materialx"
509 with zipfile.ZipFile(filename,
"r")
as zipf:
510 zipf.extractall(extract_folder)
511 self.
logger.info(f
"Extracted zip contents to folder: {extract_folder}")