MaterialXWeb 0.0.2
Utilities for using MaterialX Packages with Web clients
Loading...
Searching...
No Matches
JsAmbientCGLoader.js
1
2// Use global fetch if available (Node.js v18+), otherwise use node-fetch
3const fetch =
4 typeof globalThis.fetch === 'function'
5 ? globalThis.fetch
6 : (...args) => import('node-fetch').then(({ default: fetch }) => fetch(...args));
7const fs = require('fs');
8const { Writable } = require('stream');
9const { createGunzip } = require('zlib');
10const { parse } = require('csv-parse/sync');
11
20 if (AmbientCGLoader.instance) {
21 return AmbientCGLoader.instance;
22 }
23
24 this.logger = console;
25 this.database = {};
26 this.assets = {};
27 this.materials = null;
28 this.materialNames = [];
29 this.csvMaterials = null;
30 this.downloadMaterial = null;
31 this.downloadMaterialFileName = '';
32
33 // Cache the instance
34 AmbientCGLoader.instance = this;
35 }
36
37 loadMaterialsFromCache() {
38 const MATERIALS_CACHE_FILE = 'ambientcg_materials.json';
39 if (fs.existsSync(MATERIALS_CACHE_FILE)) {
40 try {
41 const data = fs.readFileSync(MATERIALS_CACHE_FILE, 'utf8');
42 this.materials = JSON.parse(data);
43 this.logger.info(`Loaded AmbientCG materials from cache: ${MATERIALS_CACHE_FILE}`);
44 } catch (e) {
45 this.logger.warn(`Failed to load AmbientCG materials cache: ${e.message}`);
46 }
47 }
48 }
49
50 setDebugging(debug = true) {
55 this.logger.level = debug ? 'debug' : 'info';
56 }
57
58 getMaterialNames(key = 'assetId') {
64 this.materialNames = [];
65 const uniqueNames = new Set();
66 if (this.materials) {
67 this.materials.forEach(item => uniqueNames.add(item[key]));
68 }
69 this.materialNames = Array.from(uniqueNames).sort();
70 return this.materialNames;
71 }
72
73 writeMaterialList(materialList, filename) {
79 this.logger.info(`Writing material list to file: ${filename}`);
80 fs.writeFileSync(filename, JSON.stringify(materialList, null, 4));
81 }
82
83 buildDownloadAttribute(imageFormat = 'PNG', imageResolution = '1') {
90 return `${imageResolution}K-${imageFormat}`;
91 }
92
93 splitDownloadAttribute(downloadAttribute) {
100 const parts = downloadAttribute.split('-');
101 }
102
108 return {
109 filename: this.downloadMaterialFileName,
110 content: this.downloadMaterial
111 };
112 }
113
118 if (this.downloadMaterial) {
119 this.downloadMaterial = null;
120 }
121 this.downloadMaterialFileName = '';
122 }
123
129 const haveDownload = this.downloadMaterialFileName && this.downloadMaterial;
130 if (!haveDownload) {
131 this.logger.warning('No current material downloaded');
132 return;
133 }
134
135 const filename = `${path}/${this.downloadMaterialFileName}`;
136 fs.writeFileSync(filename, this.downloadMaterial);
137 this.logger.info(`Saved downloaded material to: ${filename}`);
138 }
139
140 async downloadMaterialAsset(assetId, imageFormat = 'PNG', imageResolution = '1', downloadAttributeKey = 'downloadAttribute', downloadLinkKey = 'downloadLink') {
151
152 const items = this.findMaterial(assetId);
153 const target = this.buildDownloadAttribute(imageFormat, imageResolution);
154 let url = '';
155 let downloadAttribute = '';
156
157 items.forEach(item => {
158 downloadAttribute = item[downloadAttributeKey];
159 if (downloadAttribute === target) {
160 url = item[downloadLinkKey];
161 this.logger.info(`Found Asset: ${assetId}. Download Attribute: ${downloadAttribute} -> ${url}`);
162 }
163 });
164
165 if (!url) {
166 this.logger.error(`No download link found for asset: ${assetId}, attribute: ${target}`);
167 return '';
168 }
169
170 this.downloadMaterialFileName = url.split('file=')[1];
171 console.log('>>>> URL:', url, 'Filename:', this.downloadMaterialFileName);
172
173 try {
174 const response = await fetch(url);
175 if (!response.ok) {
176 throw new Error(`HTTP error! Status: ${response.status}`);
177 }
178
179 // Get the response as an ArrayBuffer
180 const arrayBuffer = await response.arrayBuffer();
181
182 // Convert ArrayBuffer to Buffer
183 this.downloadMaterial = Buffer.from(arrayBuffer);
184 this.logger.info(`Material file downloaded: ${this.downloadMaterialFileName}`);
185
186 } catch (error) {
187 this.downloadMaterialFileName = '';
188 this.logger.error(`Error occurred while downloading the file: ${error}`);
189 }
190
191 return this.downloadMaterialFileName;
192 }
193
194 findMaterial(assetId, key = 'assetId') {
201 if (this.materials) {
202 return this.materials.filter(item => item[key] === assetId);
203 }
204 return [];
205 }
206
213 this.materials = JSON.parse(fs.readFileSync(fileName, 'utf8'));
214 this.logger.info(`Loaded materials list from: ${fileName}`);
215 return this.materials;
216 }
217
223 const MATERIALS_CACHE_FILE = 'ambientcg_materials.json';
224 // 1. Try to load from cache file
225 if (fs.existsSync(MATERIALS_CACHE_FILE)) {
226 try {
227 const data = fs.readFileSync(MATERIALS_CACHE_FILE, 'utf8');
228 this.materials = JSON.parse(data);
229 this.logger.info(`Loaded AmbientCG materials from cache: ${MATERIALS_CACHE_FILE}`);
230 return this.materials;
231 } catch (e) {
232 this.logger.warn(`Failed to load AmbientCG materials cache: ${e.message}`);
233 }
234 }
235
236 // 2. If not in cache, fetch from network
237 const headers = { Accept: 'application/csv' };
238 const url = new URL('https://ambientCG.com/api/v2/downloads_csv');
239 url.searchParams.append('method', 'PBRPhotogrammetry');
240 url.searchParams.append('type', 'Material');
241 url.searchParams.append('sort', 'Alphabet');
242
243 this.logger.info('Downloading materials CSV list from network...');
244 try {
245 const response = await fetch(url, { headers });
246 if (response.status === 200) {
247 const csvContent = await response.text();
248 this.csvMaterials = csvContent;
249 this.materials = parse(csvContent, { columns: true });
250 this.logger.info('Downloaded CSV material list as JSON.');
251 // Save to cache file after fetching
252 try {
253 fs.writeFileSync(MATERIALS_CACHE_FILE, JSON.stringify(this.materials, null, 2));
254 this.logger.info(`Saved AmbientCG materials to cache: ${MATERIALS_CACHE_FILE}`);
255 } catch (e) {
256 this.logger.warn(`Failed to write AmbientCG materials cache: ${e.message}`);
257 }
258 } else {
259 this.materials = null;
260 this.logger.warning(`Failed to fetch the CSV material content. HTTP status code: ${response.status}`);
261 }
262 } catch (error) {
263 this.materials = null;
264 this.logger.error(`Error downloading materials list: ${error}`);
265 }
266
267 return this.materials;
268 }
269
275 return this.database;
276 }
277
283 return this.assets;
284 }
285
291 this.database = {};
292 this.assets = null;
293
294 const url = 'https://ambientcg.com/api/v2/full_json';
295 const headers = { Accept: 'application/json' };
296 const params = {
297 method: 'PBRPhotogrammetry',
298 type: 'Material',
299 sort: 'Alphabet',
300 };
301
302 try {
303 const response = await axios.get(url, { headers, params });
304 if (response.status === 200) {
305 this.database = response.data;
306 this.assets = this.database.foundAssets;
307 } else {
308 this.logger.error(`Status: ${response.status}, ${response.data}`);
309 }
310 } catch (error) {
311 this.logger.error(`Error downloading asset database: ${error}`);
312 }
313
314 return this.database;
315 }
316
323 if (!this.database) {
324 this.logger.warning('No database to write');
325 return false;
326 }
327
328 fs.writeFileSync(filename, JSON.stringify(this.database, null, 4));
329 return true;
330 }
331
338 if (!this.mx) {
339 this.logger.error('MaterialX module is required');
340 return [false, ''];
341 }
342
343 if (!doc) {
344 this.logger.warning('MaterialX document is required');
345 return [false, ''];
346 }
347
348 const valid = doc.validate();
349 return [valid, valid ? '' : 'Validation failed'];
350 }
351
352 addComment(doc, commentString) {
358 const comment = doc.addChildOfCategory('comment');
359 comment.setDocString(commentString);
360 }
361
368 if (!this.mx) {
369 this.logger.error('MaterialX module is required');
370 return;
371 }
372
373 const writeOptions = this.mx.XmlWriteOptions();
374 writeOptions.writeXIncludeEnable = false;
375 writeOptions.elementPredicate = this.skipLibraryElement;
376 return this.mx.writeToXmlString(doc, writeOptions);
377 }
378}
379
380module.exports = new AmbientCGLoader();
buildDownloadAttribute(imageFormat='PNG', imageResolution='1')
setDebugging(debug=true)
constructor()
Class to load materials from the AmbientCG site.
async downloadMaterialAsset(assetId, imageFormat='PNG', imageResolution='1', downloadAttributeKey='downloadAttribute', downloadLinkKey='downloadLink')
writeDownloadedMaterialToFile(path='')
addComment(doc, commentString)
findMaterial(assetId, key='assetId')
getMaterialNames(key='assetId')
writeDatabaseToFile(filename)
splitDownloadAttribute(downloadAttribute)
writeMaterialList(materialList, filename)