Files
cc-3-8-x-mcp/cli/src/editor/ops/clone-node.js
T
furao 14c5b00f14 Initial public release: cc-3-8-x-mcp
Cocos Creator 3.8.x MCP bridge extension with a built-in offline CLI.

Components:
- Editor extension: in-process MCP server exposing scene / asset-db /
  preview / local / editor-process-control tools
- stdio router: aggregates multiple editor instances on one machine,
  with shortName dedup
- offline CLI (cocos-mcp-cli): headless prefab read/write + a wrapper
  around the Cocos CLI build

Pure Node.js, zero third-party dependencies. Licensed under Apache-2.0.
2026-06-06 11:33:19 +08:00

151 lines
4.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// clone-node: 深拷贝 source 及其整棵子树,挂到 parent 下
// op: { op: 'clone-node', source: string|{id:N}, parent: string|{id:N}, name: string }
//
// - 为每个新节点/组件分配新 __id__(push 到数组末尾)
// - 为每个新节点和组件生成新 fileIddeterministic,种子基于 source fileId + newName
// - 更新所有内部 _parent 引用指向新副本
// - 新树挂到 parent._children(若 parent 是 stub 则走 mountedChildren
'use strict';
const { isStub, resolveNode } = require('../helpers.js');
const { collectExistingFileIds, uniqueFileId } = require('../id-utils.js');
function execCloneNode(prefabData, op) {
const { elements, rootId } = prefabData;
const { source: sourceSelector, parent: parentSelector, name: newName } = op;
if (typeof newName !== 'string') {
throw new Error(`editPrefab [clone-node]: name 必须是字符串`);
}
const { node: sourceNode, nodeId: sourceId } = resolveNode(prefabData, sourceSelector, 'clone-node');
const { node: parentNode, nodeId: parentId } = resolveNode(prefabData, parentSelector, 'clone-node');
const oldToNew = new Map();
function collectSubtreeNodeIds(nodeId) {
const ids = [nodeId];
const node = elements[nodeId];
if (node && Array.isArray(node._children)) {
for (const childRef of node._children) {
if (typeof childRef.__id__ === 'number') {
ids.push(...collectSubtreeNodeIds(childRef.__id__));
}
}
}
return ids;
}
const subtreeNodeIds = collectSubtreeNodeIds(sourceId);
const allSourceIds = [];
for (const nid of subtreeNodeIds) {
allSourceIds.push(nid);
const n = elements[nid];
if (!n) continue;
if (n._prefab && typeof n._prefab.__id__ === 'number') {
allSourceIds.push(n._prefab.__id__);
}
if (Array.isArray(n._components)) {
for (const cRef of n._components) {
if (typeof cRef.__id__ === 'number') {
const compId = cRef.__id__;
allSourceIds.push(compId);
const comp = elements[compId];
if (comp && comp.__prefab && typeof comp.__prefab.__id__ === 'number') {
allSourceIds.push(comp.__prefab.__id__);
}
}
}
}
}
const uniqueSourceIds = [...new Set(allSourceIds)];
const insertStart = elements.length;
for (let i = 0; i < uniqueSourceIds.length; i++) {
oldToNew.set(uniqueSourceIds[i], insertStart + i);
elements.push(null);
}
let sourceFileId = 'unknown';
if (sourceNode._prefab && typeof sourceNode._prefab.__id__ === 'number') {
const srcPInfo = elements[sourceNode._prefab.__id__];
if (srcPInfo && srcPInfo.fileId) sourceFileId = srcPInfo.fileId;
}
const cloneBaseSeed = `${sourceFileId}#clone#${newName}`;
const cloneExistingFileIds = collectExistingFileIds(elements);
let cloneGenCounter = 0;
function cloneGen() {
const subSeed = `${cloneBaseSeed}#slot${cloneGenCounter++}`;
return uniqueFileId(subSeed, cloneExistingFileIds);
}
function cloneObj(obj) {
if (obj === null || obj === undefined) return obj;
if (typeof obj !== 'object') return obj;
if (Array.isArray(obj)) return obj.map(cloneObj);
if (typeof obj.__id__ === 'number') {
const newId = oldToNew.get(obj.__id__);
if (newId !== undefined) return { __id__: newId };
return { ...obj };
}
const result = {};
for (const k of Object.keys(obj)) {
result[k] = cloneObj(obj[k]);
}
return result;
}
for (const oldId of uniqueSourceIds) {
const newId = oldToNew.get(oldId);
const srcObj = elements[oldId];
if (!srcObj) {
elements[newId] = null;
continue;
}
const cloned = cloneObj(srcObj);
if (cloned.__type__ === 'cc.PrefabInfo') {
cloned.fileId = cloneGen();
cloned.root = { __id__: rootId };
cloned.asset = { __id__: 0 };
cloned.instance = null;
cloned.targetOverrides = null;
cloned.nestedPrefabInstanceRoots = null;
}
if (cloned.__type__ === 'cc.CompPrefabInfo') {
cloned.fileId = cloneGen();
}
elements[newId] = cloned;
}
const newRootId = oldToNew.get(sourceId);
const newRootNode = elements[newRootId];
newRootNode._name = newName;
newRootNode._parent = { __id__: parentId };
if (isStub(elements, parentNode)) {
const prefabRef = parentNode._prefab;
const parentPrefabInfo = elements[prefabRef.__id__];
const instanceRef = parentPrefabInfo.instance;
const prefabInstance = elements[instanceRef.__id__];
if (!Array.isArray(prefabInstance.mountedChildren)) {
prefabInstance.mountedChildren = [];
}
prefabInstance.mountedChildren.push({ __id__: newRootId });
} else {
if (!Array.isArray(parentNode._children)) {
parentNode._children = [];
}
parentNode._children.push({ __id__: newRootId });
}
return newRootId;
}
module.exports = { execCloneNode };