Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions e2e/mcp/api/component.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,4 +239,107 @@ describe('MCP Component API', () => {
expect(allResult.data).toEqual(expect.arrayContaining(['cc.Label']));
});
});

describe('LODGroup 专用操作', () => {
it('should recalculate empty LODGroup bounds to zero values', async () => {
const addResult = await mcpClient.callTool('scene-add-component', {
addComponentInfo: {
nodePath: testNodePath,
component: 'cc.LODGroup',
},
});
expect(addResult.code).toBe(200);
expect(addResult.data).toBeDefined();
if (!addResult.data) return;

const recalculateResult = await mcpClient.callTool('scene-recalculate-lod-group-bounds', {
options: {
path: addResult.data.path,
record: false,
},
});

expect(recalculateResult.code).toBe(200);
expect(recalculateResult.data).toEqual({
localBoundaryCenter: { x: 0, y: 0, z: 0 },
objectSize: 0,
});
});

it('should reject a non-LODGroup component', async () => {
const addResult = await mcpClient.callTool('scene-add-component', {
addComponentInfo: {
nodePath: testNodePath,
component: 'cc.Label',
},
});
expect(addResult.code).toBe(200);
expect(addResult.data).toBeDefined();
if (!addResult.data) return;

const recalculateResult = await mcpClient.callTool('scene-recalculate-lod-group-bounds', {
options: {
path: addResult.data.path,
record: false,
},
});

expect(recalculateResult.code).toBe(400);
expect(recalculateResult.reason).toContain('component is not cc.LODGroup');
});

it('should insert, query relative height, and erase an LOD level', async () => {
const addResult = await mcpClient.callTool('scene-add-component', {
addComponentInfo: {
nodePath: testNodePath,
component: 'cc.LODGroup',
},
});
expect(addResult.code).toBe(200);
expect(addResult.data).toBeDefined();
if (!addResult.data) return;

const insertResult = await mcpClient.callTool('scene-insert-lod', {
options: {
path: addResult.data.path,
index: 0,
record: false,
},
});
expect(insertResult.code).toBe(200);
expect(insertResult.data?.lodCount).toBe(4);
expect(insertResult.data?.screenUsagePercentages).toHaveLength(4);
expect(insertResult.data?.screenUsagePercentages[0]).toBe(0.25);

const relativeHeightResult = await mcpClient.callTool('scene-query-lod-group-relative-height', {
options: {
path: addResult.data.path,
},
});
expect(relativeHeightResult).toEqual(expect.objectContaining({
code: 200,
data: 0,
}));

const secondInsertResult = await mcpClient.callTool('scene-insert-lod', {
options: {
path: addResult.data.path,
index: 1,
record: false,
},
});
expect(secondInsertResult.code).toBe(200);
expect(secondInsertResult.data?.lodCount).toBe(5);

const eraseResult = await mcpClient.callTool('scene-erase-lod', {
options: {
path: addResult.data.path,
index: 1,
record: false,
},
});
expect(eraseResult.code).toBe(200);
expect(eraseResult.data).toEqual(insertResult.data);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6234,6 +6234,10 @@ export declare interface IComponentService extends IServiceEvents {
setProperty(params: ISetPropertyOptions): Promise<boolean>;
query(params: IQueryComponentOptions | string): Promise<IComponent | null>;
queryAll(): Promise<string[]>;
recalculateLODGroupBounds(options: IRecalculateLODGroupBoundsOptions): Promise<ILODGroupBoundsResult>;
insertLOD(options: IInsertLODOptions): Promise<ILODGroupLevelsResult>;
eraseLOD(options: IEraseLODOptions): Promise<ILODGroupLevelsResult>;
queryLODGroupRelativeHeight(options: IQueryLODGroupRelativeHeightOptions): Promise<number>;
reset(params: IQueryComponentOptions): Promise<boolean>;
queryClasses(options?: IQueryClassesOptions): Promise<{
name: string;
Expand Down Expand Up @@ -6317,6 +6321,11 @@ export declare interface IEngineService extends IServiceEvents {
enterAnimationMode(): void;
exitAnimationMode(): void;
}
export declare interface IEraseLODOptions {
path: string;
index: number;
record?: boolean;
}
export declare interface IExecuteComponentMethodOptions {
path: string;
name: string;
Expand Down Expand Up @@ -6377,9 +6386,23 @@ export declare interface IGizmoService {
hideSelectionRegion(): void;
execGizmoMethods(name: string, funcName: string, params?: any[]): any;
}
export declare interface IInsertLODOptions {
path: string;
index: number;
screenUsagePercentage?: number;
record?: boolean;
}
export declare interface IIsPrefabInstanceParams {
nodePath: string;
}
export declare interface ILODGroupBoundsResult {
localBoundaryCenter: IVec3;
objectSize: number;
}
export declare interface ILODGroupLevelsResult {
lodCount: number;
screenUsagePercentages: number[];
}
export declare interface ImageAssetUserData {
type: ImageImportType;
flipVertical?: boolean;
Expand Down Expand Up @@ -6594,12 +6617,19 @@ export declare interface IQueryClassesOptions {
export declare interface IQueryComponentOptions {
path: string;
}
export declare interface IQueryLODGroupRelativeHeightOptions {
path: string;
}
export declare interface IQueryNodeParams extends INodeDumpOptions {
path: string;
}
export declare interface IQueryNodeTreeParams {
path?: string;
}
export declare interface IRecalculateLODGroupBoundsOptions {
path: string;
record?: boolean;
}
export declare interface IRectSnapConfigData {
enableSnapping: boolean;
snapThreshold: number;
Expand Down
49 changes: 48 additions & 1 deletion src/api/scene/component-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,46 @@ export const SchemaComponent: z.ZodType<IComponentInfo> = SchemaComponentIdentif

export const SchemaQueryAllComponentResult = z.array(z.string()).describe('Collection of all components, including built-in and custom components'); // 所有组件集合,包含内置与自定义组件

// Recalculate LODGroup bounds // 重新计算 LODGroup 包围盒
export const SchemaRecalculateLODGroupBoundsOptions = z.object({
path: z.string().min(1).describe('cc.LODGroup component path, e.g. "Root/LOD/cc.LODGroup"'), // cc.LODGroup 组件路径
record: z.boolean().optional().describe('Whether to record undo, defaults to true'), // 是否记录 undo,默认 true
}).describe('Information required to recalculate cc.LODGroup bounds'); // 重新计算 cc.LODGroup 包围盒所需信息

export const SchemaLODGroupBoundsResult = z.object({
localBoundaryCenter: Vec3Type.describe('Recalculated local boundary center'), // 重算后的局部边界中心
objectSize: z.number().describe('Recalculated object size'), // 重算后的对象尺寸
}).describe('Recalculated cc.LODGroup bounds'); // 重算后的 cc.LODGroup 包围盒

// Insert an LOD level // 插入 LOD 层级
export const SchemaInsertLODOptions = z.object({
path: z.string().min(1).describe('cc.LODGroup component path, e.g. "Root/LOD/cc.LODGroup"'),
index: z.number().int().nonnegative().describe('Insertion index, from 0 through the current lodCount'),
screenUsagePercentage: z.number().gt(0).max(1).optional()
.describe('Screen usage percentage in (0, 1]; omit it to let the engine calculate a value'),
record: z.boolean().optional().describe('Whether to record undo, defaults to true'),
}).describe('Information required to insert an LOD level');

// Erase an LOD level // 删除 LOD 层级
export const SchemaEraseLODOptions = z.object({
path: z.string().min(1).describe('cc.LODGroup component path, e.g. "Root/LOD/cc.LODGroup"'),
index: z.number().int().nonnegative().describe('Index of the LOD level to erase'),
record: z.boolean().optional().describe('Whether to record undo, defaults to true'),
}).describe('Information required to erase an LOD level');

// Query LODGroup relative height // 查询 LODGroup 屏幕相对高度
export const SchemaQueryLODGroupRelativeHeightOptions = z.object({
path: z.string().min(1).describe('cc.LODGroup component path, e.g. "Root/LOD/cc.LODGroup"'),
}).describe('Information required to query the LODGroup relative height under the editor camera');

export const SchemaLODGroupLevelsResult = z.object({
lodCount: z.number().int().nonnegative().describe('Current number of LOD levels'),
screenUsagePercentages: z.array(z.number()).describe('Screen usage percentages in LOD order'),
}).describe('Current cc.LODGroup level state');

export const SchemaLODGroupRelativeHeightResult = z.number().finite()
.describe('Raw relative height under the current editor camera; the value is not clamped to [0, 1]');

export const SchemaComponentResult = z.union([SchemaComponent, z.null()]).describe('Interface returned when getting current component information'); // 获取当前组件信息返回的接口
export const SchemaBooleanResult = z.boolean().describe('Interface return result'); // 接口返回结果

Expand All @@ -130,4 +170,11 @@ export type TQueryComponentOptions = z.infer<typeof SchemaQueryComponent>;
export type TSetPropertyOptions = z.infer<typeof SchemaSetPropertyOptions>;
export type TComponentResult = z.infer<typeof SchemaComponentResult>;
export type TQueryAllComponentResult = z.infer<typeof SchemaQueryAllComponentResult>;
export type TBooleanResult = z.infer<typeof SchemaBooleanResult>;
export type TRecalculateLODGroupBoundsOptions = z.infer<typeof SchemaRecalculateLODGroupBoundsOptions>;
export type TLODGroupBoundsResult = z.infer<typeof SchemaLODGroupBoundsResult>;
export type TInsertLODOptions = z.infer<typeof SchemaInsertLODOptions>;
export type TEraseLODOptions = z.infer<typeof SchemaEraseLODOptions>;
export type TQueryLODGroupRelativeHeightOptions = z.infer<typeof SchemaQueryLODGroupRelativeHeightOptions>;
export type TLODGroupLevelsResult = z.infer<typeof SchemaLODGroupLevelsResult>;
export type TLODGroupRelativeHeightResult = z.infer<typeof SchemaLODGroupRelativeHeightResult>;
export type TBooleanResult = z.infer<typeof SchemaBooleanResult>;
110 changes: 110 additions & 0 deletions src/api/scene/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,27 @@ import {
SchemaQueryAllComponentResult,
SchemaQueryComponent,
SchemaRemoveComponent,
SchemaRecalculateLODGroupBoundsOptions,
SchemaLODGroupBoundsResult,
SchemaInsertLODOptions,
SchemaEraseLODOptions,
SchemaQueryLODGroupRelativeHeightOptions,
SchemaLODGroupLevelsResult,
SchemaLODGroupRelativeHeightResult,

TAddComponentInfo,
TSetPropertyOptions,
TComponentResult,
TQueryAllComponentResult,
TRemoveComponentOptions,
TQueryComponentOptions,
TRecalculateLODGroupBoundsOptions,
TLODGroupBoundsResult,
TInsertLODOptions,
TEraseLODOptions,
TQueryLODGroupRelativeHeightOptions,
TLODGroupLevelsResult,
TLODGroupRelativeHeightResult,
} from './component-schema';

import { description, param, result, title, tool } from '../decorator/decorator.js';
Expand Down Expand Up @@ -134,4 +148,100 @@ export class ComponentApi {
};
}
}

/**
* Recalculate LODGroup bounds // 重新计算 LODGroup 包围盒
*/
@tool('scene-recalculate-lod-group-bounds')
@title('Recalculate LODGroup bounds') // 重新计算 LODGroup 包围盒
@description('Recalculate localBoundaryCenter and objectSize from all Renderers referenced by a cc.LODGroup. The path must identify a cc.LODGroup component, e.g. "Root/LOD/cc.LODGroup". Returns zero values when no valid Renderer exists.') // 根据 LODGroup 引用的 Renderer 重算边界;路径必须指向 cc.LODGroup 组件
@result(SchemaLODGroupBoundsResult)
async recalculateLODGroupBounds(
@param(SchemaRecalculateLODGroupBoundsOptions) options: TRecalculateLODGroupBoundsOptions,
): Promise<CommonResultType<TLODGroupBoundsResult>> {
try {
const bounds = await Scene.Component.recalculateLODGroupBounds(options);
return {
code: COMMON_STATUS.SUCCESS,
data: bounds,
};
} catch (e) {
return {
code: getCommonErrorStatus(e),
reason: e instanceof Error ? e.message : String(e),
};
}
}

/**
* Insert an LOD level // 插入 LOD 层级
*/
@tool('scene-insert-lod')
@title('Insert LOD level') // 插入 LOD 层级
@description('Insert an LOD level into a cc.LODGroup. Index must be from 0 through lodCount, at most 8 levels are allowed, and screenUsagePercentage must be in (0, 1]. Omit screenUsagePercentage to let the engine calculate it.')
@result(SchemaLODGroupLevelsResult)
async insertLOD(
@param(SchemaInsertLODOptions) options: TInsertLODOptions,
): Promise<CommonResultType<TLODGroupLevelsResult>> {
try {
const lodState = await Scene.Component.insertLOD(options);
return {
code: COMMON_STATUS.SUCCESS,
data: lodState,
};
} catch (e) {
return {
code: getCommonErrorStatus(e),
reason: e instanceof Error ? e.message : String(e),
};
}
}

/**
* Erase an LOD level // 删除 LOD 层级
*/
@tool('scene-erase-lod')
@title('Erase LOD level') // 删除 LOD 层级
@description('Erase an LOD level from a cc.LODGroup. Index must identify an existing level, and at least one LOD level must remain.')
@result(SchemaLODGroupLevelsResult)
async eraseLOD(
@param(SchemaEraseLODOptions) options: TEraseLODOptions,
): Promise<CommonResultType<TLODGroupLevelsResult>> {
try {
const lodState = await Scene.Component.eraseLOD(options);
return {
code: COMMON_STATUS.SUCCESS,
data: lodState,
};
} catch (e) {
return {
code: getCommonErrorStatus(e),
reason: e instanceof Error ? e.message : String(e),
};
}
}

/**
* Query LODGroup relative height // 查询 LODGroup 屏幕相对高度
*/
@tool('scene-query-lod-group-relative-height')
@title('Query LODGroup relative height') // 查询 LODGroup 屏幕相对高度
@description('Query the raw screen-relative height of a cc.LODGroup under the current editor camera. Supports perspective and orthographic cameras; the result is not clamped to [0, 1].')
@result(SchemaLODGroupRelativeHeightResult)
async queryLODGroupRelativeHeight(
@param(SchemaQueryLODGroupRelativeHeightOptions) options: TQueryLODGroupRelativeHeightOptions,
): Promise<CommonResultType<TLODGroupRelativeHeightResult>> {
try {
const relativeHeight = await Scene.Component.queryLODGroupRelativeHeight(options);
return {
code: COMMON_STATUS.SUCCESS,
data: relativeHeight,
};
} catch (e) {
return {
code: getCommonErrorStatus(e),
reason: e instanceof Error ? e.message : String(e),
};
}
}
}
Loading
Loading