에픽 게임즈가 언리얼 마켓플레이스 서비스를 대신할 팹(Fab)이라는 새로운 마켓 사이트를 공개했습니다.
2024년 10월 중순, 디지털 에셋 통합 플랫폼인 Fab을 공식적으로 출시하며,
기존의 언리얼 엔진 마켓플레이스와 퀵셀 Bridge를 통한 메가스캔 서비스를 종료하게됩니다.
메가스캔 에셋들은 2024년 말까지 무료로 제공되지만, 2025년부터는 대부분 유료화가 됩니다.
유저들은 올해 말까지 무료로 에셋을 다운로드할 수 있으며, 다운로드한 에셋은 영구적으로 사용할 수 있습니다.
참고로 에셋이 18000개가 넘어가는데, 자동으로 전부 구매하는 스크립트가 있으니 유료화가 되기전에 미리
구매해 놓으면 좋을 것 같습니다.
■ 메가스캔 에셋 자동으로 전부 구매하기
메가스캔 자동 구매하는 방법
1) https://quixel.com/ 에서 로그인 합니다.
2) https://quixel.com/megascans/collections 으로 이동합니다.
3) F12를 눌러 개발자 도구를 켭니다.
4) allow pasting 을 직접 입력해준뒤 엔터를 눌러줍니다. (복사, 붙여넣기를 가능하게 해주는 명령어)
엔터를 치면, 다음과 같은 오류 메세지가 뜨면서 붙여넣기가 바로 안되는 현상이 있는데,
무시하고 allow pasting 을 다시 한번 입력해준 후 엔터를 눌러주세요!
5) 아래 스크립트를 모두 복사한뒤 엔터를 눌러줍니다.
(async (startPage = 0, autoClearConsole = true) => {
const getCookie = (name) => {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
};
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const fetchWithTimeout = (resource, options = {}) => {
const { timeout = 10000 } = options;
return Promise.race([
fetch(resource, options),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Request timeout')), timeout)
),
]);
};
const callCacheApi = async (params = {}) => {
const defaultParams = {
page: 0,
maxValuesPerFacet: 1000,
hitsPerPage: 1000,
attributesToRetrieve: ["id", "name"].join(","),
};
const fetchData = async () => {
const response = await fetchWithTimeout("https://proxy-algolia-prod.quixel.com/algolia/cache", {
headers: {
"x-api-key": "2Zg8!d2WAHIUW?pCO28cVjfOt9seOWPx@2j",
},
body: JSON.stringify({
url: "https://6UJ1I5A072-2.algolianet.com/1/indexes/assets/query?x-algolia-application-id=6UJ1I5A072&x-algolia-api-key=e93907f4f65fb1d9f813957bdc344892",
params: new URLSearchParams({ ...defaultParams, ...params }).toString(),
}),
method: "POST",
});
if (!response.ok) {
throw new Error(`Error fetching from Cache API: ${response.statusText}`);
}
return await response.json();
};
return await retryOperation(fetchData, 2000, 5);
};
const callAcl = async ({ id, name }) => {
const fetchData = async () => {
const response = await fetchWithTimeout("https://quixel.com/v1/acl", {
headers: {
authorization: "Bearer " + authToken,
"content-type": "application/json;charset=UTF-8",
},
body: JSON.stringify({ assetID: id }),
method: "POST",
});
if (!response.ok) {
throw new Error(`Error adding item ${id} | ${name}: ${response.statusText}`);
}
const json = await response.json();
if (json?.isError) {
console.error(` --> **Failed to add item** Item ${id} | ${name} (${json?.msg})`);
} else {
console.log(` --> Added item ${id} | ${name}`);
}
};
return await retryOperation(fetchData, 2000, 5);
};
const callAcquired = async () => {
const fetchData = async () => {
const response = await fetchWithTimeout("https://quixel.com/v1/assets/acquired", {
headers: {
authorization: "Bearer " + authToken,
"content-type": "application/json;charset=UTF-8",
},
method: "GET",
});
if (!response.ok) {
throw new Error(`Error fetching acquired items: ${response.statusText}`);
}
return await response.json();
};
return await retryOperation(fetchData, 2000, 5);
};
const retryOperation = async (operation, delay, retries) => {
let lastError;
for (let attempt = 1; attempt <= retries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error;
console.warn(`Attempt ${attempt} failed (${error.message}). Retrying in ${delay}ms...`);
await sleep(delay);
delay *= 2; // Exponential backoff
}
}
throw lastError;
};
let authToken = "";
const initialize = async () => {
console.log("-> Checking Auth API Token...");
try {
const authCookie = getCookie("auth") ?? "{}";
authToken = JSON.parse(decodeURIComponent(authCookie))?.token;
if (!authToken) {
throw new Error("-> Error: Authentication token not found. Please log in again.");
}
} catch (_) {
throw new Error("-> Error: Authentication token not found. Please log in again.");
}
console.log("-> Fetching acquired items...");
acquiredItems = (await callAcquired()).map((a) => a.assetID);
console.log("-> Fetching total number of pages...");
const initialData = await callCacheApi();
totalPages = initialData.nbPages;
itemsPerPage = initialData.hitsPerPage;
totalItems = initialData.nbHits;
console.log("-> ==============================================");
console.log(`-> Total items: ${totalItems}`);
console.log(`-> ${totalPages} total pages with ${itemsPerPage} items per page`);
console.log(`-> Total items to add: ${totalItems - acquiredItems.length}.`);
console.log("-> ==============================================");
if (!confirm(`Click OK to add ${totalItems - acquiredItems.length} items to your account.`)) {
throw new Error("-> Process cancelled by user.");
}
};
let acquiredItems = [];
let totalPages = 0;
let itemsPerPage = 0;
let totalItems = 0;
const MAX_CONCURRENT_REQUESTS = 5;
const mainProcess = async () => {
for (let pageIdx = startPage || 0; pageIdx < totalPages; pageIdx++) {
console.log(`-> ======================= PAGE ${pageIdx + 1}/${totalPages} START =======================`);
console.log("-> Fetching items from page " + (pageIdx + 1) + " ...");
const pageData = await callCacheApi({ page: pageIdx });
const items = pageData.hits;
console.log("-> Adding unacquired items...");
// Filter out already acquired items
const unownedItems = items.filter((i) => !acquiredItems.includes(i.id));
// Save current progress in localStorage
localStorage.setItem('currentPage', pageIdx);
// Limit concurrent requests
const queue = [...unownedItems];
const workers = Array.from({ length: MAX_CONCURRENT_REQUESTS }, async () => {
while (queue.length > 0) {
const item = queue.shift();
try {
await callAcl(item);
} catch (error) {
console.error(`Error with item ${item.id}: ${error.message}`);
}
}
});
await Promise.all(workers);
console.log(`-> ======================= PAGE ${pageIdx + 1}/${totalPages} COMPLETED =======================`);
if (autoClearConsole) console.clear();
}
};
const finalize = async () => {
console.log("-> Fetching new acquisition info...");
const newAcquiredItems = await callAcquired();
const newItemsAcquired = newAcquiredItems.length;
const newTotalCount = (await callCacheApi()).nbHits;
console.log(`-> Completed. Your account now has a total of ${newItemsAcquired} out of ${newTotalCount} items.`);
alert(`-> Your account now has a total of ${newItemsAcquired} out of ${newTotalCount} items.\n\nIf you find some items missing, try refreshing the page and run the script again.`);
};
try {
// Check if progress was saved
const savedPage = localStorage.getItem('currentPage');
if (savedPage !== null) {
startPage = parseInt(savedPage, 10);
console.log(`-> Resuming from page ${startPage + 1}`);
}
await initialize();
await mainProcess();
await finalize();
// Clear progress
localStorage.removeItem('currentPage');
} catch (error) {
console.error(error.message);
console.log("-> The script could not be completed.");
}
})();
6) 위에 복사했던 스크립트를 붙여넣기 한 뒤, 엔터를 누릅니다.
7) 그러면 내 계정에 약 18000개 가량의 에셋 라이센스를 추가하겠다는 알림 창이 뜨는데,
확인을 눌러주면 됩니다.
8) 에셋 자동구매가 시작되고, Console 탭에서 진행상황을 확인할 수 있습니다.
추가되는 에셋들 목록이 계속 올라옵니다.
9) 간혹 화면에 구매되고있는 에셋 목록이 표시 되지 않는 경우도 있는데, 이럴 경우엔 당황하지말고 아래 버튼을 열어서 숫자가 증가하는지만 확인해주면 됩니다. 에셋을 모두 구매하기까지 대략 빠르면 30분~ 1시간 내로 완료됨.
10) 다음과 같은 알림창이 뜨면서 완료 되었다고 나옵니다.
(총 에셋은 18874갠데, 전체 에셋 보다 더 많게 뜨는 경우도 있다고함)
메가스캔 일괄 구매 완료!
전부 완료하면 Bridge에서 Purchased의 숫자가 늘어나 있는 걸 확인할 수 있습니다.
출처:
https://x.com/TJATOMICA/status/1836213808299467191
https://gist.github.com/jamiephan/0c04986c7f2e62d5c87c4e8c8ce115fc#file-run-js
'블렌더 꿀팁' 카테고리의 다른 글
블렌더] 바위 만들기 (0) | 2024.09.22 |
---|---|
블렌더] 3D 블렌더 4.2 에드온 추가하는 방법 (1) | 2024.09.18 |
블렌더] 렌더링(Cycles) 속도 높이는 방법 (0) | 2024.09.07 |
블렌더] 별 만들기 (라이팅) (0) | 2024.09.07 |
블렌더] 천에 텍스처 넣기 (0) | 2024.09.07 |