|
@@ -0,0 +1,359 @@
|
|
|
+import JSZip from 'jszip';
|
|
|
+import { s8 } from './random';
|
|
|
+import axios from 'axios';
|
|
|
+import CryptoJS from 'crypto-js';
|
|
|
+import { deepClone } from '@meta2d/core';
|
|
|
+
|
|
|
+export const MESH_SUFFIXES = ['.glb', '.l3db'];
|
|
|
+export const TEMPLATE_SUFFIXES = ['.l3dm'];
|
|
|
+export const IMAGE_SUFFIXES = ['.png', '.jpg', '.jpeg', '.svg', '.bmp', '.gif'];
|
|
|
+export const HDR_SUFFIXES = ['.hdr', '.dds', '.env'];
|
|
|
+
|
|
|
+export async function load3d(zip: JSZip, key: string) {
|
|
|
+ const _filename = key.split('/')[0];
|
|
|
+ const dataStr = await zip.file(key).async('string');
|
|
|
+ const { data, map } = JSON.parse(dataStr);
|
|
|
+ const newUrlMap: { [oldUrl: string]: string } = {};
|
|
|
+ await Promise.all(
|
|
|
+ Object.keys(map).map(async (url) => {
|
|
|
+ try {
|
|
|
+ const file = await zip
|
|
|
+ .file(_filename + '/files/' + map[url])
|
|
|
+ ?.async('arraybuffer');
|
|
|
+ if (file) {
|
|
|
+ const names = filename(url, true).split('.');
|
|
|
+ names.splice(-1, 0, s8());
|
|
|
+ const name = names.join('.');
|
|
|
+ const newFile = new File([file], name);
|
|
|
+ const suffix = '.' + names[names.length - 1];
|
|
|
+ let directory = '';
|
|
|
+ let type = '';
|
|
|
+ let tags = '';
|
|
|
+ if (MESH_SUFFIXES.includes(suffix)) {
|
|
|
+ directory = '/3D/模型/未分类';
|
|
|
+ type = '3D模型';
|
|
|
+ tags = '我的模型';
|
|
|
+ } else if (TEMPLATE_SUFFIXES.includes(suffix)) {
|
|
|
+ directory = '/3D/面板/未分类';
|
|
|
+ type = 'L3DM';
|
|
|
+ tags = '我的面板';
|
|
|
+ } else if (IMAGE_SUFFIXES.includes(suffix)) {
|
|
|
+ directory = '/3D/图片/未分类';
|
|
|
+ type = '图片';
|
|
|
+ tags = '我的图片';
|
|
|
+ } else if (HDR_SUFFIXES.includes(suffix)) {
|
|
|
+ directory = '/3D/HDR/未分类';
|
|
|
+ type = 'HDR';
|
|
|
+ tags = '我的HDR';
|
|
|
+ }
|
|
|
+ if (directory) {
|
|
|
+ const res = await uploadFile({
|
|
|
+ file: newFile,
|
|
|
+ directory,
|
|
|
+ type,
|
|
|
+ tags,
|
|
|
+ });
|
|
|
+ if (res) {
|
|
|
+ newUrlMap[url] = res.url;
|
|
|
+ // uploaded++;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // failed++;
|
|
|
+ } catch (e) {
|
|
|
+ // failed++;
|
|
|
+ } finally {
|
|
|
+ }
|
|
|
+ })
|
|
|
+ );
|
|
|
+ const newData = meta3dReplaceUrl(data, newUrlMap);
|
|
|
+
|
|
|
+ //上传
|
|
|
+ const projectData = meta3dCrypto(JSON.stringify(newData));
|
|
|
+ const res: any = await axios.post('/api/data/3d/add', {
|
|
|
+ data: {
|
|
|
+ data: projectData,
|
|
|
+ scenes: [],
|
|
|
+ updatedCount: 1,
|
|
|
+ },
|
|
|
+ image: 'xxx',
|
|
|
+ name: newData.name,
|
|
|
+ });
|
|
|
+ if (res) {
|
|
|
+ return res.id;
|
|
|
+ } else {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+}
|
|
|
+export function s16() {
|
|
|
+ let chars = [
|
|
|
+ '0',
|
|
|
+ '1',
|
|
|
+ '2',
|
|
|
+ '3',
|
|
|
+ '4',
|
|
|
+ '5',
|
|
|
+ '6',
|
|
|
+ '7',
|
|
|
+ '8',
|
|
|
+ '9',
|
|
|
+ 'A',
|
|
|
+ 'B',
|
|
|
+ 'C',
|
|
|
+ 'D',
|
|
|
+ 'E',
|
|
|
+ 'F',
|
|
|
+ 'G',
|
|
|
+ 'H',
|
|
|
+ 'I',
|
|
|
+ 'J',
|
|
|
+ 'K',
|
|
|
+ 'L',
|
|
|
+ 'M',
|
|
|
+ 'N',
|
|
|
+ 'O',
|
|
|
+ 'P',
|
|
|
+ 'Q',
|
|
|
+ 'R',
|
|
|
+ 'S',
|
|
|
+ 'T',
|
|
|
+ 'U',
|
|
|
+ 'V',
|
|
|
+ 'W',
|
|
|
+ 'X',
|
|
|
+ 'Y',
|
|
|
+ 'Z',
|
|
|
+ 'a',
|
|
|
+ 'b',
|
|
|
+ 'c',
|
|
|
+ 'd',
|
|
|
+ 'e',
|
|
|
+ 'f',
|
|
|
+ 'g',
|
|
|
+ 'h',
|
|
|
+ 'i',
|
|
|
+ 'j',
|
|
|
+ 'k',
|
|
|
+ 'l',
|
|
|
+ 'm',
|
|
|
+ 'n',
|
|
|
+ 'o',
|
|
|
+ 'p',
|
|
|
+ 'q',
|
|
|
+ 'r',
|
|
|
+ 's',
|
|
|
+ 't',
|
|
|
+ 'u',
|
|
|
+ 'v',
|
|
|
+ 'w',
|
|
|
+ 'x',
|
|
|
+ 'y',
|
|
|
+ 'z',
|
|
|
+ ];
|
|
|
+ let strs = '';
|
|
|
+ for (let i = 0; i < 16; i++) {
|
|
|
+ let id = Math.ceil(Math.random() * 61);
|
|
|
+ strs += chars[id];
|
|
|
+ }
|
|
|
+ return strs;
|
|
|
+}
|
|
|
+
|
|
|
+const a = (word: string, k: string, i: string) => {
|
|
|
+ let srcs = CryptoJS.enc.Utf8.parse(word);
|
|
|
+ let encrypted = CryptoJS.AES.encrypt(srcs, CryptoJS.enc.Utf8.parse(k), {
|
|
|
+ iv: CryptoJS.enc.Utf8.parse(i),
|
|
|
+ mode: CryptoJS.mode.CBC,
|
|
|
+ padding: CryptoJS.pad.Pkcs7,
|
|
|
+ });
|
|
|
+ return encrypted.ciphertext.toString().toUpperCase();
|
|
|
+};
|
|
|
+
|
|
|
+const c = (word: string): string => {
|
|
|
+ const k = s16().toUpperCase();
|
|
|
+ const i = s16().toUpperCase();
|
|
|
+ const d = a(word, k, i);
|
|
|
+ return k + d + i;
|
|
|
+};
|
|
|
+
|
|
|
+const meta3dCrypto = (data: string) => {
|
|
|
+ return c(data);
|
|
|
+};
|
|
|
+
|
|
|
+export const getUrlWithoutSearch = (url: string) => {
|
|
|
+ return url.split('?')[0];
|
|
|
+};
|
|
|
+
|
|
|
+export function filename(str: string, withSuffix = false) {
|
|
|
+ const paths = getUrlWithoutSearch(str).split('/');
|
|
|
+ const name = paths[paths.length - 1];
|
|
|
+ if (withSuffix) {
|
|
|
+ return name;
|
|
|
+ }
|
|
|
+ const i = name.lastIndexOf('.');
|
|
|
+ if (i === -1) {
|
|
|
+ return name;
|
|
|
+ }
|
|
|
+ return name.substring(0, i);
|
|
|
+}
|
|
|
+
|
|
|
+export const uploadFile = async (info: {
|
|
|
+ file: File;
|
|
|
+ directory: string;
|
|
|
+ type: UploadFileType;
|
|
|
+ tags?: string;
|
|
|
+ remarks?: string;
|
|
|
+ name?: string;
|
|
|
+}) => {
|
|
|
+ const { file, directory, type, tags = '', remarks = '', name = '' } = info;
|
|
|
+ const formData = new FormData();
|
|
|
+ formData.append('file', file);
|
|
|
+ formData.append('directory', directory);
|
|
|
+ formData.append('type', type);
|
|
|
+ formData.append('tags', tags);
|
|
|
+ formData.append('remarks', remarks);
|
|
|
+ formData.append('shared', 'true');
|
|
|
+ if (name) {
|
|
|
+ formData.append('name', name);
|
|
|
+ }
|
|
|
+ const res = (await axios.post(
|
|
|
+ '/api/file/upload',
|
|
|
+ formData
|
|
|
+ )) as ResponseResult<FileData>;
|
|
|
+ if (res.error) {
|
|
|
+ }
|
|
|
+ return res.data;
|
|
|
+};
|
|
|
+
|
|
|
+export type UploadFileType =
|
|
|
+ | '文档'
|
|
|
+ | '图片'
|
|
|
+ | '3D模型'
|
|
|
+ | '视频'
|
|
|
+ | '音乐'
|
|
|
+ | '其他'
|
|
|
+ | string;
|
|
|
+
|
|
|
+export interface ResponseResult<T> {
|
|
|
+ success: boolean;
|
|
|
+ data?: T;
|
|
|
+ error?: string;
|
|
|
+ detail?: string;
|
|
|
+}
|
|
|
+
|
|
|
+export interface FileData {
|
|
|
+ createdAt: string;
|
|
|
+ directory: string;
|
|
|
+ fullname: string;
|
|
|
+ id: string;
|
|
|
+ name: string;
|
|
|
+ ownerId: string;
|
|
|
+ ownerName: string;
|
|
|
+ shared: boolean;
|
|
|
+ size: number;
|
|
|
+ tags: string[];
|
|
|
+ type: UploadFileType;
|
|
|
+ updatedAt: string;
|
|
|
+ url: string;
|
|
|
+}
|
|
|
+const b = (word: string, k: string, i: string) => {
|
|
|
+ let encryptedHexStr = CryptoJS.enc.Hex.parse(word);
|
|
|
+ let srcs = CryptoJS.enc.Base64.stringify(encryptedHexStr);
|
|
|
+ let decrypt = CryptoJS.AES.decrypt(srcs, CryptoJS.enc.Utf8.parse(k), {
|
|
|
+ iv: CryptoJS.enc.Utf8.parse(i),
|
|
|
+ mode: CryptoJS.mode.CBC,
|
|
|
+ padding: CryptoJS.pad.Pkcs7,
|
|
|
+ });
|
|
|
+ let decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
|
|
|
+ return decryptedStr.toString();
|
|
|
+};
|
|
|
+
|
|
|
+const d = (word: string): string => {
|
|
|
+ const k = word.substring(0, 16);
|
|
|
+ const i = word.substring(word.length - 16);
|
|
|
+ return b(word.substring(16, word.length - 16), k, i);
|
|
|
+};
|
|
|
+
|
|
|
+const parseData = (data: Partial<any> | string): Partial<any> => {
|
|
|
+ if (typeof data === 'string') {
|
|
|
+ data = data.startsWith('{') ? data : d(data);
|
|
|
+ return JSON.parse(data);
|
|
|
+ }
|
|
|
+ return data;
|
|
|
+};
|
|
|
+
|
|
|
+export function filepath(str: string) {
|
|
|
+ const paths = str.split('/');
|
|
|
+ paths[paths.length - 1] = '';
|
|
|
+ return paths.join('/');
|
|
|
+}
|
|
|
+
|
|
|
+const meta3dReplaceUrl = (
|
|
|
+ data: any | string,
|
|
|
+ urlMap: { [url: string]: string }
|
|
|
+) => {
|
|
|
+ const _data = parseData(data);
|
|
|
+ const { scenes } = _data;
|
|
|
+ const transUrl = (url: string): string => {
|
|
|
+ if (url.startsWith('data:')) {
|
|
|
+ const str = url.substring(5);
|
|
|
+ const [image, ...arr] = str.split('#').reverse();
|
|
|
+ url = arr.reverse().join('#');
|
|
|
+ const newUrl = transUrl(url);
|
|
|
+ return 'data:' + newUrl + '#' + image;
|
|
|
+ }
|
|
|
+ if (url in urlMap) {
|
|
|
+ return urlMap[url];
|
|
|
+ }
|
|
|
+ return url;
|
|
|
+ };
|
|
|
+ const loadFile = (data: any, glbMap: any) => {
|
|
|
+ if (data.__glbUUID && glbMap) {
|
|
|
+ const glbInfo = glbMap[data.__glbUUID];
|
|
|
+ if (glbInfo) {
|
|
|
+ const { url, name } = glbInfo;
|
|
|
+ const fullUrl = url + name;
|
|
|
+ if (fullUrl in urlMap) {
|
|
|
+ glbInfo.url = filepath(urlMap[fullUrl]);
|
|
|
+ glbInfo.name = filename(urlMap[fullUrl], true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const urlProps = [
|
|
|
+ 'url',
|
|
|
+ 'imageSource',
|
|
|
+ 'skyboxUrl',
|
|
|
+ 'hdrUrl',
|
|
|
+ 'colorGradingTexture',
|
|
|
+ ];
|
|
|
+ for (const urlProp of urlProps) {
|
|
|
+ if (urlProp in data === false) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ const url = data[urlProp] || '';
|
|
|
+ data[urlProp] = transUrl(url);
|
|
|
+ }
|
|
|
+ if (data.images?.length) {
|
|
|
+ for (const image of data.images) {
|
|
|
+ const { source } = image;
|
|
|
+ image.source = transUrl(source);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ for (const sceneData of scenes) {
|
|
|
+ if (!sceneData) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ const { nodes = [], scene = {}, glbMap = {}, textures = {} } = sceneData;
|
|
|
+ for (const node of [
|
|
|
+ scene,
|
|
|
+ ...nodes,
|
|
|
+ ...Object.keys(textures).map((id) => textures[id]),
|
|
|
+ ]) {
|
|
|
+ loadFile(node, glbMap);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return _data;
|
|
|
+};
|