finder-refferenct3/source/main.ts

456 lines
15 KiB
TypeScript
Raw Permalink Normal View History

2024-10-31 02:37:08 +00:00
import ObjectUtil from './util/ObjectUtil';
import FileUtil from './util/FileUtil';
import PJSON from '../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
*/
export const methods: { [key: string]: (...any: any) => any } = {
openDebug() {
console.log('openDebug')
Editor.Panel.open(PJSON.name);
},
/**
* @en A method that can be triggered by message
* @zh message
*/
async findCurrentSelection() {
const uuids = Editor.Selection.getSelected('asset');
if (uuids.length === 0) {
console.log(`[${EXTENSION_NAME}]`, '先选择资源');
return;
}
// 根据 uuid 查找
for (let i = 0; i < uuids.length; i++) {
await this.module.methods.findViaUuid(uuids[i]);
}
},
/**
* 使 uuid
* @param {string} uuid
*/
async findViaUuid(uuid) {
// 是否为有效 uuid
if (!Editor.Utils.UUID.isUUID(uuid)) {
console.log(`[${EXTENSION_NAME}]`, 'invalidUuid' + uuid);
return;
}
// 获取资源信息
const assetInfo = await 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 ? await this.findReferences(uuid) : [];
// Done
this.printResult(results);
} else {
// 不存在的资源,直接查找 uuid
console.log(`[${EXTENSION_NAME}]`, 'findAssetRefs' + uuid);
const result = await 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.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
*/
async findRefsInNode(node, uuid, nodeTree, container) {
// 检查节点上的组件是否有引用
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 = await 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.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++) {
await this.findRefsInNode(children[i], uuid, nodeTree, container);
}
}
},
/**
*
* @param {string} uuid
* @returns {object[]}
*/
async findReferences(uuid) {
const results = [];
/**
*
* @param {string} filePath
* @param {Fs.Stats} stats
*/
const searchHandler = async (filePath, stats) => {
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++) {
await this.findRefsInNode(children[i], uuid, nodeTree, refs);
}
// 保存当前文件引用结果
if (refs.length > 0) {
results.push({
type: typeMap[extname],
fileUrl: await 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.containsValue(curveData, uuid);
if (contains) {
results.push({
type: typeMap[extname],
fileUrl: await 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.containsValue(data, uuid);
if (contains && !(data['uuid'] === uuid)) {
const _extname = (extname === '.mtl') ? '.mtl' : '.fnt.meta';
results.push({
type: typeMap[_extname],
fileUrl: await Editor.Message.request('asset-db', 'query-url', filePath)
});
}
}
};
// 遍历资源目录下的文件
const assetsPath = await Editor.Message.request('asset-db', 'query-path', 'db://assets/');
await FileUtil.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.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
*/
export function load() { }
/**
* @en Method triggered when uninstalling the extension
* @zh
*/
export function unload() { }