load3d.ts 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. import JSZip from 'jszip';
  2. import { s8 } from './random';
  3. import axios from 'axios';
  4. import CryptoJS from 'crypto-js';
  5. import { deepClone } from '@meta2d/core';
  6. export const MESH_SUFFIXES = ['.glb', '.l3db'];
  7. export const TEMPLATE_SUFFIXES = ['.l3dm'];
  8. export const IMAGE_SUFFIXES = ['.png', '.jpg', '.jpeg', '.svg', '.bmp', '.gif'];
  9. export const HDR_SUFFIXES = ['.hdr', '.dds', '.env'];
  10. export async function load3d(zip: JSZip, key: string) {
  11. const _filename = key.split('/')[0];
  12. const dataStr = await zip.file(key).async('string');
  13. const { data, map } = JSON.parse(dataStr);
  14. const newUrlMap: { [oldUrl: string]: string } = {};
  15. await Promise.all(
  16. Object.keys(map).map(async (url) => {
  17. try {
  18. const file = await zip
  19. .file(_filename + '/files/' + map[url])
  20. ?.async('arraybuffer');
  21. if (file) {
  22. const names = filename(url, true).split('.');
  23. names.splice(-1, 0, s8());
  24. const name = names.join('.');
  25. const newFile = new File([file], name);
  26. const suffix = '.' + names[names.length - 1];
  27. let directory = '';
  28. let type = '';
  29. let tags = '';
  30. if (MESH_SUFFIXES.includes(suffix)) {
  31. directory = '/3D/模型/未分类';
  32. type = '3D模型';
  33. tags = '我的模型';
  34. } else if (TEMPLATE_SUFFIXES.includes(suffix)) {
  35. directory = '/3D/面板/未分类';
  36. type = 'L3DM';
  37. tags = '我的面板';
  38. } else if (IMAGE_SUFFIXES.includes(suffix)) {
  39. directory = '/3D/图片/未分类';
  40. type = '图片';
  41. tags = '我的图片';
  42. } else if (HDR_SUFFIXES.includes(suffix)) {
  43. directory = '/3D/HDR/未分类';
  44. type = 'HDR';
  45. tags = '我的HDR';
  46. }
  47. if (directory) {
  48. const res:any = await uploadFile({
  49. file: newFile,
  50. directory,
  51. type,
  52. tags,
  53. });
  54. if (res) {
  55. newUrlMap[url] = res.url;
  56. // uploaded++;
  57. return;
  58. }
  59. }
  60. }
  61. // failed++;
  62. } catch (e) {
  63. // failed++;
  64. } finally {
  65. }
  66. })
  67. );
  68. const newData = meta3dReplaceUrl(data, newUrlMap);
  69. //上传
  70. const projectData = meta3dCrypto(JSON.stringify(newData));
  71. const res: any = await axios.post('/api/data/3d/add', {
  72. data: {
  73. data: projectData,
  74. scenes: [],
  75. updatedCount: 1,
  76. },
  77. image: 'xxx',
  78. name: newData.name,
  79. });
  80. if (res) {
  81. return res.id;
  82. } else {
  83. return '';
  84. }
  85. }
  86. export function s16() {
  87. let chars = [
  88. '0',
  89. '1',
  90. '2',
  91. '3',
  92. '4',
  93. '5',
  94. '6',
  95. '7',
  96. '8',
  97. '9',
  98. 'A',
  99. 'B',
  100. 'C',
  101. 'D',
  102. 'E',
  103. 'F',
  104. 'G',
  105. 'H',
  106. 'I',
  107. 'J',
  108. 'K',
  109. 'L',
  110. 'M',
  111. 'N',
  112. 'O',
  113. 'P',
  114. 'Q',
  115. 'R',
  116. 'S',
  117. 'T',
  118. 'U',
  119. 'V',
  120. 'W',
  121. 'X',
  122. 'Y',
  123. 'Z',
  124. 'a',
  125. 'b',
  126. 'c',
  127. 'd',
  128. 'e',
  129. 'f',
  130. 'g',
  131. 'h',
  132. 'i',
  133. 'j',
  134. 'k',
  135. 'l',
  136. 'm',
  137. 'n',
  138. 'o',
  139. 'p',
  140. 'q',
  141. 'r',
  142. 's',
  143. 't',
  144. 'u',
  145. 'v',
  146. 'w',
  147. 'x',
  148. 'y',
  149. 'z',
  150. ];
  151. let strs = '';
  152. for (let i = 0; i < 16; i++) {
  153. let id = Math.ceil(Math.random() * 61);
  154. strs += chars[id];
  155. }
  156. return strs;
  157. }
  158. const a = (word: string, k: string, i: string) => {
  159. let srcs = CryptoJS.enc.Utf8.parse(word);
  160. let encrypted = CryptoJS.AES.encrypt(srcs, CryptoJS.enc.Utf8.parse(k), {
  161. iv: CryptoJS.enc.Utf8.parse(i),
  162. mode: CryptoJS.mode.CBC,
  163. padding: CryptoJS.pad.Pkcs7,
  164. });
  165. return encrypted.ciphertext.toString().toUpperCase();
  166. };
  167. const c = (word: string): string => {
  168. const k = s16().toUpperCase();
  169. const i = s16().toUpperCase();
  170. const d = a(word, k, i);
  171. return k + d + i;
  172. };
  173. const meta3dCrypto = (data: string) => {
  174. return c(data);
  175. };
  176. export const getUrlWithoutSearch = (url: string) => {
  177. return url.split('?')[0];
  178. };
  179. export function filename(str: string, withSuffix = false) {
  180. const paths = getUrlWithoutSearch(str).split('/');
  181. const name = paths[paths.length - 1];
  182. if (withSuffix) {
  183. return name;
  184. }
  185. const i = name.lastIndexOf('.');
  186. if (i === -1) {
  187. return name;
  188. }
  189. return name.substring(0, i);
  190. }
  191. export const uploadFile = async (info: {
  192. file: File;
  193. directory: string;
  194. type: UploadFileType;
  195. tags?: string;
  196. remarks?: string;
  197. name?: string;
  198. }) => {
  199. const { file, directory, type, tags = '', remarks = '', name = '' } = info;
  200. const formData = new FormData();
  201. formData.append('file', file);
  202. formData.append('directory', directory);
  203. formData.append('type', type);
  204. formData.append('tags', tags);
  205. formData.append('remarks', remarks);
  206. formData.append('shared', 'true');
  207. if (name) {
  208. formData.append('name', name);
  209. }
  210. const res = (await axios.post(
  211. '/api/file/upload',
  212. formData
  213. )) as ResponseResult<FileData>;
  214. if (res.error) {
  215. }
  216. return res;
  217. };
  218. export type UploadFileType =
  219. | '文档'
  220. | '图片'
  221. | '3D模型'
  222. | '视频'
  223. | '音乐'
  224. | '其他'
  225. | string;
  226. export interface ResponseResult<T> {
  227. success: boolean;
  228. data?: T;
  229. error?: string;
  230. detail?: string;
  231. }
  232. export interface FileData {
  233. createdAt: string;
  234. directory: string;
  235. fullname: string;
  236. id: string;
  237. name: string;
  238. ownerId: string;
  239. ownerName: string;
  240. shared: boolean;
  241. size: number;
  242. tags: string[];
  243. type: UploadFileType;
  244. updatedAt: string;
  245. url: string;
  246. }
  247. const b = (word: string, k: string, i: string) => {
  248. let encryptedHexStr = CryptoJS.enc.Hex.parse(word);
  249. let srcs = CryptoJS.enc.Base64.stringify(encryptedHexStr);
  250. let decrypt = CryptoJS.AES.decrypt(srcs, CryptoJS.enc.Utf8.parse(k), {
  251. iv: CryptoJS.enc.Utf8.parse(i),
  252. mode: CryptoJS.mode.CBC,
  253. padding: CryptoJS.pad.Pkcs7,
  254. });
  255. let decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
  256. return decryptedStr.toString();
  257. };
  258. const d = (word: string): string => {
  259. const k = word.substring(0, 16);
  260. const i = word.substring(word.length - 16);
  261. return b(word.substring(16, word.length - 16), k, i);
  262. };
  263. const parseData = (data: Partial<any> | string): Partial<any> => {
  264. if (typeof data === 'string') {
  265. data = data.startsWith('{') ? data : d(data);
  266. return JSON.parse(data);
  267. }
  268. return data;
  269. };
  270. export function filepath(str: string) {
  271. const paths = str.split('/');
  272. paths[paths.length - 1] = '';
  273. return paths.join('/');
  274. }
  275. const meta3dReplaceUrl = (
  276. data: any | string,
  277. urlMap: { [url: string]: string }
  278. ) => {
  279. const _data = parseData(data);
  280. const { scenes } = _data;
  281. const transUrl = (url: string): string => {
  282. if (url in urlMap) {
  283. return urlMap[url];
  284. }
  285. return url;
  286. };
  287. for (const sceneData of scenes) {
  288. if (!sceneData) {
  289. continue;
  290. }
  291. const {
  292. nodes = [],
  293. scene = {},
  294. glbMap = {},
  295. textures = {},
  296. materials = {},
  297. DOMDatas = [],
  298. } = sceneData;
  299. Object.keys(glbMap).forEach((glbId) => {
  300. const { url, name } = glbMap[glbId];
  301. const fullUrl = url + name;
  302. if (fullUrl in urlMap) {
  303. glbMap[glbId].url = filepath(urlMap[fullUrl]);
  304. glbMap[glbId].name = filename(urlMap[fullUrl], true);
  305. }
  306. });
  307. for (const node of [
  308. scene,
  309. ...nodes,
  310. ...DOMDatas,
  311. ...Object.keys(materials).map((id) => materials[id]),
  312. ...Object.keys(textures).map((id) => textures[id]),
  313. ]) {
  314. convertResourceAddress(node, transUrl);
  315. }
  316. }
  317. return _data;
  318. };
  319. const urlProps = [
  320. 'url',
  321. 'imageSource',
  322. 'skyboxUrl',
  323. 'hdrUrl',
  324. 'colorGradingTexture',
  325. 'source',
  326. 'backgroundImage',
  327. ];
  328. function convertResourceAddress(
  329. data: any,
  330. transFn: (url: string) => any,
  331. reset = true
  332. ) {
  333. for (const urlProp of urlProps) {
  334. if (urlProp in data === false) {
  335. continue;
  336. }
  337. const url = data[urlProp] || '';
  338. const newUrl = transFn(url);
  339. if (reset) {
  340. data[urlProp] = newUrl;
  341. }
  342. }
  343. if (data.contents) {
  344. data.contents.forEach((content: any) =>
  345. convertResourceAddress(content, transFn, reset)
  346. );
  347. }
  348. if (data.__initOption) {
  349. convertResourceAddress(data.__initOption, transFn, reset);
  350. }
  351. if (data.children) {
  352. data.children.forEach((child: any) =>
  353. convertResourceAddress(child, transFn, reset)
  354. );
  355. }
  356. }