45 Fetch MaterialX assets from PolyHaven API and filter them by resolution.
47 @param max_items Maximum number of assets to fetch. If None, fetch all.
48 @param download_id If set, only fetch asset with this ID.
49 @return Tuple (materialx_assets, all_assets, filtered_polyhaven_assets):
50 - materialx_assets: Dictionary of MaterialX assets with URLs and texture files.
51 - all_assets: All assets returned by PolyHaven API.
52 - filtered_polyhaven_assets: Filtered assets containing only MaterialX files.
59 resp.raise_for_status()
60 all_assets = resp.json()
63 filtered_polyhaven_assets = {}
66 for id, data
in all_assets.items():
68 if download_id
and id != download_id:
73 thumbnail_url = data.get(
"thumbnail_url")
75 resp = requests.get(f
"{self.FILES_API}/{id}", headers=self.
HEADERS)
76 resp.raise_for_status()
77 files_data = resp.json()
81 files_data = {k: v
for k, v
in files_data.items()
if k ==
"mtlx"}
83 mtlx_files = files_data.get(
"mtlx", [])
86 self.
logger.info(f
"Found MaterialX data for '{id}'")
95 for resolution_key
in resolutions.keys():
96 res = mtlx_files.get(resolution_key,
None)
100 n_k_mtlx = res.get(
"mtlx")
104 include_files = n_k_mtlx.get(
"include", {})
106 for path, data
in include_files.items():
107 texture_url = data.get(
"url")
109 texture_struct[path] = texture_url
110 mtlx_url = n_k_mtlx.get(
"url")
111 res_id = id +
'_' + resolution_key
113 materialx_assets[res_id] = {
115 "texture_files": texture_struct,
116 "thumbnail_url": thumbnail_url
118 json_string = json.dumps(materialx_assets[res_id], indent=4)
128 filtered_polyhaven_assets[id] = files_data
131 if download_id == id:
135 if not download_id
and max_items:
137 if item_count >= max_items:
143 return materialx_assets, all_assets, filtered_polyhaven_assets
147 Download MaterialX asset and its textures from PolyHaven.
149 @param asset_list Dictionary of MaterialX assets with URLs and texture files.
150 @param convert_exr_to_png If True, attempt to use PNG images instead of EXR if available other attempt
151 to convert using OpenImageIO if installed. Default is to use PNG if possible, and convert EXR to PNG if OpenImageIO is available.
152 @return Tuple (id, mtlx_string, texture_binaries):
153 - id: ID of the downloaded asset.
154 - mtlx_string: The MaterialX document as a string.
155 - texture_binaries: List of tuples (path, binary content) for textures and thumbnails.
157 for id, asset
in asset_list.items():
158 url = asset.get(
"url")
160 self.
logger.info(f
"No MaterialX URL found for '{id}'")
163 resp = requests.get(url, headers=self.
HEADERS)
164 resp.raise_for_status()
165 mtlx_string = resp.text
166 self.
logger.info(f
"Download MaterialX document {url}, length: {len(mtlx_string)} characters")
168 texture_binaries = []
169 download_texture_names = []
170 for path, texture_url
in asset.get(
"texture_files", {}).items():
172 ext = Path(path).suffix.lower()
173 texture_content =
None
175 if ext ==
".exr" and convert_exr_to_png:
177 old_texture_url = texture_url
178 texture_url = texture_url.replace(
"/exr/",
"/png/").replace(
".exr",
".png")
179 texture_resp = requests.get(texture_url, headers=self.
HEADERS)
180 texture_resp.raise_for_status()
181 texture_content = texture_resp.content
183 self.
logger.info(f
"Download {texture_url} instead of {old_texture_url} SUCCESSFUL")
187 path = str(Path(path).with_suffix(ext))
188 self.
logger.info(f
"- Updating texture path from {old_path} to {path}")
191 self.
logger.info(f
"Download {texture_url} instead of {old_texture_url} FAILED")
193 if not texture_content:
194 self.
logger.info(f
"Download texture from {texture_url} ...")
195 texture_resp = requests.get(texture_url, headers=self.
HEADERS)
196 texture_resp.raise_for_status()
197 texture_content = texture_resp.content
199 name = Path(path).stem
202 if HAVE_OPENIMAGEIO
and convert_exr_to_png:
203 self.
logger.info(f
"Converting EXR to PNG for texture: {path}")
205 with tempfile.NamedTemporaryFile(suffix=
".exr", delete=
False)
as tmp_exr:
206 tmp_exr.write(texture_content)
207 tmp_exr_path = tmp_exr.name
209 inbuf = oiio.ImageInput.open(tmp_exr_path)
212 pixels = inbuf.read_image(format=oiio.UINT8)
215 with tempfile.NamedTemporaryFile(suffix=
".png", delete=
False)
as tmp_png:
216 outbuf = oiio.ImageOutput.create(tmp_png.name)
218 outbuf.open(tmp_png.name, spec)
219 outbuf.write_image(pixels)
222 png_bytes = tmp_png.read()
223 png_name = f
"{name}.png"
224 texture_binaries.append((png_name, png_bytes))
228 self.
logger.info(
"Failed to read EXR with OpenImageIO")
230 os.remove(tmp_exr_path)
231 if 'tmp_png' in locals():
232 os.remove(tmp_png.name)
233 self.
logger.info(f
" WARNING: EXR file present which may not be supported by MaterialX texture loader: {path}")
236 download_texture_name = path.split(
'/')[-1]
237 download_texture_names.append(download_texture_name)
239 texture_binaries.append((path, texture_content))
241 thumbnail_url = asset.get(
"thumbnail_url")
243 self.
logger.info(f
"Download thumbnail from {thumbnail_url} ...")
244 thumbnail_resp = requests.get(thumbnail_url, headers=self.
HEADERS)
245 thumbnail_resp.raise_for_status()
247 clean_url = thumbnail_url
249 clean_url = clean_url.split(
'?')[0].split(
'#')[0]
250 clean_url = clean_url.split(
'/')[-1]
251 extension = Path(clean_url).suffix.lower()
252 texture_binaries.append((f
"{id}_thumbnail{extension}", thumbnail_resp.content))
255 for name
in download_texture_names:
256 extension = Path(name).suffix.lower()
257 exr_name = name.replace(extension,
".exr")
259 mtlx_string = mtlx_string.replace(exr_name, name)
261 before_mtlx_string = mtlx_string
262 mtlx_string = mtlx_string.replace(
".exr",
".png")
263 if (before_mtlx_string != mtlx_string):
264 self.
logger.info(f
"Updated MaterialX string to reference PNG texture instead of EXR for {path}")
267 return id, mtlx_string, texture_binaries
271 Save MaterialX string and texture binaries to a zip file.
273 @param id The ID of the MaterialX asset.
274 @param mtlx_string The MaterialX string content.
275 @param texture_binaries List of tuples (path, binary content) for textures and thumbnails.
276 @param data_folder Folder to save the zip file.
277 @param extract_zip If True, extract the zip file after saving zip.
281 filename = f
"{id}_materialx.zip"
282 filename = Path(data_folder) / filename
283 with zipfile.ZipFile(filename,
"w")
as zipf:
285 zipf.writestr(f
"{id}.mtlx", mtlx_string)
287 for path, content
in texture_binaries:
288 zipf.writestr(path, content)
289 self.
logger.info(f
"Saved zip: {filename}")
292 extract_folder = Path(data_folder) / f
"{id}_materialx"
294 with zipfile.ZipFile(filename,
"r")
as zipf:
295 zipf.extractall(extract_folder)
296 self.
logger.info(f
"Extracted zip contents to folder: {extract_folder}")