@pixel-normal-edit/mcp 2.0.5 → 2.0.7
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.
- package/domains/art-tree/3d_lessons.js +114 -0
- package/domains/art-tree/3d_shapes.js +116 -0
- package/domains/art-tree/3d_tools.js +60 -0
- package/domains/art-tree/advanced_lessons.js +124 -0
- package/domains/art-tree/advanced_shapes.js +73 -0
- package/domains/art-tree/advanced_tools.js +69 -0
- package/domains/art-tree/cross_section_lessons.js +65 -0
- package/domains/art-tree/cross_section_shapes.js +84 -0
- package/domains/art-tree/cross_section_tools.js +51 -0
- package/domains/art-tree/curve_lessons.js +138 -0
- package/domains/art-tree/curve_shapes.js +90 -0
- package/domains/art-tree/curve_tools.js +51 -0
- package/domains/art-tree/ellipse_lessons.js +105 -0
- package/domains/art-tree/ellipse_shapes.js +63 -0
- package/domains/art-tree/ellipse_tools.js +60 -0
- package/domains/art-tree/hidden_lessons.js +55 -0
- package/domains/art-tree/hidden_shapes.js +97 -0
- package/domains/art-tree/hidden_tools.js +51 -0
- package/domains/art-tree/index.js +22 -0
- package/domains/art-tree/lessons.js +112 -0
- package/domains/art-tree/light_lessons.js +92 -0
- package/domains/art-tree/light_shapes.js +136 -0
- package/domains/art-tree/light_tools.js +49 -0
- package/domains/art-tree/perspective_lessons.js +138 -0
- package/domains/art-tree/perspective_shapes.js +96 -0
- package/domains/art-tree/perspective_tools.js +60 -0
- package/domains/art-tree/shapes.js +15 -0
- package/domains/art-tree/structure_lessons.js +102 -0
- package/domains/art-tree/structure_shapes.js +74 -0
- package/domains/art-tree/structure_tools.js +51 -0
- package/domains/art-tree/surface_lessons.js +112 -0
- package/domains/art-tree/surface_shapes.js +87 -0
- package/domains/art-tree/surface_tools.js +60 -0
- package/domains/art-tree/tools.js +54 -0
- package/domains/art-tree/transform_lessons_1.js +108 -0
- package/domains/art-tree/transform_lessons_2.js +102 -0
- package/domains/art-tree/transform_shapes.js +109 -0
- package/domains/art-tree/transform_tools.js +104 -0
- package/package.json +1 -1
- package/tools/workspace.js +14 -4
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const shapes = require('./shapes');
|
|
2
|
+
const shapes3d = require('./3d_shapes');
|
|
3
|
+
|
|
4
|
+
function drawCupAnalysis(cx, cy, r, h) {
|
|
5
|
+
const commands = [];
|
|
6
|
+
const topY = cy - Math.floor(h/2);
|
|
7
|
+
const botY = cy + Math.floor(h/2);
|
|
8
|
+
const topRy = Math.floor(r * 0.3);
|
|
9
|
+
const botRy = Math.floor(r * 0.4);
|
|
10
|
+
|
|
11
|
+
// Outer Wall (Convex) - Blue
|
|
12
|
+
commands.push(shapes.drawLine(cx - r, topY, cx - Math.floor(r*0.8), botY, '#2196f3'));
|
|
13
|
+
commands.push(shapes.drawLine(cx + r, topY, cx + Math.floor(r*0.8), botY, '#2196f3'));
|
|
14
|
+
// Base (Flat/Curved)
|
|
15
|
+
commands.push(shapes.drawEllipse(cx, botY, Math.floor(r*0.8), botRy, '#4caf50', false));
|
|
16
|
+
|
|
17
|
+
// Inner Wall (Concave) - Purple
|
|
18
|
+
// Draw the back half of the inner ellipse (the visible inside of the cup)
|
|
19
|
+
const innerR = Math.floor(r * 0.9);
|
|
20
|
+
const innerRy = Math.floor(topRy * 0.9);
|
|
21
|
+
// Just draw the full inner ellipse but highlighted differently
|
|
22
|
+
commands.push(shapes.drawEllipse(cx, topY, innerR, innerRy, '#9c27b0', false));
|
|
23
|
+
|
|
24
|
+
// Lip / Rim (Transitional edge) - Orange
|
|
25
|
+
// We represent the lip as the gap between the inner and outer ellipses at the top
|
|
26
|
+
commands.push(shapes.drawEllipse(cx, topY, r, topRy, '#ff9800', false));
|
|
27
|
+
|
|
28
|
+
// Handle (Tube surface) - Red
|
|
29
|
+
// Draw a curved handle on the right side
|
|
30
|
+
const hx = cx + r;
|
|
31
|
+
const hy = cy;
|
|
32
|
+
const hr = Math.floor(h * 0.3);
|
|
33
|
+
// Two nested semi-circles (arcs) for the handle thickness
|
|
34
|
+
const handleOuter = [];
|
|
35
|
+
const handleInner = [];
|
|
36
|
+
for(let i=0; i<=18; i++) {
|
|
37
|
+
const angle = (i/18) * Math.PI - Math.PI/2; // -90 to 90 degrees
|
|
38
|
+
handleOuter.push({ x: Math.round(hx + hr * Math.cos(angle)), y: Math.round(hy + hr * Math.sin(angle)) });
|
|
39
|
+
handleInner.push({ x: Math.round(hx + (hr-5) * Math.cos(angle)), y: Math.round(hy + (hr-5) * Math.sin(angle)) });
|
|
40
|
+
}
|
|
41
|
+
commands.push(shapes.drawPolyline(handleOuter, '#f44336'));
|
|
42
|
+
commands.push(shapes.drawPolyline(handleInner, '#f44336'));
|
|
43
|
+
|
|
44
|
+
return commands;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function drawChairAnalysis(cx, cy, w) {
|
|
48
|
+
const commands = [];
|
|
49
|
+
const h = Math.floor(w * 1.5);
|
|
50
|
+
const d = Math.floor(w * 0.8);
|
|
51
|
+
const seatY = cy;
|
|
52
|
+
const dx = Math.floor(d * 0.7);
|
|
53
|
+
const dy = Math.floor(d * 0.5);
|
|
54
|
+
|
|
55
|
+
// Seat (Flat plane) - Green
|
|
56
|
+
const fX = cx - Math.floor(w/2);
|
|
57
|
+
const bX = fX + dx;
|
|
58
|
+
const bY = seatY - dy;
|
|
59
|
+
// Seat wireframe
|
|
60
|
+
commands.push(shapes.drawRectangle(fX, seatY, w, 10, '#4caf50', false)); // Front thickness
|
|
61
|
+
commands.push(shapes.drawLine(fX, seatY, bX, bY, '#4caf50'));
|
|
62
|
+
commands.push(shapes.drawLine(fX+w, seatY, bX+w, bY, '#4caf50'));
|
|
63
|
+
commands.push(shapes.drawLine(bX, bY, bX+w, bY, '#4caf50'));
|
|
64
|
+
|
|
65
|
+
// Backrest (Flat plane + Hard edges) - Blue
|
|
66
|
+
commands.push(shapes.drawRectangle(bX, bY - Math.floor(h*0.5), w, Math.floor(h*0.5), '#2196f3', false));
|
|
67
|
+
// Inner rungs (Hard edges)
|
|
68
|
+
commands.push(shapes.drawRectangle(bX + 10, bY - Math.floor(h*0.4), 5, Math.floor(h*0.4), '#ff9800', false));
|
|
69
|
+
commands.push(shapes.drawRectangle(bX + w - 15, bY - Math.floor(h*0.4), 5, Math.floor(h*0.4), '#ff9800', false));
|
|
70
|
+
|
|
71
|
+
// Legs (Cylindrical surfaces) - Red
|
|
72
|
+
const legH = Math.floor(h*0.4);
|
|
73
|
+
const legR = 3;
|
|
74
|
+
// Front-left
|
|
75
|
+
commands.push(shapes.drawLine(fX+legR, seatY+10, fX+legR, seatY+10+legH, '#f44336'));
|
|
76
|
+
commands.push(shapes.drawLine(fX+10-legR, seatY+10, fX+10-legR, seatY+10+legH, '#f44336'));
|
|
77
|
+
// Front-right
|
|
78
|
+
commands.push(shapes.drawLine(fX+w-10+legR, seatY+10, fX+w-10+legR, seatY+10+legH, '#f44336'));
|
|
79
|
+
commands.push(shapes.drawLine(fX+w-legR, seatY+10, fX+w-legR, seatY+10+legH, '#f44336'));
|
|
80
|
+
|
|
81
|
+
return commands;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = {
|
|
85
|
+
drawCupAnalysis,
|
|
86
|
+
drawChairAnalysis
|
|
87
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
const { registerTool, sendCommand } = require('../../core/server');
|
|
3
|
+
const surfaceLessons = require('./surface_lessons');
|
|
4
|
+
|
|
5
|
+
const hexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/).describe('Hex color e.g. #ff0000');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Register all surface analysis tools on the MCP server.
|
|
9
|
+
* @param {McpServer} server
|
|
10
|
+
*/
|
|
11
|
+
function register(server) {
|
|
12
|
+
registerTool(server, 'art_tree_lesson_surface_types',
|
|
13
|
+
'Lesson: Surfaces - Analyze Flat, Convex, and Concave planes',
|
|
14
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#2196f3') },
|
|
15
|
+
async (p) => {
|
|
16
|
+
const commands = surfaceLessons.lessonSurfaceTypes(p.canvasSize, p.color);
|
|
17
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
18
|
+
return { content: [{ type: 'text', text: `✓ Surface Types lesson completed` }] };
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
registerTool(server, 'art_tree_lesson_edge_types',
|
|
22
|
+
'Lesson: Surfaces - Analyze Hard vs Soft (Transitional) edges',
|
|
23
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#ff5722') },
|
|
24
|
+
async (p) => {
|
|
25
|
+
const commands = surfaceLessons.lessonEdgeTypes(p.canvasSize, p.color);
|
|
26
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
27
|
+
return { content: [{ type: 'text', text: `✓ Edge Types lesson completed` }] };
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
registerTool(server, 'art_tree_lesson_surface_cup',
|
|
31
|
+
'Lesson: Surfaces - Cup Analysis (Real object breakdown)',
|
|
32
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#607d8b') },
|
|
33
|
+
async (p) => {
|
|
34
|
+
const commands = surfaceLessons.lessonCupAnalysis(p.canvasSize, p.color);
|
|
35
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
36
|
+
return { content: [{ type: 'text', text: `✓ Cup Surface Analysis lesson completed` }] };
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
registerTool(server, 'art_tree_lesson_surface_chair',
|
|
40
|
+
'Lesson: Surfaces - Chair Analysis (Real object breakdown)',
|
|
41
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#795548') },
|
|
42
|
+
async (p) => {
|
|
43
|
+
const commands = surfaceLessons.lessonChairAnalysis(p.canvasSize, p.color);
|
|
44
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
45
|
+
return { content: [{ type: 'text', text: `✓ Chair Surface Analysis lesson completed` }] };
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
registerTool(server, 'art_tree_lesson_surface_catalog',
|
|
49
|
+
'Get the catalog of all available Surface lessons',
|
|
50
|
+
{},
|
|
51
|
+
() => {
|
|
52
|
+
const catalog = surfaceLessons.getSurfaceLessonCatalog();
|
|
53
|
+
const text = catalog.map(l =>
|
|
54
|
+
` • ${l.id}: ${l.name} — ${l.description}`
|
|
55
|
+
).join('\n');
|
|
56
|
+
return { content: [{ type: 'text', text: `📚 Surface Lessons:\n\n${text}` }] };
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = { register };
|
|
@@ -164,6 +164,60 @@ function register(server) {
|
|
|
164
164
|
return { content: [{ type: 'text', text: `✓ Bài học tổng hợp hoàn thành (${commands.length} nét vẽ)` }] };
|
|
165
165
|
});
|
|
166
166
|
|
|
167
|
+
registerTool(server, 'art_tree_lesson_freehand',
|
|
168
|
+
'Lesson: Vẽ bằng tay — Luyện vẽ bằng tay, hạn chế dùng thước',
|
|
169
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#795548') },
|
|
170
|
+
async (p) => {
|
|
171
|
+
const commands = lessons.lessonFreehand(p.canvasSize, p.color);
|
|
172
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
173
|
+
return { content: [{ type: 'text', text: `✓ Bài học vẽ tay hoàn thành (${commands.length} đoạn thẳng nhỏ)` }] };
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
registerTool(server, 'art_tree_lesson_proportions',
|
|
177
|
+
'Lesson: Tỉ lệ và góc — Tập giữ tỉ lệ và góc (Proportions and Angles)',
|
|
178
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#3f51b5') },
|
|
179
|
+
async (p) => {
|
|
180
|
+
const commands = lessons.lessonProportionsAndAngles(p.canvasSize, p.color);
|
|
181
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
182
|
+
return { content: [{ type: 'text', text: `✓ Bài học tỉ lệ và góc hoàn thành` }] };
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
registerTool(server, 'art_tree_lesson_curves',
|
|
186
|
+
'Lesson: Đường cong — Đường cong C, S',
|
|
187
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#9c27b0') },
|
|
188
|
+
async (p) => {
|
|
189
|
+
const commands = lessons.lessonCurves(p.canvasSize, p.color);
|
|
190
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
191
|
+
return { content: [{ type: 'text', text: `✓ Bài học đường cong C, S hoàn thành (${commands.length} nét)` }] };
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
registerTool(server, 'art_tree_lesson_spiral',
|
|
195
|
+
'Lesson: Xoắn ốc — Vẽ đường xoắn ốc',
|
|
196
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#ff9800') },
|
|
197
|
+
async (p) => {
|
|
198
|
+
const commands = lessons.lessonSpiral(p.canvasSize, p.color);
|
|
199
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
200
|
+
return { content: [{ type: 'text', text: `✓ Bài học xoắn ốc hoàn thành (${commands.length} nét)` }] };
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
registerTool(server, 'art_tree_lesson_strokes',
|
|
204
|
+
'Lesson: Các loại nét — Nét dài, nét ngắn, nét đậm–nhạt',
|
|
205
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#2196f3') },
|
|
206
|
+
async (p) => {
|
|
207
|
+
const commands = lessons.lessonStrokes(p.canvasSize, p.color);
|
|
208
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
209
|
+
return { content: [{ type: 'text', text: `✓ Bài học nét vẽ hoàn thành` }] };
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
registerTool(server, 'art_tree_lesson_parallel_intersecting',
|
|
213
|
+
'Lesson: Song song & giao nhau — Vẽ các đường song song và đường giao nhau',
|
|
214
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#00bcd4') },
|
|
215
|
+
async (p) => {
|
|
216
|
+
const commands = lessons.lessonParallelAndIntersecting(p.canvasSize, p.color);
|
|
217
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
218
|
+
return { content: [{ type: 'text', text: `✓ Bài học song song và giao nhau hoàn thành` }] };
|
|
219
|
+
});
|
|
220
|
+
|
|
167
221
|
registerTool(server, 'art_tree_lesson_catalog',
|
|
168
222
|
'Get the catalog of all available Art Tree lessons with descriptions',
|
|
169
223
|
{},
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const shapes = require('./shapes');
|
|
2
|
+
const transformShapes = require('./transform_shapes');
|
|
3
|
+
const csShapes = require('./cross_section_shapes');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Lesson: Stretch (Elongate)
|
|
7
|
+
* Sphere -> Egg, Box -> Bar
|
|
8
|
+
*/
|
|
9
|
+
function lessonStretch(canvasSize = 32, color = '#2196f3') {
|
|
10
|
+
const commands = [];
|
|
11
|
+
const qX1 = Math.floor(canvasSize * 0.25);
|
|
12
|
+
const qX2 = Math.floor(canvasSize * 0.75);
|
|
13
|
+
const cy = Math.floor(canvasSize / 2);
|
|
14
|
+
const r = Math.floor(canvasSize * 0.15);
|
|
15
|
+
|
|
16
|
+
// 1. Stretch: Sphere -> Egg
|
|
17
|
+
commands.push(shapes.drawCircle(qX1, cy, r, '#bdbdbd', false)); // Original sphere (ghost)
|
|
18
|
+
commands.push(...transformShapes.drawEgg(qX1, cy, r, color)); // Stretched into egg
|
|
19
|
+
|
|
20
|
+
// 2. Stretch: Box -> Bar (Tall)
|
|
21
|
+
const w = Math.floor(canvasSize * 0.15);
|
|
22
|
+
// Original box (ghost)
|
|
23
|
+
commands.push(shapes.drawRectangle(qX2 - w/2, cy - w/2, w, w, '#bdbdbd', false));
|
|
24
|
+
// Stretched bar
|
|
25
|
+
commands.push(shapes.drawRectangle(qX2 - w/2, cy - w * 1.5, w, w * 3, '#9c27b0', false));
|
|
26
|
+
|
|
27
|
+
return commands;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Lesson: Squash (Compress)
|
|
32
|
+
* Sphere -> Disk, Box -> Plate
|
|
33
|
+
*/
|
|
34
|
+
function lessonSquash(canvasSize = 32, color = '#ff9800') {
|
|
35
|
+
const commands = [];
|
|
36
|
+
const qX1 = Math.floor(canvasSize * 0.25);
|
|
37
|
+
const qX2 = Math.floor(canvasSize * 0.75);
|
|
38
|
+
const cy = Math.floor(canvasSize / 2);
|
|
39
|
+
const r = Math.floor(canvasSize * 0.15);
|
|
40
|
+
|
|
41
|
+
// 1. Squash: Sphere -> Disk
|
|
42
|
+
commands.push(shapes.drawCircle(qX1, cy, r, '#bdbdbd', false)); // Original
|
|
43
|
+
// Squashed into a flat ellipse/disk
|
|
44
|
+
commands.push(shapes.drawEllipse(qX1, cy, r * 1.2, Math.floor(r * 0.3), color, false));
|
|
45
|
+
|
|
46
|
+
// 2. Squash: Box -> Plate
|
|
47
|
+
const w = Math.floor(canvasSize * 0.2);
|
|
48
|
+
commands.push(shapes.drawRectangle(qX2 - w/2, cy - w/2, w, w, '#bdbdbd', false)); // Original
|
|
49
|
+
// Squashed into plate
|
|
50
|
+
commands.push(shapes.drawRectangle(qX2 - w, cy - Math.floor(w*0.2), w * 2, Math.floor(w*0.4), '#4caf50', false));
|
|
51
|
+
|
|
52
|
+
return commands;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Lesson: Taper & Swell (Thu nhỏ hoặc phình to)
|
|
57
|
+
* Cylinder -> Bottle
|
|
58
|
+
*/
|
|
59
|
+
function lessonTaperSwell(canvasSize = 32, color = '#00bcd4') {
|
|
60
|
+
const commands = [];
|
|
61
|
+
const cx = Math.floor(canvasSize / 2);
|
|
62
|
+
const totalH = Math.floor(canvasSize * 0.8);
|
|
63
|
+
const topY = Math.floor(canvasSize * 0.1);
|
|
64
|
+
const bottomY = topY + totalH;
|
|
65
|
+
const r = Math.floor(canvasSize * 0.25);
|
|
66
|
+
|
|
67
|
+
// Original Cylinder (ghost)
|
|
68
|
+
commands.push(shapes.drawLine(cx - r, topY, cx - r, bottomY, '#bdbdbd'));
|
|
69
|
+
commands.push(shapes.drawLine(cx + r, topY, cx + r, bottomY, '#bdbdbd'));
|
|
70
|
+
|
|
71
|
+
// Tapered and Swelled form (Bottle)
|
|
72
|
+
const sections = [
|
|
73
|
+
{ y: topY, r: Math.floor(r * 0.3) }, // Neck (Tapered)
|
|
74
|
+
{ y: topY + Math.floor(totalH * 0.4), r: r },// Body (Swelled back to original radius)
|
|
75
|
+
{ y: bottomY, r: Math.floor(r * 0.8) } // Base (Slightly tapered)
|
|
76
|
+
];
|
|
77
|
+
commands.push(...csShapes.drawLoftedForm(cx, topY, bottomY, sections, color, true));
|
|
78
|
+
|
|
79
|
+
return commands;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Lesson: Bend (Uốn cong)
|
|
84
|
+
* Cylinder -> Bent Tube
|
|
85
|
+
*/
|
|
86
|
+
function lessonBend(canvasSize = 32, color = '#e91e63') {
|
|
87
|
+
const commands = [];
|
|
88
|
+
const cx = Math.floor(canvasSize / 2);
|
|
89
|
+
const cy = Math.floor(canvasSize / 2);
|
|
90
|
+
const r = Math.floor(canvasSize * 0.1);
|
|
91
|
+
const length = Math.floor(canvasSize * 0.35);
|
|
92
|
+
|
|
93
|
+
// Original straight tube (ghost, pointing right)
|
|
94
|
+
commands.push(shapes.drawLine(cx, cy - r, cx + length * 2, cy - r, '#bdbdbd'));
|
|
95
|
+
commands.push(shapes.drawLine(cx, cy + r, cx + length * 2, cy + r, '#bdbdbd'));
|
|
96
|
+
|
|
97
|
+
// Bent tube
|
|
98
|
+
commands.push(...transformShapes.drawBentTube(cx, cy + length, r, length, color));
|
|
99
|
+
|
|
100
|
+
return commands;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = {
|
|
104
|
+
lessonStretch,
|
|
105
|
+
lessonSquash,
|
|
106
|
+
lessonTaperSwell,
|
|
107
|
+
lessonBend
|
|
108
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
const shapes = require('./shapes');
|
|
2
|
+
const transformShapes = require('./transform_shapes');
|
|
3
|
+
const structureLessons = require('./structure_lessons');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Lesson: Rotate (Xoay)
|
|
7
|
+
* Analyzing how objects change when rotated around X, Y, Z axes.
|
|
8
|
+
* We can reuse the Axis Orientation lesson as it perfectly demonstrates rotation.
|
|
9
|
+
*/
|
|
10
|
+
function lessonRotate(canvasSize = 32, color = '#ff5722') {
|
|
11
|
+
// Reusing the axis orientation lesson which draws cylinders rotated along X, Y, Z
|
|
12
|
+
return structureLessons.lessonAxisOrientation(canvasSize, color);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Lesson: Cut (Cắt khối)
|
|
17
|
+
* Sphere -> Truncated Sphere (exposing internal cross-section)
|
|
18
|
+
*/
|
|
19
|
+
function lessonCut(canvasSize = 32, color = '#8bc34a') {
|
|
20
|
+
const commands = [];
|
|
21
|
+
const cx = Math.floor(canvasSize / 2);
|
|
22
|
+
const cy = Math.floor(canvasSize / 2);
|
|
23
|
+
const r = Math.floor(canvasSize * 0.35);
|
|
24
|
+
|
|
25
|
+
// Original sphere (ghost)
|
|
26
|
+
commands.push(shapes.drawCircle(cx, cy, r, '#bdbdbd', false));
|
|
27
|
+
|
|
28
|
+
// Cut sphere
|
|
29
|
+
commands.push(...transformShapes.drawTruncatedSphere(cx, cy, r, color));
|
|
30
|
+
|
|
31
|
+
return commands;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Lesson: Hollow (Khoét rỗng)
|
|
36
|
+
* Cylinder -> Pipe/Hole
|
|
37
|
+
*/
|
|
38
|
+
function lessonHollow(canvasSize = 32, color = '#3f51b5') {
|
|
39
|
+
const commands = [];
|
|
40
|
+
const cx = Math.floor(canvasSize / 2);
|
|
41
|
+
const cy = Math.floor(canvasSize / 2);
|
|
42
|
+
const outerR = Math.floor(canvasSize * 0.35);
|
|
43
|
+
const innerR = Math.floor(canvasSize * 0.25);
|
|
44
|
+
const h = Math.floor(canvasSize * 0.6);
|
|
45
|
+
|
|
46
|
+
// Hollow cylinder
|
|
47
|
+
commands.push(...transformShapes.drawHollowCylinder(cx, cy, outerR, innerR, h, color));
|
|
48
|
+
|
|
49
|
+
return commands;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Lesson: Combine (Ghép khối)
|
|
54
|
+
* Box + Cylinder (e.g., a wheel on an axle, or a tower on a base)
|
|
55
|
+
*/
|
|
56
|
+
function lessonCombine(canvasSize = 32, color = '#607d8b') {
|
|
57
|
+
const commands = [];
|
|
58
|
+
const cx = Math.floor(canvasSize / 2);
|
|
59
|
+
const cy = Math.floor(canvasSize / 2);
|
|
60
|
+
|
|
61
|
+
// 1. Base Box
|
|
62
|
+
const w = Math.floor(canvasSize * 0.5);
|
|
63
|
+
const h = Math.floor(canvasSize * 0.2);
|
|
64
|
+
const d = Math.floor(canvasSize * 0.3);
|
|
65
|
+
const dx = Math.floor(d * 0.7);
|
|
66
|
+
const dy = Math.floor(d * 0.5);
|
|
67
|
+
|
|
68
|
+
const fX = cx - Math.floor(w/2);
|
|
69
|
+
const fY = cy + Math.floor(canvasSize * 0.2);
|
|
70
|
+
|
|
71
|
+
// Front face
|
|
72
|
+
commands.push(shapes.drawRectangle(fX, fY, w, h, color, false));
|
|
73
|
+
// Top face (oblique)
|
|
74
|
+
commands.push(shapes.drawLine(fX, fY, fX + dx, fY - dy, color));
|
|
75
|
+
commands.push(shapes.drawLine(fX + w, fY, fX + w + dx, fY - dy, color));
|
|
76
|
+
commands.push(shapes.drawLine(fX + dx, fY - dy, fX + w + dx, fY - dy, color));
|
|
77
|
+
|
|
78
|
+
// 2. Intersecting Cylinder (Tower on top of box)
|
|
79
|
+
const cylR = Math.floor(canvasSize * 0.15);
|
|
80
|
+
const cylH = Math.floor(canvasSize * 0.4);
|
|
81
|
+
const cylX = cx + Math.floor(dx/2);
|
|
82
|
+
const cylY = fY - Math.floor(dy/2); // Center of the top face
|
|
83
|
+
|
|
84
|
+
// Draw cylinder standing on the box
|
|
85
|
+
const topY = cylY - cylH;
|
|
86
|
+
// Top ellipse
|
|
87
|
+
commands.push(shapes.drawEllipse(cylX, topY, cylR, Math.floor(cylR * 0.3), '#ff9800', false));
|
|
88
|
+
// Base ellipse (intersecting the box top face)
|
|
89
|
+
commands.push(shapes.drawEllipse(cylX, cylY, cylR, Math.floor(cylR * 0.3), '#ff9800', false));
|
|
90
|
+
// Vertical edges
|
|
91
|
+
commands.push(shapes.drawLine(cylX - cylR, topY, cylX - cylR, cylY, '#ff9800'));
|
|
92
|
+
commands.push(shapes.drawLine(cylX + cylR, topY, cylX + cylR, cylY, '#ff9800'));
|
|
93
|
+
|
|
94
|
+
return commands;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = {
|
|
98
|
+
lessonRotate,
|
|
99
|
+
lessonCut,
|
|
100
|
+
lessonHollow,
|
|
101
|
+
lessonCombine
|
|
102
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const shapes = require('./shapes');
|
|
2
|
+
const ellipseShapes = require('./ellipse_shapes');
|
|
3
|
+
|
|
4
|
+
function drawEgg(cx, cy, r, color) {
|
|
5
|
+
const commands = [];
|
|
6
|
+
// Approximating an egg by drawing two halves (top stretched, bottom semi-circle)
|
|
7
|
+
// We'll use multiple ellipses overlapping, or a polygon approximation
|
|
8
|
+
// Let's use a simpler polygon approximation for the egg contour
|
|
9
|
+
const points = [];
|
|
10
|
+
for (let i = 0; i <= 36; i++) {
|
|
11
|
+
const t = (i / 36) * 2 * Math.PI;
|
|
12
|
+
const px = r * Math.cos(t);
|
|
13
|
+
// Stretch the top half
|
|
14
|
+
const stretch = Math.sin(t) < 0 ? 1.5 : 1.0;
|
|
15
|
+
const py = r * Math.sin(t) * stretch;
|
|
16
|
+
points.push({ x: Math.round(cx + px), y: Math.round(cy + py) });
|
|
17
|
+
}
|
|
18
|
+
commands.push(shapes.drawPolyline(points, color));
|
|
19
|
+
|
|
20
|
+
// Add cross contour to show 3D volume
|
|
21
|
+
commands.push(shapes.drawEllipse(cx, cy + Math.floor(r * 0.2), r, Math.floor(r * 0.3), '#03a9f4', false));
|
|
22
|
+
return commands;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function drawBentTube(cx, cy, r, length, color) {
|
|
26
|
+
const commands = [];
|
|
27
|
+
// Draw a curved tube using a semicircular path
|
|
28
|
+
const pathR = length; // radius of the bend path
|
|
29
|
+
// We place ellipses along the arc
|
|
30
|
+
for (let i = 0; i <= 6; i++) {
|
|
31
|
+
const angle = (i / 6) * Math.PI; // 0 to 180 degrees
|
|
32
|
+
const ex = Math.round(cx + pathR * Math.cos(angle));
|
|
33
|
+
const ey = Math.round(cy - pathR * Math.sin(angle));
|
|
34
|
+
// Rotate the ellipse to be perpendicular to the path
|
|
35
|
+
commands.push(...ellipseShapes.drawRotatedEllipse(ex, ey, Math.floor(r * 0.3), r, angle, color));
|
|
36
|
+
}
|
|
37
|
+
// Connect inner and outer edges (approximate arcs)
|
|
38
|
+
const innerPts = [], outerPts = [];
|
|
39
|
+
for (let i = 0; i <= 18; i++) {
|
|
40
|
+
const a = (i / 18) * Math.PI;
|
|
41
|
+
innerPts.push({ x: Math.round(cx + (pathR - r) * Math.cos(a)), y: Math.round(cy - (pathR - r) * Math.sin(a)) });
|
|
42
|
+
outerPts.push({ x: Math.round(cx + (pathR + r) * Math.cos(a)), y: Math.round(cy - (pathR + r) * Math.sin(a)) });
|
|
43
|
+
}
|
|
44
|
+
commands.push(shapes.drawPolyline(innerPts, color));
|
|
45
|
+
commands.push(shapes.drawPolyline(outerPts, color));
|
|
46
|
+
|
|
47
|
+
return commands;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function drawTruncatedSphere(cx, cy, r, color) {
|
|
51
|
+
const commands = [];
|
|
52
|
+
// Sphere outline (partial)
|
|
53
|
+
const points = [];
|
|
54
|
+
const cutAngle = Math.PI / 4; // Top section cut off
|
|
55
|
+
for (let i = 0; i <= 36; i++) {
|
|
56
|
+
const t = (i / 36) * 2 * Math.PI;
|
|
57
|
+
// Skip the top part
|
|
58
|
+
if (t > Math.PI + cutAngle && t < 2 * Math.PI - cutAngle) continue;
|
|
59
|
+
points.push({ x: Math.round(cx + r * Math.cos(t)), y: Math.round(cy + r * Math.sin(t)) });
|
|
60
|
+
}
|
|
61
|
+
commands.push(shapes.drawPolyline(points, color));
|
|
62
|
+
|
|
63
|
+
// Cut surface (ellipse)
|
|
64
|
+
const cutY = Math.round(cy + r * Math.sin(Math.PI + cutAngle));
|
|
65
|
+
const cutRx = Math.round(r * Math.cos(cutAngle));
|
|
66
|
+
const cutRy = Math.floor(cutRx * 0.3);
|
|
67
|
+
commands.push(shapes.drawEllipse(cx, cutY, cutRx, cutRy, '#ff5722', false)); // Highlight cut surface
|
|
68
|
+
|
|
69
|
+
// Cross contour to reinforce sphere shape
|
|
70
|
+
commands.push(shapes.drawEllipse(cx, cy + Math.floor(r*0.2), r, Math.floor(r*0.3), '#03a9f4', false));
|
|
71
|
+
|
|
72
|
+
return commands;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function drawHollowCylinder(cx, cy, outerR, innerR, h, color) {
|
|
76
|
+
const commands = [];
|
|
77
|
+
const topY = cy - Math.floor(h/2);
|
|
78
|
+
const botY = cy + Math.floor(h/2);
|
|
79
|
+
|
|
80
|
+
// Outer bottom
|
|
81
|
+
commands.push(shapes.drawEllipse(cx, botY, outerR, Math.floor(outerR * 0.3), color, false));
|
|
82
|
+
|
|
83
|
+
// Outer top
|
|
84
|
+
commands.push(shapes.drawEllipse(cx, topY, outerR, Math.floor(outerR * 0.3), color, false));
|
|
85
|
+
// Inner top (the hole)
|
|
86
|
+
commands.push(shapes.drawEllipse(cx, topY, innerR, Math.floor(innerR * 0.3), '#e91e63', false));
|
|
87
|
+
|
|
88
|
+
// Inner bottom (visible through hole - rough approximation)
|
|
89
|
+
// We just draw the back curve of the inner bottom ellipse
|
|
90
|
+
// For simplicity, we draw the full ellipse but lighter
|
|
91
|
+
commands.push(shapes.drawEllipse(cx, botY, innerR, Math.floor(innerR * 0.3), '#bdbdbd', false));
|
|
92
|
+
|
|
93
|
+
// Vertical edges
|
|
94
|
+
commands.push(shapes.drawLine(cx - outerR, topY, cx - outerR, botY, color));
|
|
95
|
+
commands.push(shapes.drawLine(cx + outerR, topY, cx + outerR, botY, color));
|
|
96
|
+
|
|
97
|
+
// Inner edges (depth)
|
|
98
|
+
commands.push(shapes.drawLine(cx - innerR, topY, cx - innerR, botY, '#9e9e9e'));
|
|
99
|
+
commands.push(shapes.drawLine(cx + innerR, topY, cx + innerR, botY, '#9e9e9e'));
|
|
100
|
+
|
|
101
|
+
return commands;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = {
|
|
105
|
+
drawEgg,
|
|
106
|
+
drawBentTube,
|
|
107
|
+
drawTruncatedSphere,
|
|
108
|
+
drawHollowCylinder
|
|
109
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
const { registerTool, sendCommand } = require('../../core/server');
|
|
3
|
+
const lessons1 = require('./transform_lessons_1');
|
|
4
|
+
const lessons2 = require('./transform_lessons_2');
|
|
5
|
+
|
|
6
|
+
const hexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/).describe('Hex color e.g. #ff0000');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Register all shape transformation tools on the MCP server.
|
|
10
|
+
* @param {McpServer} server
|
|
11
|
+
*/
|
|
12
|
+
function register(server) {
|
|
13
|
+
// --- Scale & Deform ---
|
|
14
|
+
registerTool(server, 'art_tree_lesson_transform_stretch',
|
|
15
|
+
'Lesson: Transform - Stretch (Elongate a sphere or box)',
|
|
16
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#2196f3') },
|
|
17
|
+
async (p) => {
|
|
18
|
+
const commands = lessons1.lessonStretch(p.canvasSize, p.color);
|
|
19
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
20
|
+
return { content: [{ type: 'text', text: `✓ Stretch transformation lesson completed` }] };
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
registerTool(server, 'art_tree_lesson_transform_squash',
|
|
24
|
+
'Lesson: Transform - Squash (Compress a sphere or box)',
|
|
25
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#ff9800') },
|
|
26
|
+
async (p) => {
|
|
27
|
+
const commands = lessons1.lessonSquash(p.canvasSize, p.color);
|
|
28
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
29
|
+
return { content: [{ type: 'text', text: `✓ Squash transformation lesson completed` }] };
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
registerTool(server, 'art_tree_lesson_transform_taper',
|
|
33
|
+
'Lesson: Transform - Taper/Swell (Cylinder to Bottle)',
|
|
34
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#00bcd4') },
|
|
35
|
+
async (p) => {
|
|
36
|
+
const commands = lessons1.lessonTaperSwell(p.canvasSize, p.color);
|
|
37
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
38
|
+
return { content: [{ type: 'text', text: `✓ Taper/Swell transformation lesson completed` }] };
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
registerTool(server, 'art_tree_lesson_transform_bend',
|
|
42
|
+
'Lesson: Transform - Bend (Cylinder to curved tube)',
|
|
43
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#e91e63') },
|
|
44
|
+
async (p) => {
|
|
45
|
+
const commands = lessons1.lessonBend(p.canvasSize, p.color);
|
|
46
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
47
|
+
return { content: [{ type: 'text', text: `✓ Bend transformation lesson completed` }] };
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// --- Boolean & Orientation ---
|
|
51
|
+
registerTool(server, 'art_tree_lesson_transform_rotate',
|
|
52
|
+
'Lesson: Transform - Rotate (Analyze rotation around X, Y, Z)',
|
|
53
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#ff5722') },
|
|
54
|
+
async (p) => {
|
|
55
|
+
const commands = lessons2.lessonRotate(p.canvasSize, p.color);
|
|
56
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
57
|
+
return { content: [{ type: 'text', text: `✓ Rotate transformation lesson completed` }] };
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
registerTool(server, 'art_tree_lesson_transform_cut',
|
|
61
|
+
'Lesson: Transform - Cut (Truncate a sphere)',
|
|
62
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#8bc34a') },
|
|
63
|
+
async (p) => {
|
|
64
|
+
const commands = lessons2.lessonCut(p.canvasSize, p.color);
|
|
65
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
66
|
+
return { content: [{ type: 'text', text: `✓ Cut transformation lesson completed` }] };
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
registerTool(server, 'art_tree_lesson_transform_hollow',
|
|
70
|
+
'Lesson: Transform - Hollow (Hollow out a cylinder)',
|
|
71
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#3f51b5') },
|
|
72
|
+
async (p) => {
|
|
73
|
+
const commands = lessons2.lessonHollow(p.canvasSize, p.color);
|
|
74
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
75
|
+
return { content: [{ type: 'text', text: `✓ Hollow transformation lesson completed` }] };
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
registerTool(server, 'art_tree_lesson_transform_combine',
|
|
79
|
+
'Lesson: Transform - Combine (Intersect a box and a cylinder)',
|
|
80
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#607d8b') },
|
|
81
|
+
async (p) => {
|
|
82
|
+
const commands = lessons2.lessonCombine(p.canvasSize, p.color);
|
|
83
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
84
|
+
return { content: [{ type: 'text', text: `✓ Combine transformation lesson completed` }] };
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
registerTool(server, 'art_tree_lesson_transform_catalog',
|
|
88
|
+
'Get the catalog of all available Transformation lessons',
|
|
89
|
+
{},
|
|
90
|
+
() => {
|
|
91
|
+
const text = `📚 Transformation Lessons:
|
|
92
|
+
• transform_stretch: Stretch/Elongate (Cầu -> Trứng)
|
|
93
|
+
• transform_squash: Squash/Compress (Cầu -> Đĩa dẹt)
|
|
94
|
+
• transform_taper: Taper/Swell (Trụ -> Chai)
|
|
95
|
+
• transform_bend: Bend (Trụ -> Ống cong)
|
|
96
|
+
• transform_rotate: Rotate (Xoay 3D)
|
|
97
|
+
• transform_cut: Cut/Truncate (Cắt khối)
|
|
98
|
+
• transform_hollow: Hollow out (Khoét rỗng)
|
|
99
|
+
• transform_combine: Combine (Ghép khối)`;
|
|
100
|
+
return { content: [{ type: 'text', text }] };
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = { register };
|
package/package.json
CHANGED
package/tools/workspace.js
CHANGED
|
@@ -19,14 +19,24 @@ function register(server) {
|
|
|
19
19
|
{},
|
|
20
20
|
() => ({ action: 'getActiveTabId' }));
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
const { sendCommand } = require('../core/server');
|
|
23
|
+
server.tool('workspace_create_tab',
|
|
23
24
|
'Create a new blank canvas tab',
|
|
24
25
|
{
|
|
25
26
|
name: z.string().optional().describe('Tab name'),
|
|
26
|
-
width: z.number().int().
|
|
27
|
-
height: z.number().int().
|
|
27
|
+
width: z.number().int().optional().describe('Canvas width'),
|
|
28
|
+
height: z.number().int().optional().describe('Canvas height')
|
|
28
29
|
},
|
|
29
|
-
(p) =>
|
|
30
|
+
async (p) => {
|
|
31
|
+
if (p.width === undefined || p.height === undefined) {
|
|
32
|
+
return {
|
|
33
|
+
isError: true,
|
|
34
|
+
content: [{ type: 'text', text: 'Please provide the width and height dimensions (resize) when creating a new canvas.' }]
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return sendCommand({ action: 'createTab', ...p });
|
|
38
|
+
}
|
|
39
|
+
);
|
|
30
40
|
|
|
31
41
|
registerTool(server, 'workspace_switch_tab',
|
|
32
42
|
'Switch to a different tab by ID (get IDs from workspace_list_tabs)',
|