MaterialXWeb 0.0.2
Utilities for using MaterialX Packages with Web clients
Loading...
Searching...
No Matches
JsGPUOpenMaterialLoader.js
1
10// Use global fetch if available (Node.js v18+), otherwise use node-fetch
11const fetch =
12 typeof globalThis.fetch === 'function'
13 ? globalThis.fetch
14 : (...args) => import('node-fetch').then(({ default: fetch }) => fetch(...args));
15const fs = require('fs');
16const MATERIALS_CACHE_FILE = 'gpuopen_materials.json';
17
24 if (JsGPUOpenMaterialLoader.instance) {
25 return JsGPUOpenMaterialLoader.instance;
26 }
27
28 this.rootUrl = 'https://api.matlib.gpuopen.com/api';
29 this.url = `${this.rootUrl}/materials`;
30 this.packageUrl = `${this.rootUrl}/packages`;
31 this.materials = null;
32 this.materialNames = null;
33
34 this.logger = console;
35
36 // Cache the instance
37 JsGPUOpenMaterialLoader.instance = this;
38 }
39
40 loadMaterialsFromCache() {
41 if (fs.existsSync(MATERIALS_CACHE_FILE)) {
42 try {
43 const data = fs.readFileSync(MATERIALS_CACHE_FILE, 'utf8');
44 this.materials = JSON.parse(data);
45 // Rebuild materialNames
46 this.materialNames = [];
47 for (const jsonData of this.materials) {
48 for (const material of jsonData.results) {
49 this.materialNames.push(material['title']);
50 }
51 }
52 this.logger.info(`Loaded GPUOpen materials from cache: ${MATERIALS_CACHE_FILE}`);
53 } catch (e) {
54 this.logger.warn(`Failed to load GPUOpen materials cache: ${e.message}`);
55 }
56 }
57 }
58
64 // Save to cache file after fetching
65 try {
66 fs.writeFileSync(MATERIALS_CACHE_FILE, JSON.stringify(this.materials, null, 2));
67 this.logger.info(`Saved GPUOpen materials to cache: ${MATERIALS_CACHE_FILE}`);
68 } catch (e) {
69 this.logger.warn(`Failed to write GPUOpen materials cache: ${e.message}`);
70 }
71 return this.materials;
72 }
73
79 return this.materialNames;
80 }
81
87 async getMaterials(batchSize = 50) {
88 // 1. Try to load from cache file
89 if (fs.existsSync(MATERIALS_CACHE_FILE)) {
90 try {
91 const data = fs.readFileSync(MATERIALS_CACHE_FILE, 'utf8');
92 this.materials = JSON.parse(data);
93 // Rebuild materialNames
94 this.materialNames = [];
95 for (const jsonData of this.materials) {
96 for (const material of jsonData.results) {
97 this.materialNames.push(material['title']);
98 }
99 }
100 this.logger.info(`Loaded GPUOpen materials from cache: ${MATERIALS_CACHE_FILE}`);
101 return this.materials;
102 } catch (e) {
103 this.logger.warn(`Failed to load GPUOpen materials cache: ${e.message}`);
104 }
105 }
106
107 // 2. If not in cache, fetch from network
108 this.materials = [];
109 this.materialNames = [];
110
111 let url = this.url;
112 let params = new URLSearchParams({
113 limit: batchSize,
114 offset: 0
115 });
116 if (params) {
117 url += '?' + params.toString();
118 }
119
120 let haveMoreMaterials = true;
121 while (haveMoreMaterials) {
122 try {
123 console.log('Fetch materials from url:', url);
124 const response = await fetch(url);
125 if (!response.ok) {
126 throw new Error(`HTTP error! status: ${response.status}`);
127 }
128 const jsonData = await response.json();
129 this.materials.push(jsonData);
130 for (const material of jsonData.results) {
131 this.materialNames.push(material['title']);
132 }
133 let nextURL = jsonData.next;
134 if (nextURL) {
135 url = nextURL;
136 haveMoreMaterials = true;
137 } else {
138 console.log('Finished fetching materials');
139 haveMoreMaterials = false;
140 break;
141 }
142 } catch (error) {
143 this.logger.info(`Error: ${error.message}`);
144 haveMoreMaterials = true;
145 }
146 }
147 // Save to cache file after fetching
148 try {
149 fs.writeFileSync(MATERIALS_CACHE_FILE, JSON.stringify(this.materials, null, 2));
150 this.logger.info(`Saved GPUOpen materials to cache: ${MATERIALS_CACHE_FILE}`);
151 } catch (e) {
152 this.logger.warn(`Failed to write GPUOpen materials cache: ${e.message}`);
153 }
154 return this.materials;
155 }
156
157 async downloadPackage(listNumber, materialNumber, packageId = 0) {
158 if (this.materials === null || this.materials.length === 0) {
159 return [null, null];
160 }
161
162 const jsonData = this.materials[listNumber];
163 if (!jsonData) {
164 return [null, null];
165 }
166
167 let jsonResults = null;
168 let jsonResult = null;
169 if ('results' in jsonData) {
170 jsonResults = jsonData['results'];
171 if (jsonResults.length <= materialNumber) {
172 return [null, null];
173 } else {
174 jsonResult = jsonResults[materialNumber];
175 }
176 }
177
178 if (!jsonResult) {
179 return [null, null];
180 }
181
182 let jsonPackages = null;
183 if ('packages' in jsonResult) {
184 jsonPackages = jsonResult['packages'];
185 }
186 if (!jsonPackages) {
187 return [null, null];
188 }
189
190 if (jsonPackages.length <= packageId) {
191 return [null, null];
192 }
193 const packageIdValue = jsonPackages[packageId];
194
195 if (!packageIdValue) {
196 return [null, null];
197 }
198
199 const url = `${this.packageUrl}/${packageIdValue}/download`
200 const response = await fetch(url);
201 const data = await response.arrayBuffer();
202 const title = jsonResult['title'];
203
204 console.log(`- Loader: Downloaded package: ${title} from ${url}`);
205 return [data, title];
206 }
207
214 findMaterialsByName(materialName)
215 {
216 if (!this.materials) {
217 return [];
218 }
219
220 const materialsList = [];
221 let listNumber = 0;
222
223 // Create a RegExp object for case-insensitive matching
224 const regex = new RegExp(materialName, 'i');
225
226 this.materials.forEach((materialList, materialIndex) => {
227 let materialNumber = 0;
228
229 materialList['results'].forEach((material) => {
230
231 //console.log('testing material:', material['title'], ' with regex:', regex)
232 if (regex.test(material['title'])) {
233 materialsList.push({
234 listNumber: listNumber,
235 materialNumber: materialNumber,
236 title: material['title'],
237 });
238 }
239 materialNumber += 1;
240 });
241
242 listNumber += 1;
243 });
244
245 return materialsList;
246 }
247
256 async downloadPackageByExpression(searchExpr, packageId = 0) {
257 const downloadList = [];
258
259 const foundList = this.findMaterialsByName(searchExpr);
260 if (foundList.length > 0) {
261 for (const found of foundList) {
262 const listNumber = found['listNumber'];
263 const materialNumber = found['materialNumber'];
264 const matName = found['title'];
265 this.logger.info(`> Download material: ${matName} List: ${listNumber}. Index: ${materialNumber}`);
266 const [data, title] = await this.downloadPackage(listNumber, materialNumber, packageId);
267 downloadList.push([data, title]);
268 }
269 }
270 return downloadList;
271 }
272}
273
274// Export a singleton instance of the class
275module.exports = new JsGPUOpenMaterialLoader();
276
Class to download MaterialX materials from the GPUOpen material database.
async getMaterials(batchSize=50)
Get lists of materials from the GPUOpen material database.
findMaterialsByName(materialName)
Find materials by name.
getMaterialList()
Return downloaded material list.
getMaterialNames()
Return downloaded material names.
constructor()
Constructor for the JsGPUOpenMaterialLoader class.
async downloadPackageByExpression(searchExpr, packageId=0)
Download a material package by string expression.