"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.methods = void 0; exports.load = load; exports.unload = unload; const ObjectUtil_1 = __importDefault(require("./util/ObjectUtil")); const FileUtil_1 = __importDefault(require("./util/FileUtil")); const package_json_1 = __importDefault(require("../package.json")); const Fs = require('fs'); const Path = require('path'); const EXTENSION_NAME = '🔎'; /** 扩展名对应文件类型 */ const typeMap = { '.fire': 'scene', '.prefab': 'prefab', '.anim': 'animation', '.mtl': 'material', '.fnt.meta': 'font', }; /** 类型对应图标 */ const iconMap = { 'scene': '📺', 'prefab': '🧸', 'node': '💾', 'component': '💿', 'property': '🎲', 'asset': '📦', }; const translate = (key) => `H.${key}`; /** * @en Registration method for the main process of Extension * @zh 为扩展的主进程的注册方法 */ exports.methods = { openDebug() { console.log('openDebug'); Editor.Panel.open(package_json_1.default.name); }, /** * @en A method that can be triggered by message * @zh 通过 message 触发的方法 */ findCurrentSelection() { return __awaiter(this, void 0, void 0, function* () { const uuids = Editor.Selection.getSelected('asset'); if (uuids.length === 0) { console.log(`[${EXTENSION_NAME}]`, '先选择资源'); return; } // 根据 uuid 查找 for (let i = 0; i < uuids.length; i++) { yield this.module.methods.findViaUuid(uuids[i]); } }); }, /** * 使用 uuid 进行查找 * @param {string} uuid */ findViaUuid(uuid) { return __awaiter(this, void 0, void 0, function* () { // 是否为有效 uuid if (!Editor.Utils.UUID.isUUID(uuid)) { console.log(`[${EXTENSION_NAME}]`, 'invalidUuid' + uuid); return; } // 获取资源信息 const assetInfo = yield Editor.Message.request('asset-db', 'query-asset-info', uuid); if (assetInfo) { const url = assetInfo.url.replace('db://', ''); // 暂不查找文件夹 if (assetInfo.type === 'folder') { console.log(`[${EXTENSION_NAME}]`, 'notSupportFolder' + url); return; } // 处理文件路径 & 打印头部日志 const urlItems = url.split('/'); if (!urlItems[urlItems.length - 1].includes('.')) { urlItems.splice(urlItems.length - 1); } console.log(`[${EXTENSION_NAME}]`, 'findAssetRefs' + urlItems.join('/')); // 记录子资源 uuid const subUuids = assetInfo ? [] : null; // 资源类型检查 if (assetInfo.type === "cc.ImageAsset") { // 纹理子资源 uuid = uuid + '@f9941'; } else if (assetInfo.type === 'cc.Script') { // 脚本 uuid = Editor.Utils.UUID.compressUUID(uuid, true); } // 查找 const results = uuid ? yield this.findReferences(uuid) : []; // Done this.printResult(results); } else { // 不存在的资源,直接查找 uuid console.log(`[${EXTENSION_NAME}]`, 'findAssetRefs' + uuid); const result = yield this.findReferences(uuid); // Done this.printResult(result); } }); }, /** * 获取对象中是否包含指定值以及相应属性名 * @param {object} object 对象 * @param {any} value 值 * @returns {{ contains: boolean, property: string }} */ getContainsInfo(object, value) { const result = { contains: false, property: null }; // 搜索 const search = (target, parentKey) => { if (ObjectUtil_1.default.isObject(target)) { for (const key in target) { if (target[key] === value) { result.contains = true; result.property = parentKey; return; } search(target[key], key); } } else if (Array.isArray(target)) { for (let i = 0, l = target.length; i < l; i++) { search(target[i], parentKey); } } }; search(object, null); // Done return result; }, /** * 查找节点中的引用 * @param {object} node 目标节点 * @param {string} uuid 查找的 uuid * @param {object} nodeTree 节点所在的节点树 * @param {object[]} container 结果容器 */ findRefsInNode(node, uuid, nodeTree, container) { return __awaiter(this, void 0, void 0, function* () { // 检查节点上的组件是否有引用 const components = node.components; if (components && components.length > 0) { for (let i = 0, l = components.length; i < l; i++) { const info = this.getContainsInfo(components[i], uuid); if (!info.contains) continue; // 资源类型 let type = components[i]['__type__']; // 是否为脚本资源 if (Editor.Utils.UUID.isUUID(type)) { const scriptUuid = Editor.Utils.UUID.decompressUUID(type), assetInfo = yield Editor.Message.request('asset-db', 'query-asset-info', scriptUuid); type = Path.basename(assetInfo.url); } // 处理属性名称 let property = info.property; if (property) { // Label 组件需要特殊处理 if (type === 'cc.Label' && property === '_N$file') { property = 'font'; } else { // 去除属性名的前缀 if (property.startsWith('_N$')) { property = property.replace('_N$', ''); } else if (property[0] === '_') { property = property.substring(1); } } } // 保存结果 container.push({ node: node.path, component: type, property: property }); } } // 检查预制体是否有引用 const prefab = node.prefab; if (prefab) { // 排除预制体自己 if (uuid !== nodeTree['__uuid__']) { const contains = ObjectUtil_1.default.containsValue(prefab, uuid); if (contains) { container.push({ node: node.path }); } } } // 遍历子节点 const children = node.children; if (children && children.length > 0) { for (let i = 0, l = children.length; i < l; i++) { yield this.findRefsInNode(children[i], uuid, nodeTree, container); } } }); }, /** * 查找引用 * @param {string} uuid * @returns {object[]} */ findReferences(uuid) { return __awaiter(this, void 0, void 0, function* () { const results = []; /** * 文件处理函数 * @param {string} filePath 文件路径 * @param {Fs.Stats} stats */ const searchHandler = (filePath, stats) => __awaiter(this, void 0, void 0, function* () { const extname = Path.extname(filePath); // 场景和预制体资源 if (extname === '.fire' || extname === '.prefab') { // 将资源数据转为节点树 const nodeTree = this.getNodeTree(filePath), children = nodeTree.children, refs = []; // 遍历节点 for (let i = 0, l = children.length; i < l; i++) { yield this.findRefsInNode(children[i], uuid, nodeTree, refs); } // 保存当前文件引用结果 if (refs.length > 0) { results.push({ type: typeMap[extname], fileUrl: yield Editor.Message.request('asset-db', 'query-url', filePath), //Editor.assetdb.fspathToUrl(), refs: refs }); } } // 动画资源 else if (extname === '.anim') { const data = JSON.parse(Fs.readFileSync(filePath)), curveData = data['curveData'], contains = ObjectUtil_1.default.containsValue(curveData, uuid); if (contains) { results.push({ type: typeMap[extname], fileUrl: yield Editor.Message.request('asset-db', 'query-url', filePath) }); } } // 材质和字体资源 else if (extname === '.mtl' || filePath.indexOf('.fnt.meta') !== -1) { const data = JSON.parse(Fs.readFileSync(filePath)), contains = ObjectUtil_1.default.containsValue(data, uuid); if (contains && !(data['uuid'] === uuid)) { const _extname = (extname === '.mtl') ? '.mtl' : '.fnt.meta'; results.push({ type: typeMap[_extname], fileUrl: yield Editor.Message.request('asset-db', 'query-url', filePath) }); } } }); // 遍历资源目录下的文件 const assetsPath = yield Editor.Message.request('asset-db', 'query-path', 'db://assets/'); yield FileUtil_1.default.map(assetsPath, searchHandler); // Done return results; }); }, /** * 获取节点树 * @param {string} filePath 文件路径 * @returns {object} */ getNodeTree(filePath) { // 获取缓存 let cache = this.cache; if (!cache) { cache = this.cache = Object.create(null); } // 从缓存中读取 if (!cache[filePath]) { // 将资源数据转为节点树 const data = JSON.parse(Fs.readFileSync(filePath)); cache[filePath] = this.convertToNodeTree(data); } // Done return cache[filePath]; }, /** * 读取节点 * @param {object} source 元数据 * @param {number} id 节点 ID * @param {object} parent 父容器 */ convertNode(source, id, parent) { const node = Object.create(null), sourceData = source[id]; // 基本信息 node['__id__'] = id; node['_name'] = sourceData['_name']; node['__type__'] = sourceData['__type__']; // 路径 const parentPath = parent.path || (parent['_name'] || null); node.path = (parentPath ? `${parentPath}/` : '') + node['_name']; // 组件 const components = sourceData['_components']; if (components && components.length > 0) { const _components = node.components = []; for (let i = 0, l = components.length; i < l; i++) { const sourceComponent = source[components[i]['__id__']]; _components.push(this.extractValidInfo(sourceComponent)); } } // 预制体引用 const prefab = sourceData['_prefab']; if (prefab) { const realPrefab = source[prefab['__id__']]; node.prefab = this.extractValidInfo(realPrefab); } // 子节点 const children = sourceData['_children']; if (children && children.length > 0) { node.children = []; for (let i = 0, l = children.length; i < l; i++) { const childId = children[i]['__id__']; this.convertNode(source, childId, node); } } // 保存数据 parent.children.push(node); }, /** * 提取有效信息(含有 uuid) * @param {object} data 元数据 * @returns {{ __type__: string, _name: string, fileId?: string }} */ extractValidInfo(data) { const info = Object.create(null); // 记录有用的属性 const keys = ['__type__', '_name', 'fileId']; for (let i = 0, l = keys.length; i < l; i++) { const key = keys[i]; if (data[key]) { info[key] = data[key]; } } // 记录包含 uuid 的属性 for (const key in data) { if (ObjectUtil_1.default.containsProperty(data[key], '__uuid__')) { info[key] = data[key]; } } // Done return info; }, /** * 将资源数据转为节点树 * @param {object} source 元数据 * @returns {object} */ convertToNodeTree(source) { const nodeTree = Object.create(null), type = source[0]['__type__']; // 场景资源 if (type === 'cc.SceneAsset') { const sceneId = source[0]['scene']['__id__'], children = source[sceneId]['_children']; nodeTree['__type__'] = 'cc.Scene'; // 类型 nodeTree['__id__'] = sceneId; // ID // 遍历节点 nodeTree.children = []; for (let i = 0, l = children.length; i < l; i++) { const nodeId = children[i]['__id__']; this.convertNode(source, nodeId, nodeTree); } } // 预制体资源 else if (type === 'cc.Prefab') { const uuid = source[source.length - 1]['asset']['__uuid__']; nodeTree['__type__'] = 'cc.Prefab'; // 类型 nodeTree['__uuid__'] = uuid; // uuid // 从根节点开始读取 nodeTree.children = []; const rootId = source[0]['data']['__id__']; this.convertNode(source, rootId, nodeTree); } // Done return nodeTree; }, printResult(results) { if (results.length === 0) { console.log(`[${EXTENSION_NAME}]`, 'noRefs'); console.log(`${'----'.repeat(36)}`); return; } // 添加引用 const nodeRefs = []; let nodeRefsCount = 0; const assetRefs = []; let assetRefsCount = 0; for (let i = 0, l = results.length; i < l; i++) { const result = results[i], type = result.type, url = result.fileUrl.replace('db://', '').replace('.meta', ''); if (type === 'scene' || type === 'prefab') { nodeRefs.push(`   · ${iconMap[type]} [${translate(type)}] ${url}`); const refs = result.refs; for (let j = 0, n = refs.length; j < n; j++) { nodeRefsCount++; { const ref = refs[j]; let item = `       ${iconMap['node']} [${translate('node')}] ${ref.node}`; if (ref.component) { item += `  →  ${iconMap['component']} [${translate('component')}] ${ref.component}`; } if (ref.property) { item += `  →  ${iconMap['property']} [${translate('property')}] ${ref.property}`; } nodeRefs.push(item); } } } else { assetRefsCount++; assetRefs.push(`   · ${iconMap['asset']} [${translate(type)}] ${url}`); } } // 合并 const texts = [`[${EXTENSION_NAME}] ${translate('searchResult')} >>>`]; if (nodeRefs.length > 0) { nodeRefs.unshift(`   📙 ${translate('引用数')} x ${nodeRefsCount}`); texts.push(...nodeRefs); } if (assetRefs.length > 0) { assetRefs.unshift(`   📘 ${translate('资源引用数')} x ${assetRefsCount}`); texts.push(...assetRefs); } // 分隔 texts.push(`${'----'.repeat(36)}`); // 打印到控制台 if (this.expand) { // 逐行打印 for (let i = 0, l = texts.length; i < l; i++) { console.log(texts[i]); } } else { // 单行打印 const content = texts.join('\n'); console.log(content); } } }; /** * @en Method Triggered on Extension Startup * @zh 扩展启动时触发的方法 */ function load() { } /** * @en Method triggered when uninstalling the extension * @zh 卸载扩展时触发的方法 */ function unload() { }