freeform-modeling-mcp 1.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +61 -0
  2. package/index.cjs +21 -0
  3. package/package.json +48 -0
  4. package/src/cli.ts +47 -0
  5. package/src/commands/start.ts +88 -0
  6. package/src/commands/status.ts +81 -0
  7. package/src/dev.ts +24 -0
  8. package/src/lib/fileproxy/index.ts +505 -0
  9. package/src/lib/fileproxy/proxy-server.js +347 -0
  10. package/src/lib/gltf2obj/compress.js +33 -0
  11. package/src/lib/gltf2obj/engine/core/core.js +34 -0
  12. package/src/lib/gltf2obj/engine/core/eventnotifier.js +39 -0
  13. package/src/lib/gltf2obj/engine/core/taskrunner.js +88 -0
  14. package/src/lib/gltf2obj/engine/export/exporter.js +38 -0
  15. package/src/lib/gltf2obj/engine/export/exporterbase.js +86 -0
  16. package/src/lib/gltf2obj/engine/export/exportermodel.js +117 -0
  17. package/src/lib/gltf2obj/engine/export/exporterobj.js +147 -0
  18. package/src/lib/gltf2obj/engine/geometry/box3d.js +60 -0
  19. package/src/lib/gltf2obj/engine/geometry/coord2d.js +35 -0
  20. package/src/lib/gltf2obj/engine/geometry/coord3d.js +126 -0
  21. package/src/lib/gltf2obj/engine/geometry/coord4d.js +15 -0
  22. package/src/lib/gltf2obj/engine/geometry/geometry.js +56 -0
  23. package/src/lib/gltf2obj/engine/geometry/matrix.js +440 -0
  24. package/src/lib/gltf2obj/engine/geometry/octree.js +160 -0
  25. package/src/lib/gltf2obj/engine/geometry/quaternion.js +83 -0
  26. package/src/lib/gltf2obj/engine/geometry/transformation.js +63 -0
  27. package/src/lib/gltf2obj/engine/geometry/tween.js +31 -0
  28. package/src/lib/gltf2obj/engine/import/importer.js +270 -0
  29. package/src/lib/gltf2obj/engine/import/importerbase.js +115 -0
  30. package/src/lib/gltf2obj/engine/import/importerfiles.js +139 -0
  31. package/src/lib/gltf2obj/engine/import/importergltf.js +1045 -0
  32. package/src/lib/gltf2obj/engine/import/importerutils.js +102 -0
  33. package/src/lib/gltf2obj/engine/io/binaryreader.js +93 -0
  34. package/src/lib/gltf2obj/engine/io/binarywriter.js +92 -0
  35. package/src/lib/gltf2obj/engine/io/bufferutils.js +85 -0
  36. package/src/lib/gltf2obj/engine/io/externallibs.js +42 -0
  37. package/src/lib/gltf2obj/engine/io/fileutils.js +120 -0
  38. package/src/lib/gltf2obj/engine/io/textwriter.js +41 -0
  39. package/src/lib/gltf2obj/engine/main.js +19 -0
  40. package/src/lib/gltf2obj/engine/model/color.js +130 -0
  41. package/src/lib/gltf2obj/engine/model/generator.js +433 -0
  42. package/src/lib/gltf2obj/engine/model/material.js +243 -0
  43. package/src/lib/gltf2obj/engine/model/mesh.js +173 -0
  44. package/src/lib/gltf2obj/engine/model/meshbuffer.js +233 -0
  45. package/src/lib/gltf2obj/engine/model/meshinstance.js +128 -0
  46. package/src/lib/gltf2obj/engine/model/meshutils.js +64 -0
  47. package/src/lib/gltf2obj/engine/model/model.js +195 -0
  48. package/src/lib/gltf2obj/engine/model/modelfinalization.js +377 -0
  49. package/src/lib/gltf2obj/engine/model/modelutils.js +117 -0
  50. package/src/lib/gltf2obj/engine/model/node.js +178 -0
  51. package/src/lib/gltf2obj/engine/model/object.js +90 -0
  52. package/src/lib/gltf2obj/engine/model/property.js +86 -0
  53. package/src/lib/gltf2obj/engine/model/quantities.js +46 -0
  54. package/src/lib/gltf2obj/engine/model/topology.js +139 -0
  55. package/src/lib/gltf2obj/engine/model/triangle.js +99 -0
  56. package/src/lib/gltf2obj/engine/threejs/threemodelloader.js +85 -0
  57. package/src/lib/gltf2obj/handler.js +63 -0
  58. package/src/lib/gltf2obj/index.js +47 -0
  59. package/src/lib/logger.ts +304 -0
  60. package/src/lib/print.ts +37 -0
  61. package/src/lib/utils.ts +34 -0
  62. package/src/lib/wsbridge/bridge.ts +639 -0
  63. package/src/lib/wsbridge/lock.ts +258 -0
  64. package/src/llmClient.ts +245 -0
  65. package/src/prompt.ts +187 -0
  66. package/src/server.ts +125 -0
  67. package/src/tools/assets-tool.ts +314 -0
  68. package/src/tools/basic-tools.ts +531 -0
  69. package/src/tools/index.ts +2 -0
  70. package/src/tools/material-tools.ts +88 -0
  71. package/src/tools/modeling/auxiliary-curve-tool.ts +44 -0
  72. package/src/tools/modeling/find-face.ts +38 -0
  73. package/src/tools/modeling/sweep-tool.ts +60 -0
  74. package/src/tools/modeling/utils.ts +181 -0
  75. package/src/tools/modeling-tools.ts +135 -0
  76. package/src/tools/screenshot-tools.ts +107 -0
  77. package/src/tools/tools-info.ts +107 -0
  78. package/src/tools/tripo3d-tools.ts +496 -0
  79. package/src/types/index.ts +25 -0
  80. package/src/types/tripo3d.ts +38 -0
  81. package/tsconfig.json +22 -0
@@ -0,0 +1,531 @@
1
+ // src/tools/basic-tools.ts
2
+ import { Context } from '../types';
3
+ import { z } from 'zod';
4
+ import logger from '../lib/logger';
5
+ import { llmClient } from '../llmClient';
6
+
7
+ // getSceneInfo 工具
8
+ export const getSceneInfoTool = {
9
+ name: 'get_scene_info',
10
+ description: 'Get detailed information about the current Koomaster scene',
11
+ parameters: z.object({}),
12
+ execute: async (_args: {}, context: Context): Promise<string> => {
13
+ logger.info(`Tool executed: get_scene_info`);
14
+
15
+ try {
16
+ if (!llmClient.hasConnectedKoomasterClients()) {
17
+ logger.warn('No Koomaster clients connected when attempting to get scene info');
18
+ return "No Koomaster clients connected. Please start the Koomaster addon and connect to this server.";
19
+ }
20
+
21
+ logger.debug(`Sending 'get_scene_info' command to Koomaster`);
22
+ const result = await llmClient.sendCommand("get_scene_info");
23
+ return result
24
+ } catch (e) {
25
+ const errorMessage = `Error getting scene info from Koomaster: ${e instanceof Error ? e.message : String(e)}`;
26
+ logger.error(errorMessage);
27
+ context.log.error(errorMessage);
28
+ return `Error getting scene info: ${e instanceof Error ? e.message : String(e)}`;
29
+ }
30
+ }
31
+ };
32
+ // getObjectsInfo 工具
33
+ export const getObjectsInfoTool = {
34
+ name: 'get_objects_info',
35
+ description: 'Get detailed information about specific objects in the Koomaster scene',
36
+ parameters: z.object({
37
+ names: z.array(z.string()).describe('Array of object names to get information about'),
38
+ type: z.enum(['face', 'group', 'auxiliary_curve']).describe('The type of objects to get information for')
39
+ }),
40
+ execute: async (args: { names: string[], type: 'face' | 'group' | 'auxiliary_curve' }, context: Context): Promise<string> => {
41
+ logger.info(`Tool executed: get_objects_info for ${args.names.length} objects of type "${args.type}"`);
42
+ try {
43
+ if (!llmClient.hasConnectedKoomasterClients()) {
44
+ logger.warn('No Koomaster clients connected when attempting to get objects info');
45
+ return "No Koomaster clients connected. Please start the Koomaster addon and connect to this server.";
46
+ }
47
+
48
+ // 将type字符串映射到ObjectType枚举
49
+
50
+ logger.debug(`Sending 'get_objects_info' command for ${args.names.length} objects of type "${args.type}" to Koomaster`);
51
+ const result = await llmClient.sendCommand("get_objects_info",args);
52
+
53
+ return result;
54
+ } catch (e) {
55
+ const errorMessage = `Error getting info for objects of type "${args.type}" from Koomaster: ${e instanceof Error ? e.message : String(e)}`;
56
+ logger.error(errorMessage);
57
+ context.log.error(errorMessage);
58
+ return `Error getting objects info: ${e instanceof Error ? e.message : String(e)}`;
59
+ }
60
+ }
61
+ };
62
+
63
+ // createObject 工具
64
+ const DEFAULT_TORUS_SIZE = { ring: 500, pipe: 100 };
65
+ const DEFAULT_TORUS_SEGMENTS = { ring: 32, pipe: 16 };
66
+ const TORUS_RING_RADIUS_RANGE = { min: 0, max: 1000000, step: 50, minOpen: true };
67
+ const TORUS_RING_SEG_RANGE = { min: 3, max: 64, step: 1 };
68
+ const TORUS_PIPE_RADIUS_RANGE = { min: 0, max: 1000000, step: 10, minOpen: true };
69
+ const TORUS_PIPE_SEG_RANGE = { min: 3, max: 64, step: 1 };
70
+
71
+ const SLICE_RANGE = { min: 0, max: 360, step: 10 }; // degree
72
+ const DEFAULT_SLICE = { f: 0, t: 180 };
73
+
74
+ // createObject 工具 - 使用绝对尺寸替代缩放
75
+ export const createObjectTool = {
76
+ name: 'create_object',
77
+ description: 'Create a new object in the koo master scene',
78
+ parameters: z.object({
79
+ type: z.string().describe('Object type (CUBE, SPHERE, CYLINDER, PLANE, CONE, TORUS, EMPTY, CAMERA, LIGHT)').default("CUBE"),
80
+ name: z.string().optional().describe('Optional name for the object'),
81
+ location: z.array(z.number()).optional().describe('Optional [x, y, z] center location coordinates'),
82
+ rotation: z.array(z.number()).optional().describe('Optional [x, y, z] rotation in degrees. Following the Z-up coordinate system: rotation around X-axis tilts the object left/right (like nodding sideways), rotation around Y-axis tilts the object forward/backward (like nodding), and rotation around Z-axis rotates the object in the horizontal plane (like spinning on the ground). Positive values follow the right-hand rule: point thumb in positive axis direction, fingers show positive rotation direction.'),
83
+
84
+ // 使用绝对尺寸替代缩放
85
+ dimensions: z.array(z.number()).describe('Optional [width, height, depth] absolute dimensions').optional()
86
+ .default(undefined),
87
+
88
+ // 圆环特有参数 - 验证器链修复,default() 放在最后
89
+ slice_from: z.number()
90
+ .min(SLICE_RANGE.min)
91
+ .max(SLICE_RANGE.max)
92
+ .describe(`Torus only: Starting angle of the slice in degrees (${SLICE_RANGE.min}-${SLICE_RANGE.max}), step ${SLICE_RANGE.step}`)
93
+ .default(DEFAULT_SLICE.f),
94
+
95
+ slice_to: z.number()
96
+ .min(SLICE_RANGE.min)
97
+ .max(SLICE_RANGE.max)
98
+ .describe(`Torus only: Ending angle of the slice in degrees (${SLICE_RANGE.min}-${SLICE_RANGE.max}), step ${SLICE_RANGE.step}`)
99
+ .default(DEFAULT_SLICE.t),
100
+
101
+ ring_radius: z.number()
102
+ .gt(0)
103
+ .max(TORUS_RING_RADIUS_RANGE.max)
104
+ .describe(`Torus only: Radius of the ring (0-${TORUS_RING_RADIUS_RANGE.max}), step ${TORUS_RING_RADIUS_RANGE.step}`)
105
+ .default(DEFAULT_TORUS_SIZE.ring),
106
+
107
+ ring_segments: z.number()
108
+ .min(TORUS_RING_SEG_RANGE.min)
109
+ .max(TORUS_RING_SEG_RANGE.max)
110
+ .int()
111
+ .describe(`Torus only: Number of segments for the ring (${TORUS_RING_SEG_RANGE.min}-${TORUS_RING_SEG_RANGE.max}), integers only`)
112
+ .default(DEFAULT_TORUS_SEGMENTS.ring),
113
+
114
+ pipe_radius: z.number()
115
+ .gt(0)
116
+ .max(TORUS_PIPE_RADIUS_RANGE.max)
117
+ .describe(`Torus only: Radius of the pipe/tube (0-${TORUS_PIPE_RADIUS_RANGE.max}), step ${TORUS_PIPE_RADIUS_RANGE.step}`)
118
+ .default(DEFAULT_TORUS_SIZE.pipe),
119
+
120
+ pipe_segments: z.number()
121
+ .min(TORUS_PIPE_SEG_RANGE.min)
122
+ .max(TORUS_PIPE_SEG_RANGE.max)
123
+ .int()
124
+ .describe(`Torus only: Number of segments for the pipe (${TORUS_PIPE_SEG_RANGE.min}-${TORUS_PIPE_SEG_RANGE.max}), integers only`)
125
+ .default(DEFAULT_TORUS_SEGMENTS.pipe),
126
+
127
+ // 其他特有参数
128
+ // radius: z.number().optional().describe('Sphere only: Radius of the sphere'),
129
+ // base_radius: z.number().optional().describe('Cone only: Radius of the base'),
130
+ // top_radius: z.number().optional().describe('Cylinder/Cone only: Radius of the top (0 for cone point)'),
131
+ }),
132
+ execute: async (args, context: Context): Promise<string> => {
133
+ logger.info(`Tool executed: create_object of type "${args.type}"${args.name ? ` with name "${args.name}"` : ''}`);
134
+
135
+ try {
136
+ if (!llmClient.hasConnectedKoomasterClients()) {
137
+ logger.warn(`No Koomaster clients connected when attempting to create object of type "${args.type}"`);
138
+ return "No Koomaster clients connected. Please start the modeling software and connect to this server.";
139
+ }
140
+
141
+ // 设置位置和旋转的默认值
142
+ const loc = args.location || [0, 0, 0];
143
+ const rot = args.rotation || [0, 0, 0];
144
+
145
+ logger.debug(`Creating object with location=${JSON.stringify(loc)}, rotation=${JSON.stringify(rot)}`);
146
+
147
+ const params: Record<string, any> = {
148
+ type: args.type,
149
+ location: loc,
150
+ rotation: rot,
151
+ };
152
+
153
+ if (args.name) {
154
+ params.name = args.name;
155
+ logger.debug(`Using custom name: "${args.name}"`);
156
+ }
157
+
158
+ // 添加尺寸参数(如果提供)
159
+ if (args.dimensions) {
160
+ params.dimensions = args.dimensions;
161
+ logger.debug(`Setting dimensions to [${args.dimensions.join(', ')}]`);
162
+ }
163
+
164
+ // 根据对象类型添加特定参数
165
+ switch (args.type) {
166
+ case "TORUS":
167
+ // 检查 slice_from 是否小于 slice_to
168
+ if (args.slice_from >= args.slice_to) {
169
+ const errorMsg = `Invalid slice range: slice_from (${args.slice_from}) must be less than slice_to (${args.slice_to})`;
170
+ logger.warn(errorMsg);
171
+ return errorMsg;
172
+ }
173
+
174
+ // 确保分段值为整数
175
+ const ringSegments = Math.round(args.ring_segments);
176
+ const pipeSegments = Math.round(args.pipe_segments);
177
+
178
+ logger.debug(`Creating torus with: ring_radius=${args.ring_radius}, ring_segments=${ringSegments}, pipe_radius=${args.pipe_radius}, pipe_segments=${pipeSegments}`);
179
+ logger.debug(`Slice parameters: from=${args.slice_from}°, to=${args.slice_to}°`);
180
+
181
+ // 添加圆环特有参数
182
+ params.slice_from = args.slice_from;
183
+ params.slice_to = args.slice_to;
184
+ params.ring_radius = args.ring_radius;
185
+ params.ring_segments = ringSegments;
186
+ params.pipe_radius = args.pipe_radius;
187
+ params.pipe_segments = pipeSegments;
188
+ break;
189
+
190
+ case "SPHERE":
191
+ if (args.radius) {
192
+ params.radius = args.radius;
193
+ logger.debug(`Creating sphere with radius=${args.radius}`);
194
+ }
195
+ if (args.segments) {
196
+ params.segments = args.segments;
197
+ logger.debug(`Sphere segments=${args.segments}`);
198
+ }
199
+ break;
200
+
201
+ case "CYLINDER":
202
+ if (args.height) {
203
+ params.height = args.height;
204
+ logger.debug(`Cylinder height=${args.height}`);
205
+ }
206
+ if (args.top_radius) {
207
+ params.top_radius = args.top_radius;
208
+ logger.debug(`Cylinder top_radius=${args.top_radius}`);
209
+ }
210
+ if (args.segments) {
211
+ params.segments = args.segments;
212
+ logger.debug(`Cylinder segments=${args.segments}`);
213
+ }
214
+ break;
215
+
216
+ case "CONE":
217
+ if (args.height) {
218
+ params.height = args.height;
219
+ logger.debug(`Cone height=${args.height}`);
220
+ }
221
+ if (args.base_radius) {
222
+ params.base_radius = args.base_radius;
223
+ logger.debug(`Cone base_radius=${args.base_radius}`);
224
+ }
225
+ if (args.top_radius !== undefined) {
226
+ params.top_radius = args.top_radius;
227
+ logger.debug(`Cone top_radius=${args.top_radius}`);
228
+ }
229
+ if (args.segments) {
230
+ params.segments = args.segments;
231
+ logger.debug(`Cone segments=${args.segments}`);
232
+ }
233
+ break;
234
+
235
+ case "PLANE":
236
+ if (args.width) {
237
+ params.width = args.width;
238
+ logger.debug(`Plane width=${args.width}`);
239
+ }
240
+ if (args.length) {
241
+ params.length = args.length;
242
+ logger.debug(`Plane length=${args.length}`);
243
+ }
244
+ break;
245
+ }
246
+
247
+ logger.debug(`Sending 'create_object' command for ${args.type} with ${Object.keys(params).length} parameters`);
248
+ const result = await llmClient.sendCommand("create_object", params);
249
+ logger.info(`Created ${args.type.toLowerCase()} object: "${result.name}" at [${loc.join(', ')}]`);
250
+ return `Created ${args.type} object: ${result.name}`;
251
+ } catch (e) {
252
+ const errorMessage = `Error creating object of type "${args.type}": ${e instanceof Error ? e.message : String(e)}`;
253
+ logger.error(errorMessage);
254
+ context.log.error(errorMessage);
255
+ return `Error creating object: ${e instanceof Error ? e.message : String(e)}`;
256
+ }
257
+ }
258
+ };
259
+
260
+ // modifyObject 工具
261
+ export const modifyObjectTool = {
262
+ name: 'modify_object',
263
+ description: 'Modify an existing object in the scene',
264
+ parameters: z.object({
265
+ name: z.string().describe('Name of the object to modify'),
266
+ location: z.array(z.number()).optional().describe('Optional [x, y, z] center location coordinates'),
267
+ rotation: z.array(z.number()).optional().describe('Optional [x, y, z] rotation in degrees. Following the Z-up coordinate system: rotation around X-axis tilts the object left/right (like nodding sideways), rotation around Y-axis tilts the object forward/backward (like nodding), and rotation around Z-axis rotates the object in the horizontal plane (like spinning on the ground). Positive values follow the right-hand rule: point thumb in positive axis direction, fingers show positive rotation direction.'),
268
+
269
+ // 使用绝对尺寸替代缩放
270
+ dimensions: z.array(z.number()).optional().describe('Optional [width, height, depth] absolute dimensions'),
271
+
272
+ // 其他可能需要修改的特定参数
273
+ visible: z.boolean().optional().describe('Optional boolean to set visibility'),
274
+
275
+ // 圆环特有参数
276
+ slice_from: z.number().optional().describe('Torus only: Starting angle of the slice in degrees (0-360)'),
277
+ slice_to: z.number().optional().describe('Torus only: Ending angle of the slice in degrees (0-360)'),
278
+ ring_radius: z.number().optional().describe('Torus only: Radius of the ring'),
279
+ pipe_radius: z.number().optional().describe('Torus only: Radius of the pipe/tube'),
280
+
281
+ // 基本几何参数,适用于不同的对象
282
+ radius: z.number().optional().describe('Sphere/Cylinder/Cone: Radius value'),
283
+ height: z.number().optional().describe('Cylinder/Cone: Height value'),
284
+ }),
285
+ execute: async (args, context: Context): Promise<string> => {
286
+ logger.info(`Tool executed: modify_object for object "${args.name}"`);
287
+
288
+ try {
289
+ if (!llmClient.hasConnectedKoomasterClients()) {
290
+ logger.warn(`No clients connected when attempting to modify object "${args.name}"`);
291
+ return "No modeling software clients connected. Please start the software and connect to this server.";
292
+ }
293
+
294
+ const params: Record<string, any> = { name: args.name };
295
+ const modifiedProps: string[] = [];
296
+
297
+ // 添加位置和旋转参数
298
+ if (args.location !== undefined) {
299
+ params.location = args.location;
300
+ logger.debug(`Setting location to [${args.location.join(', ')}]`);
301
+ modifiedProps.push("location");
302
+ }
303
+
304
+ if (args.rotation !== undefined) {
305
+ params.rotation = args.rotation;
306
+ logger.debug(`Setting rotation to [${args.rotation.join(', ')}]`);
307
+ modifiedProps.push("rotation");
308
+ }
309
+
310
+ // 添加尺寸参数(如果提供)
311
+ if (args.dimensions !== undefined) {
312
+ params.dimensions = args.dimensions;
313
+ logger.debug(`Setting dimensions to [${args.dimensions.join(', ')}]`);
314
+ modifiedProps.push("dimensions");
315
+ }
316
+
317
+ // 添加可见性参数
318
+ if (args.visible !== undefined) {
319
+ params.visible = args.visible;
320
+ logger.debug(`Setting visibility to ${args.visible}`);
321
+ modifiedProps.push("visibility");
322
+ }
323
+
324
+ // 添加圆环特有参数
325
+ if (args.slice_from !== undefined) {
326
+ params.slice_from = args.slice_from;
327
+ modifiedProps.push("slice_from");
328
+ }
329
+
330
+ if (args.slice_to !== undefined) {
331
+ params.slice_to = args.slice_to;
332
+ modifiedProps.push("slice_to");
333
+ }
334
+
335
+ if (args.ring_radius !== undefined) {
336
+ params.ring_radius = args.ring_radius;
337
+ modifiedProps.push("ring_radius");
338
+ }
339
+
340
+ if (args.pipe_radius !== undefined) {
341
+ params.pipe_radius = args.pipe_radius;
342
+ modifiedProps.push("pipe_radius");
343
+ }
344
+
345
+ // 添加其他几何参数
346
+ if (args.radius !== undefined) {
347
+ params.radius = args.radius;
348
+ modifiedProps.push("radius");
349
+ }
350
+
351
+ if (args.height !== undefined) {
352
+ params.height = args.height;
353
+ modifiedProps.push("height");
354
+ }
355
+
356
+ // 如果没有参数修改,返回提示
357
+ if (modifiedProps.length === 0) {
358
+ return `No properties specified to modify for object: ${args.name}`;
359
+ }
360
+
361
+ logger.debug(`Sending 'modify_object' command for "${args.name}" with properties: ${modifiedProps.join(', ')}`);
362
+ const result = await llmClient.sendCommand("modify_object", params);
363
+
364
+ logger.info(`Modified object: "${result.name || args.name}" (${modifiedProps.join(', ')})`);
365
+ return `Modified object ${args.name}: updated ${modifiedProps.join(', ')}`;
366
+ } catch (e) {
367
+ const errorMessage = `Error modifying object "${args.name}": ${e instanceof Error ? e.message : String(e)}`;
368
+ logger.error(errorMessage);
369
+ context.log.error(errorMessage);
370
+ return `Error modifying object: ${e instanceof Error ? e.message : String(e)}`;
371
+ }
372
+ }
373
+ };
374
+ export const deleteObjectsTool = {
375
+ name: 'delete_objects',
376
+ description: 'Delete objects from the Koomaster scene by name',
377
+ parameters: z.object({
378
+ names: z.array(z.string())
379
+ .min(1, "At least one object name must be provided")
380
+ .describe('Array of object names to delete'),
381
+ type: z.enum(['group', 'face', 'auxiliary_curve'])
382
+ .describe('Type of objects to delete: "group" for KJL models, Tripo3D models and basic geometry; "face" for individual faces; "auxiliary_curve" for reference curves')
383
+ }),
384
+ execute: async (args: { names: string[], type: 'group' | 'face' | 'auxiliary_curve' }, context: Context): Promise<string> => {
385
+ const objectNames = args.names.join(', ');
386
+ const objectCount = args.names.length;
387
+
388
+ logger.info(`Tool executed: delete_objects for ${objectCount} ${args.type}(s): "${objectNames}"`);
389
+
390
+ try {
391
+ // Check if Koomaster client is connected
392
+ if (!llmClient.hasConnectedKoomasterClients()) {
393
+ const errorMsg = `No Koomaster clients connected when attempting to delete ${objectCount} ${args.type}(s)`;
394
+ logger.warn(errorMsg);
395
+ return "No Koomaster clients connected. Please start the Koomaster addon and connect to this server.";
396
+ }
397
+
398
+ // Input validation
399
+ if (args.names.length === 0) {
400
+ return "Error: No object names provided. Please specify at least one object to delete.";
401
+ }
402
+
403
+ // Send the delete command
404
+ logger.debug(`Sending 'delete_objects' command for ${objectCount} ${args.type}(s): "${objectNames}"`);
405
+
406
+ const response = await llmClient.sendCommand("delete_objects", {
407
+ names: args.names,
408
+ type: args.type
409
+ });
410
+
411
+ return response;
412
+ } catch (e) {
413
+ // Handle exception with improved error reporting
414
+ const errorMessage = `Error deleting ${objectCount} ${args.type}(s) "${objectNames}": ${e instanceof Error ? e.message : String(e)}`;
415
+ logger.error(errorMessage);
416
+ context.log.error(errorMessage);
417
+
418
+ // Provide a more user-friendly error message
419
+ return `Failed to delete ${args.type}(s). Error: ${e instanceof Error ? e.message : String(e)}`;
420
+ }
421
+ }
422
+ };
423
+
424
+ // copyObject 工具
425
+ export const copyObjectTool = {
426
+ name: 'copy_object',
427
+ description: 'Create a copy of an existing object with a new name and transform properties. Currently only supports copying "group" type objects (KJL models, Tripo3D models, and basic geometry).',
428
+ parameters: z.object({
429
+ name: z.string().describe('Name of the group object to copy'),
430
+ newName: z.string().describe('Name for the new copied group object'),
431
+ location: z.array(z.number()).optional().describe('Optional [x, y, z] center location coordinates for the new object'),
432
+ rotation: z.array(z.number()).optional().describe('Optional [x, y, z] rotation in degrees. Following the Z-up coordinate system: rotation around X-axis tilts the object left/right (like nodding sideways), rotation around Y-axis tilts the object forward/backward (like nodding), and rotation around Z-axis rotates the object in the horizontal plane (like spinning on the ground). Positive values follow the right-hand rule: point thumb in positive axis direction, fingers show positive rotation direction.'),
433
+ dimensions: z.array(z.number()).optional().describe('Optional [width, height, depth] dimensions for the new object')
434
+ }),
435
+ execute: async (args, context: Context): Promise<string> => {
436
+ logger.info(`Tool executed: copy_object from "${args.name}" to "${args.newName}"`);
437
+
438
+ try {
439
+ if (!llmClient.hasConnectedKoomasterClients()) {
440
+ logger.warn(`No clients connected when attempting to copy object "${args.name}"`);
441
+ return "No modeling software clients connected. Please start the software and connect to this server.";
442
+ }
443
+
444
+ // 准备参数,仅包含提供的属性
445
+ const params: Record<string, any> = {
446
+ name: args.name,
447
+ newName: args.newName
448
+ };
449
+
450
+ // 添加可选参数(如果提供)
451
+ if (args.location !== undefined) {
452
+ params.location = args.location;
453
+ logger.debug(`Setting location for new object to [${args.location.join(', ')}]`);
454
+ }
455
+
456
+ if (args.rotation !== undefined) {
457
+ params.rotation = args.rotation;
458
+ logger.debug(`Setting rotation for new object to [${args.rotation.join(', ')}]`);
459
+ }
460
+
461
+ if (args.dimensions !== undefined) {
462
+ params.dimensions = args.dimensions;
463
+ logger.debug(`Setting dimensions for new object to [${args.dimensions.join(', ')}]`);
464
+ }
465
+
466
+ logger.debug(`Sending 'copy_object' command for "${args.name}" with new name "${args.newName}"`);
467
+
468
+ const result = await llmClient.sendCommand("copy_object", params);
469
+
470
+ logger.info(`Successfully copied object from "${args.name}" to "${args.newName}"`);
471
+ return `Successfully copied object "${args.name}" to a new object named "${args.newName}".\n${result}`;
472
+
473
+ } catch (e) {
474
+ const errorMessage = `Error copying object "${args.name}": ${e instanceof Error ? e.message : String(e)}`;
475
+ logger.error(errorMessage);
476
+ context.log.error(errorMessage);
477
+ return `Error copying object: ${e instanceof Error ? e.message : String(e)}`;
478
+ }
479
+ }
480
+ };
481
+
482
+ // Tool: Get Selection Details
483
+ export const getSelectionTool = {
484
+ name: 'get_selection',
485
+ description: 'Get detailed information about the currently selected objects in the scene. Supports analyzing selected faces (with vertex coordinates and bounding boxes) or selected groups (with transform and hierarchy information).',
486
+ parameters: z.object({
487
+ type: z.enum(['face', 'group', 'auxiliary_curve']).describe('The type of selection to analyze. "face" returns geometry data for selected faces. "group" returns information about selected objects. "auxiliary_curve" returns IDs and details of selected path curves.')
488
+ }),
489
+ execute: async (args, context: Context): Promise<string> => {
490
+ logger.info(`Tool executed: get_selection with type "${args.type}"`);
491
+ try {
492
+ // Check if any modeling software clients are connected
493
+ if (!llmClient.hasConnectedKoomasterClients()) {
494
+ return "No modeling software clients connected. Please start the software and connect to this server.";
495
+ }
496
+
497
+ // Send the get_selection command to the client
498
+ const result = await llmClient.sendCommand('get_selection', args);
499
+
500
+ // If no selection, provide a helpful message
501
+ if (!result || result.trim() === '' || result.includes('No objects are currently selected')) {
502
+ if (args.type === 'face') {
503
+ return "No faces are currently selected. Please select one or more faces in the scene and try again.";
504
+ } else if (args.type === 'group') {
505
+ return "No groups are currently selected. Please select one or more objects or groups in the scene and try again.";
506
+ } else if (args.type === 'auxiliary_curve') {
507
+ return "No auxiliary curves are currently selected. Please ask the user to select one or more auxiliary curves in the scene, or create some using the create_auxiliary_curve tool.";
508
+ }
509
+ return "No objects of the requested type are currently selected. Please make a selection and try again.";
510
+ }
511
+
512
+ // Return the selection details
513
+ return result;
514
+
515
+ } catch (e) {
516
+ const errorMessage = `Error retrieving selection information: ${e instanceof Error ? e.message : String(e)}`;
517
+ logger.error(errorMessage);
518
+ context.log.error(errorMessage);
519
+ return errorMessage;
520
+ }
521
+ }
522
+ };
523
+
524
+ // 导出所有工具
525
+ export const basicTools = {
526
+ getSceneInfoTool,
527
+ getObjectsInfoTool,
528
+ createObjectTool,
529
+ modifyObjectTool,
530
+ deleteObjectTool: deleteObjectsTool,
531
+ };
@@ -0,0 +1,2 @@
1
+ // src/tools/index.ts
2
+ export * from './basic-tools';
@@ -0,0 +1,88 @@
1
+ import { Context } from '../types';
2
+ import { z } from 'zod';
3
+ import logger from '../lib/logger';
4
+ import { llmClient } from '../llmClient';
5
+
6
+ export const getMaterialNames = {
7
+ name: 'get_material_names',
8
+ description: 'Retrieves a list of all available material names from the connected connected koomaster client. This function queries the active connection to obtain the names of all materials that can be applied to 3D models. Returns the complete list of material names as a string, with materials typically separated by a delimiter. Useful for discovering what materials are available before applying them to specific models.',
9
+ parameters: z.object({}),
10
+ execute: async (args, context: Context): Promise<string> => {
11
+
12
+ try {
13
+ if (!llmClient.hasConnectedKoomasterClients()) {
14
+ logger.warn(`No clients connected when attempting to set material for model "${args.modelName}"`);
15
+ return "No modeling software clients connected. Please start the software and connect to this server.";
16
+ }
17
+
18
+ const result = await llmClient.sendCommand("get_material_names");
19
+
20
+ return result;
21
+
22
+ } catch (e) {
23
+ const errorMessage = `Error setting material for model "${args.modelName}": ${e instanceof Error ? e.message : String(e)}`;
24
+ logger.error(errorMessage);
25
+ context.log.error(errorMessage);
26
+ return `Error setting material: ${e instanceof Error ? e.message : String(e)}`;
27
+ }
28
+ }
29
+ };
30
+
31
+ export const setFaceMaterialTool = {
32
+ name: 'set_face_material',
33
+ description: 'Applies a specific material to an existing face in the 3D model. This tool allows you to change the appearance of a face by assigning a different material to it.',
34
+ parameters: z.object({
35
+ faceName: z.string().describe('The name of the face to apply material to. This should be the full face name (e.g., "face_wall1"). You can get face names from previously created faces or by using tools that provide model information.'),
36
+ materialName: z.string().describe('The name of the material to apply to the face. Use the get_material_names function first to retrieve a list of all available materials in the current scene. Only materials returned by get_material_names can be successfully applied.')
37
+ }),
38
+ execute: async (args, context: Context): Promise<string> => {
39
+ try {
40
+ if (!llmClient.hasConnectedKoomasterClients()) {
41
+ logger.warn(`No clients connected when attempting to set material for face "${args.faceName}"`);
42
+ return "No modeling software clients connected. Please start the software and connect to this server.";
43
+ }
44
+
45
+ const result = await llmClient.sendCommand("set_face_material", args);
46
+
47
+ return result;
48
+ } catch (e) {
49
+ const errorMessage = `Error setting material for face "${args.faceName}": ${e instanceof Error ? e.message : String(e)}`;
50
+ logger.error(errorMessage);
51
+ context.log.error(errorMessage);
52
+
53
+ if (String(e).includes("Face not found")) {
54
+ return `Error: Face "${args.faceName}" not found in the model. Please verify the face name is correct and that the face exists.`;
55
+ } else if (String(e).includes("Material not found")) {
56
+ return `Error: Material "${args.materialName}" not found. Use the get_material_names function to see available materials.`;
57
+ }
58
+
59
+ return errorMessage;
60
+ }
61
+ }
62
+ };
63
+
64
+ export const setGroupMaterialTool = {
65
+ name: 'set_group_material',
66
+ description: 'Applies a specific material to an entire group of objects in the 3D model. This tool changes the appearance of all elements within the named group by assigning a different material to them.',
67
+ parameters: z.object({
68
+ modelName: z.string().describe('The name of the group or model to apply material to. This should be the name you assigned when creating the group or importing the model. You can get model names using tools that provide scene information.'),
69
+ materialName: z.string().describe('The name of the material to apply to the entire group. Use the get_material_names function first to retrieve a list of all available materials in the current scene. Only materials returned by get_material_names can be successfully applied.')
70
+ }),
71
+ execute: async (args, context: Context): Promise<string> => {
72
+ try {
73
+ if (!llmClient.hasConnectedKoomasterClients()) {
74
+ logger.warn(`No clients connected when attempting to set material for group "${args.modelName}"`);
75
+ return "No modeling software clients connected. Please start the software and connect to this server.";
76
+ }
77
+
78
+ const result = await llmClient.sendCommand("set_group_material", args);
79
+
80
+ return result;
81
+ } catch (e) {
82
+ const errorMessage = `Error setting material for group "${args.modelName}": ${e instanceof Error ? e.message : String(e)}`;
83
+ logger.error(errorMessage);
84
+ context.log.error(errorMessage);
85
+ return errorMessage;
86
+ }
87
+ }
88
+ };
@@ -0,0 +1,44 @@
1
+ import { Context } from "../../types";
2
+ import { z } from "zod";
3
+ import logger from "../../lib/logger";
4
+ import { llmClient } from "../../llmClient";
5
+ import { parseSvgPath3D } from "./utils";
6
+
7
+ export const createAuxiliaryCurve = {
8
+ name: 'create_auxiliary_curve',
9
+ description: 'Creates an auxiliary 3D curve using SVG-like path commands with explicit 3D coordinates. Auxiliary curves can be used for sweep operations, as guides, or for visualization purposes.',
10
+ parameters: z.object({
11
+ path: z.string().describe('3D curve path defined using SVG path commands:\n- M x,y,z: Move to initial point\n- L x,y,z: Line to point\n- C cp1x,cp1y,cp1z cp2x,cp2y,cp2z endX,endY,endZ: Cubic Bezier curve with two control points\n- A midX,midY,midZ endX,endY,endZ: Arc defined by three points\n - Current position is the start point\n - midX,midY,midZ: A point ON THE ARC between start and end (NOT the center)\n - endX,endY,endZ: End point of the arc\n- Z: Close path (optional for auxiliary curves)\n\nIMPORTANT NOTES:\n1. CRITICAL: For Bezier curves (C command), ALL POINTS (start, control points, and end) MUST be coplanar (lie on the same plane). Non-planar Bezier curves will fail to create\n2. Unlike faces, auxiliary curves as a whole do NOT need to be closed or planar\n3. For connected curve segments used in sweep operations, ensure endpoints match exactly\n4. The arc command uses a custom 3-point format (not standard SVG arc)\n5. For arcs, the three points must not be collinear\n6. For a circle, use 3-4 arcs connecting points along the circumference\n\nEXAMPLES:\n- Straight line: "M 0,0,0 L 100,0,0"\n- Polyline: "M 0,0,0 L 50,50,50 L 100,0,100"\n- Arc: "M 0,0,0 A 50,50,50 100,0,0"\n- Planar Bezier curve: "M 0,0,0 C 25,25,0 75,75,0 100,100,0" (note all z-values are 0, ensuring coplanarity)\n- Closed shape: "M 0,0,0 L 100,0,0 L 100,100,0 L 0,100,0 Z"\n- Complex path (arcs and lines): "M 0,0,0 L 100,0,0 A 150,50,0 200,0,0 L 200,100,0 L 0,100,0 Z"'),
12
+ }),
13
+ execute: async (args, context: Context): Promise<string> => {
14
+ logger.info(`Tool executed: create_auxiliary_curve with path "${args.path}" and name "${args.name}"`);
15
+ try {
16
+ // Check if any modeling software clients are connected
17
+ if (!llmClient.hasConnectedKoomasterClients()) {
18
+ return "No modeling software clients connected. Please start the software and connect to this server.";
19
+ }
20
+
21
+ // Parse the SVG path into 3D curve segments
22
+ try {
23
+ // Parse the path
24
+ const pathCurves = parseSvgPath3D(args.path);
25
+
26
+ // Send the command to create the auxiliary curve
27
+ const result = await llmClient.sendCommand('create_auxiliary_curve', {
28
+ ...args,
29
+ path: pathCurves,
30
+ });
31
+
32
+ return result;
33
+ } catch (parseError) {
34
+ return `Error parsing SVG path: ${parseError instanceof Error ? parseError.message : String(parseError)}`;
35
+ }
36
+
37
+ } catch (e) {
38
+ const errorMessage = `Error creating auxiliary curve: ${e instanceof Error ? e.message : String(e)}`;
39
+ logger.error(errorMessage);
40
+ context.log.error(errorMessage);
41
+ return errorMessage;
42
+ }
43
+ }
44
+ };