43 const categories =
new Set();
45 for (
const [
id, materialData] of Object.entries(rawData)) {
47 if (!materialData.name || !materialData.categories)
continue;
50 let authorsList =
'Author(s): ';
51 const authors = materialData.authors || {};
52 const authorNames = Object.keys(authors);
53 authorsList += authorNames.join(
', ');
55 let max_resolution = materialData.max_resolution
56 let maxresolutionString =
'';
58 maxresolutionString = `${max_resolution[0]} x ${max_resolution[1]}`
63 name: materialData.name,
64 description: authorsList,
65 categories: materialData.categories,
66 tags: materialData.tags || [],
68 maps: materialData.maxresolutionString || {}
71 materials.push(material);
74 material.categories.forEach(cat => categories.add(cat));
77 return { materials, categories };
87 const response = await fetch(`${this.baseUrl}/files/${materialId}`, {
88 headers: {
"User-Agent": this.userAgent }
92 throw new Error(`Failed to fetch MaterialX data
for ${materialId}`);
95 const result = await response.json();
96 console.log(`Fetched MaterialX files
for ${materialId}:`, result);
99 console.error(`Error fetching material files
for ${materialId}:`, error);
184 async
function downloadWithConcurrency(tasks, concurrency = 3) {
186 const queue = tasks.slice();
187 async
function worker() {
188 while (queue.length) {
189 const task = queue.shift();
190 results.push(await task());
193 await Promise.all(Array(concurrency).fill().map(worker));
197 async
function blobToUint8Array(blob) {
198 return new Uint8Array(await blob.arrayBuffer());
202 let filesData, mtlxData, mtlxContent, textureFiles;
203 if (preFetchedData) {
204 mtlxContent = preFetchedData.mtlxContent;
205 textureFiles = preFetchedData.textureFiles;
208 mtlxData = filesData.mtlx?.[resolution]?.mtlx;
209 if (!mtlxData)
throw new Error(`No MaterialX files
for ${resolution}`);
211 textureFiles = mtlxData.include || {};
215 console.log(
'> createMaterialXPackage - fetched MaterialX data:', mtlxData);
221 let texturePaths = [];
224 const texturePromises = Object.entries(textureFiles).map(([path, fileData]) => async () =>
227 console.log(`Processing texture: ${path} from URL: ${fileData.url}`);
229 texturePaths.push(path);
232 const pathParts = path.split(
'/');
233 const localPath = pathParts.join(
'/');
235 blobs[localPath] = await blobToUint8Array(textureBlob);
238 console.error(`Error downloading texture from URI ${path}:`, error);
239 const pathParts = path.split(
'/');
240 const localPath = pathParts.join(
'/');
241 blobs[localPath] = fflate.strToU8(`Error downloading texture: ${error.message}`);
244 await downloadWithConcurrency(texturePromises, 3);
246 for (
const [localPath, blobData] of Object.entries(blobs)) {
247 zip[localPath] = blobData;
252 if (material.thumb_url) {
255 const thumbUrl =
new URL(material.thumb_url);
256 const thumbPath = thumbUrl.pathname.split(
'/').pop();
257 const thumbExt = thumbPath.split(
'.').pop();
259 zip[`${material.id}_thumbnail.${thumbExt}`] = await blobToUint8Array(thumbBlob);
261 console.error(
'Error downloading thumbnail:', error);
266 await Promise.all(texturePromises);
270 zip[`${material.id}.mtlx`] = fflate.strToU8(mtlxContent);
274 zip[
"README.txt"] = fflate.strToU8(
275 `Material: ${material.name}\n` +
276 `Resolution: ${resolution}\n` +
278 `Downloaded: ${
new Date().toISOString()}\n\n` +
279 `Contains the following files:\n` +
280 `- ${material.id}.mtlx\n` +
281 (material.thumb_url ? `- ${material.id}_thumbnail.png\n` :
'') +
282 (texturePaths.length > 0 ? texturePaths.map(t => `- ${t}`).join(
'\n') +
'\n' :
'')
285 for (
const [k, v] of Object.entries(zip)) {
286 console.log(`ZIP entry: ${k}, size: ${v.length || v.size ||
'unknown'}`);
291 const zipped = fflate.zipSync(zip);
295 const blob =
new Blob([zipped], { type:
'application/zip' });
297 console.log(
'MaterialX package created successfully:', blob);
301 console.error(
'Error creating MaterialX package:', error);
316 const mtlxData = filesData.mtlx?.[resolution]?.mtlx;
319 throw new Error(`No MaterialX files found
for ${resolution} resolution`);
325 let textureFileData = mtlxData.include || {};
328 if (textureFormat.length > 0)
330 if (textureFileData && Object.keys(textureFileData).length > 0) {
331 const textureExtension =
"." + textureFormat.toLowerCase();
333 for (
const [path, fileData] of Object.entries(textureFileData)) {
334 const baseName = path.replace(/\\/g,
'/').split(
'/').pop().replace(/\.[^.]+$/,
'');
337 const extRegex =
new RegExp(baseName +
'\\.[a-zA-Z0-9]+',
'g');
338 console.log(`Remapping texture references in MTLX content
for ${baseName}: ${extRegex}`);
339 let prevMtlxContent = mtlxContent;
340 mtlxContent = mtlxContent.replace(extRegex, baseName + textureExtension);
341 if (prevMtlxContent == mtlxContent) {
342 console.warn(`No references found in MTLX content
for texture ${baseName}. There is a mismatch between the MTLX content and the texture file data !`);
346 let extension = path.split(
'.').pop().toLowerCase();
347 if (extension !== textureFormat) {
348 const prevPath = path;
349 const newPath = path.replace(/\.[^.]+$/i, textureExtension);
352 fileData.url = fileData.url.replace(/\.[^.]+$/i, textureExtension);
354 const urlParts = fileData.url.split(
'/');
355 const extFromPath = extension;
356 fileData.url = urlParts.map(part => part.toLowerCase() === extFromPath ? textureFormat : part).join(
'/');
358 delete textureFileData[path];
359 textureFileData[newPath] = fileData;
360 console.log(`>> Remapped texture path: ${prevPath} -> ${newPath}. File data: ${fileData.url}`);
362 console.log(`>> Texture path is already ${textureFormat}: ${path}. File data: ${fileData.url}`);
373 textureFiles: textureFileData
376 console.error(
'Error getting material content:', error);