@pixel-normal-edit/mcp 2.0.6 → 2.0.8
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/analysis_lessons.js +32 -0
- package/domains/art-tree/analysis_shapes.js +146 -0
- package/domains/art-tree/analysis_tools.js +49 -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 +32 -0
- package/domains/art-tree/layer_lessons.js +32 -0
- package/domains/art-tree/layer_shapes.js +117 -0
- package/domains/art-tree/layer_tools.js +43 -0
- package/domains/art-tree/lessons.js +119 -7
- 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/material_lessons.js +71 -0
- package/domains/art-tree/material_shapes.js +130 -0
- package/domains/art-tree/material_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/sky_lessons_1.js +82 -0
- package/domains/art-tree/sky_shapes_1.js +68 -0
- package/domains/art-tree/sky_tools.js +51 -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 +69 -15
- 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/domains/art-tree/vocab_lessons.js +32 -0
- package/domains/art-tree/vocab_shapes.js +116 -0
- package/domains/art-tree/vocab_tools.js +41 -0
- package/package.json +1 -1
- package/tools/index.js +2 -0
- package/tools/layer.js +80 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
const shapes = require('./shapes');
|
|
2
|
+
const perspShapes = require('./perspective_shapes');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Lesson: 1-Point Perspective
|
|
6
|
+
*/
|
|
7
|
+
function lesson1PointPerspective(canvasSize = 32, color = '#2196f3') {
|
|
8
|
+
const commands = [];
|
|
9
|
+
const horizonY = Math.floor(canvasSize * 0.4);
|
|
10
|
+
const vpX = Math.floor(canvasSize * 0.5);
|
|
11
|
+
const vpY = horizonY;
|
|
12
|
+
|
|
13
|
+
// Horizon & VP
|
|
14
|
+
commands.push(...perspShapes.drawHorizon(canvasSize, horizonY));
|
|
15
|
+
commands.push(...perspShapes.drawVP(vpX, vpY));
|
|
16
|
+
|
|
17
|
+
// Box below horizon (seeing top face)
|
|
18
|
+
const w = Math.floor(canvasSize * 0.2);
|
|
19
|
+
const h = Math.floor(canvasSize * 0.15);
|
|
20
|
+
commands.push(...perspShapes.draw1PointBox(vpX, vpY, Math.floor(canvasSize * 0.2), horizonY + Math.floor(canvasSize * 0.2), w, h, 0.4, color));
|
|
21
|
+
|
|
22
|
+
// Box above horizon (seeing bottom face)
|
|
23
|
+
commands.push(...perspShapes.draw1PointBox(vpX, vpY, Math.floor(canvasSize * 0.6), horizonY - Math.floor(canvasSize * 0.3), w, h, 0.4, '#e91e63'));
|
|
24
|
+
|
|
25
|
+
return commands;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Lesson: 2-Point Perspective
|
|
30
|
+
*/
|
|
31
|
+
function lesson2PointPerspective(canvasSize = 32, color = '#4caf50') {
|
|
32
|
+
const commands = [];
|
|
33
|
+
const horizonY = Math.floor(canvasSize * 0.5);
|
|
34
|
+
const vp1X = Math.floor(canvasSize * 0.1);
|
|
35
|
+
const vp2X = Math.floor(canvasSize * 0.9);
|
|
36
|
+
|
|
37
|
+
// Horizon & VPs
|
|
38
|
+
commands.push(...perspShapes.drawHorizon(canvasSize, horizonY));
|
|
39
|
+
commands.push(...perspShapes.drawVP(vp1X, horizonY));
|
|
40
|
+
commands.push(...perspShapes.drawVP(vp2X, horizonY));
|
|
41
|
+
|
|
42
|
+
// 2-Point Box straddling the horizon
|
|
43
|
+
const startX = Math.floor(canvasSize * 0.4);
|
|
44
|
+
const startY = Math.floor(canvasSize * 0.3);
|
|
45
|
+
const h = Math.floor(canvasSize * 0.4);
|
|
46
|
+
|
|
47
|
+
commands.push(...perspShapes.draw2PointBox(vp1X, horizonY, vp2X, horizonY, startX, startY, h, 0.3, 0.4, color));
|
|
48
|
+
|
|
49
|
+
return commands;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Lesson: 3-Point Perspective (Bird's eye view)
|
|
54
|
+
*/
|
|
55
|
+
function lesson3PointPerspective(canvasSize = 32, color = '#ff9800') {
|
|
56
|
+
const commands = [];
|
|
57
|
+
const horizonY = Math.floor(canvasSize * 0.2);
|
|
58
|
+
const vp1X = Math.floor(canvasSize * 0.1);
|
|
59
|
+
const vp2X = Math.floor(canvasSize * 0.9);
|
|
60
|
+
const vp3X = Math.floor(canvasSize * 0.5);
|
|
61
|
+
const vp3Y = Math.floor(canvasSize * 1.5); // VP3 far below (Bird's eye)
|
|
62
|
+
|
|
63
|
+
// Horizon & VPs
|
|
64
|
+
commands.push(...perspShapes.drawHorizon(canvasSize, horizonY));
|
|
65
|
+
commands.push(...perspShapes.drawVP(vp1X, horizonY));
|
|
66
|
+
commands.push(...perspShapes.drawVP(vp2X, horizonY));
|
|
67
|
+
// VP3 is usually off-canvas, but we draw a line to it to show direction
|
|
68
|
+
|
|
69
|
+
// We approximate a 3-point box by drawing converging vertical lines to VP3
|
|
70
|
+
const topY = Math.floor(canvasSize * 0.4);
|
|
71
|
+
const h = Math.floor(canvasSize * 0.4);
|
|
72
|
+
|
|
73
|
+
// Center edge converging to VP3
|
|
74
|
+
commands.push(shapes.drawLine(vp3X, topY, vp3X, topY+h, color)); // simplified
|
|
75
|
+
commands.push(shapes.drawLine(vp3X, topY+h, vp3X, vp3Y, '#e0e0e0')); // Guide to VP3
|
|
76
|
+
|
|
77
|
+
// Left and Right top edges to VP1, VP2
|
|
78
|
+
commands.push(shapes.drawLine(vp3X, topY, vp1X, horizonY, '#e0e0e0'));
|
|
79
|
+
commands.push(shapes.drawLine(vp3X, topY, vp2X, horizonY, '#e0e0e0'));
|
|
80
|
+
|
|
81
|
+
// Draw the rest of the box structure to simulate 3-point
|
|
82
|
+
const lX = vp3X - Math.floor(canvasSize * 0.15);
|
|
83
|
+
const rX = vp3X + Math.floor(canvasSize * 0.2);
|
|
84
|
+
// Vertical edges also converge to VP3 slightly (foreshortening)
|
|
85
|
+
commands.push(shapes.drawLine(lX, topY + Math.floor(canvasSize * 0.1), lX + 2, topY + h, color));
|
|
86
|
+
commands.push(shapes.drawLine(rX, topY + Math.floor(canvasSize * 0.15), rX - 3, topY + h - 5, color));
|
|
87
|
+
|
|
88
|
+
return commands;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Lesson: Foreshortening (Depth foreshortening)
|
|
93
|
+
* Drawing a cylinder pointing directly at the viewer
|
|
94
|
+
*/
|
|
95
|
+
function lessonForeshortening(canvasSize = 32, color = '#9c27b0') {
|
|
96
|
+
const commands = [];
|
|
97
|
+
const cx = Math.floor(canvasSize / 2);
|
|
98
|
+
const cy = Math.floor(canvasSize / 2);
|
|
99
|
+
|
|
100
|
+
// Draw overlapping circles getting larger (coming towards viewer)
|
|
101
|
+
const numCircles = 5;
|
|
102
|
+
const maxR = Math.floor(canvasSize * 0.4);
|
|
103
|
+
const minR = Math.floor(canvasSize * 0.15);
|
|
104
|
+
|
|
105
|
+
for(let i=0; i<numCircles; i++) {
|
|
106
|
+
const t = i / (numCircles - 1); // 0 to 1
|
|
107
|
+
// Radius grows exponentially to simulate perspective
|
|
108
|
+
const r = minR + (maxR - minR) * (t * t);
|
|
109
|
+
// Y position shifts slightly to show overlap
|
|
110
|
+
const y = cy - Math.floor(canvasSize * 0.2) + Math.floor(canvasSize * 0.4 * t);
|
|
111
|
+
|
|
112
|
+
// Draw the circle
|
|
113
|
+
commands.push(shapes.drawCircle(cx, y, Math.floor(r), color, false));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Draw bounding lines converging
|
|
117
|
+
commands.push(shapes.drawLine(cx - minR, cy - Math.floor(canvasSize * 0.2), cx - maxR, cy + Math.floor(canvasSize * 0.2), '#bdbdbd'));
|
|
118
|
+
commands.push(shapes.drawLine(cx + minR, cy - Math.floor(canvasSize * 0.2), cx + maxR, cy + Math.floor(canvasSize * 0.2), '#bdbdbd'));
|
|
119
|
+
|
|
120
|
+
return commands;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function getPerspectiveLessonCatalog() {
|
|
124
|
+
return [
|
|
125
|
+
{ id: 'persp_1point', name: '1-Point Perspective', description: 'Horizon, 1 VP, Front vs Receding planes', fn: lesson1PointPerspective },
|
|
126
|
+
{ id: 'persp_2point', name: '2-Point Perspective', description: 'Horizon, 2 VPs, Edges facing viewer', fn: lesson2PointPerspective },
|
|
127
|
+
{ id: 'persp_3point', name: '3-Point Perspective', description: '3 VPs (Bird\'s eye view)', fn: lesson3PointPerspective },
|
|
128
|
+
{ id: 'persp_foreshorten', name: 'Foreshortening', description: 'Cylinder pointing at viewer', fn: lessonForeshortening },
|
|
129
|
+
];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = {
|
|
133
|
+
lesson1PointPerspective,
|
|
134
|
+
lesson2PointPerspective,
|
|
135
|
+
lesson3PointPerspective,
|
|
136
|
+
lessonForeshortening,
|
|
137
|
+
getPerspectiveLessonCatalog
|
|
138
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const shapes = require('./shapes');
|
|
2
|
+
|
|
3
|
+
function drawHorizon(canvasSize, yPos) {
|
|
4
|
+
const commands = [];
|
|
5
|
+
// Horizon line (Eye level)
|
|
6
|
+
commands.push(shapes.drawLine(0, yPos, canvasSize, yPos, '#9e9e9e'));
|
|
7
|
+
return commands;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function drawVP(x, y) {
|
|
11
|
+
// Red dot for vanishing point
|
|
12
|
+
return [shapes.drawCircle(x, y, 2, '#ff0000', true)];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function draw1PointBox(vpX, vpY, fX, fY, w, h, depthFactor, color) {
|
|
16
|
+
const commands = [];
|
|
17
|
+
// Front face (Front-facing plane)
|
|
18
|
+
commands.push(shapes.drawRectangle(fX, fY, w, h, color, false));
|
|
19
|
+
|
|
20
|
+
// Orthogonal lines to VP (Depth lines)
|
|
21
|
+
const guideColor = '#e0e0e0';
|
|
22
|
+
commands.push(shapes.drawLine(fX, fY, vpX, vpY, guideColor));
|
|
23
|
+
commands.push(shapes.drawLine(fX+w, fY, vpX, vpY, guideColor));
|
|
24
|
+
commands.push(shapes.drawLine(fX, fY+h, vpX, vpY, guideColor));
|
|
25
|
+
commands.push(shapes.drawLine(fX+w, fY+h, vpX, vpY, guideColor));
|
|
26
|
+
|
|
27
|
+
// Back face (Receding plane calculation)
|
|
28
|
+
// Distance from front face to VP
|
|
29
|
+
const dTLx = vpX - fX;
|
|
30
|
+
const dTLy = vpY - fY;
|
|
31
|
+
|
|
32
|
+
// Back face coordinates based on depth factor (0 to 1)
|
|
33
|
+
const bX = fX + Math.floor(dTLx * depthFactor);
|
|
34
|
+
const bY = fY + Math.floor(dTLy * depthFactor);
|
|
35
|
+
const bW = Math.floor(w * (1 - depthFactor));
|
|
36
|
+
const bH = Math.floor(h * (1 - depthFactor));
|
|
37
|
+
|
|
38
|
+
commands.push(shapes.drawRectangle(bX, bY, bW, bH, color, false));
|
|
39
|
+
|
|
40
|
+
// Connect corners
|
|
41
|
+
commands.push(shapes.drawLine(fX, fY, bX, bY, color));
|
|
42
|
+
commands.push(shapes.drawLine(fX+w, fY, bX+bW, bY, color));
|
|
43
|
+
commands.push(shapes.drawLine(fX, fY+h, bX, bY+bH, color));
|
|
44
|
+
commands.push(shapes.drawLine(fX+w, fY+h, bX+bW, bY+bH, color));
|
|
45
|
+
|
|
46
|
+
return commands;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function draw2PointBox(vp1X, vp1Y, vp2X, vp2Y, startX, startY, h, depth1, depth2, color) {
|
|
50
|
+
const commands = [];
|
|
51
|
+
const guideColor = '#e0e0e0';
|
|
52
|
+
|
|
53
|
+
// Center vertical edge (faces viewer)
|
|
54
|
+
commands.push(shapes.drawLine(startX, startY, startX, startY + h, color));
|
|
55
|
+
|
|
56
|
+
// Orthogonals to VP1 (Left)
|
|
57
|
+
commands.push(shapes.drawLine(startX, startY, vp1X, vp1Y, guideColor));
|
|
58
|
+
commands.push(shapes.drawLine(startX, startY + h, vp1X, vp1Y, guideColor));
|
|
59
|
+
|
|
60
|
+
// Orthogonals to VP2 (Right)
|
|
61
|
+
commands.push(shapes.drawLine(startX, startY, vp2X, vp2Y, guideColor));
|
|
62
|
+
commands.push(shapes.drawLine(startX, startY + h, vp2X, vp2Y, guideColor));
|
|
63
|
+
|
|
64
|
+
// Left vertical edge
|
|
65
|
+
const lX = startX + Math.floor((vp1X - startX) * depth1);
|
|
66
|
+
const lY1 = startY + Math.floor((vp1Y - startY) * depth1);
|
|
67
|
+
const lY2 = (startY + h) + Math.floor((vp1Y - (startY + h)) * depth1);
|
|
68
|
+
commands.push(shapes.drawLine(lX, lY1, lX, lY2, color));
|
|
69
|
+
commands.push(shapes.drawLine(startX, startY, lX, lY1, color));
|
|
70
|
+
commands.push(shapes.drawLine(startX, startY+h, lX, lY2, color));
|
|
71
|
+
|
|
72
|
+
// Right vertical edge
|
|
73
|
+
const rX = startX + Math.floor((vp2X - startX) * depth2);
|
|
74
|
+
const rY1 = startY + Math.floor((vp2Y - startY) * depth2);
|
|
75
|
+
const rY2 = (startY + h) + Math.floor((vp2Y - (startY + h)) * depth2);
|
|
76
|
+
commands.push(shapes.drawLine(rX, rY1, rX, rY2, color));
|
|
77
|
+
commands.push(shapes.drawLine(startX, startY, rX, rY1, color));
|
|
78
|
+
commands.push(shapes.drawLine(startX, startY+h, rX, rY2, color));
|
|
79
|
+
|
|
80
|
+
// Back vertical edge (intersection of lines from left/right edges to opposite VPs)
|
|
81
|
+
// For simplicity, we just draw the top and bottom back lines converging to VPs
|
|
82
|
+
commands.push(shapes.drawLine(lX, lY1, vp2X, vp2Y, guideColor));
|
|
83
|
+
commands.push(shapes.drawLine(rX, rY1, vp1X, vp1Y, guideColor));
|
|
84
|
+
|
|
85
|
+
// Approximate intersection for top back corner
|
|
86
|
+
// (In a full implementation, we'd calculate line intersection, here we just show the structure)
|
|
87
|
+
|
|
88
|
+
return commands;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = {
|
|
92
|
+
drawHorizon,
|
|
93
|
+
drawVP,
|
|
94
|
+
draw1PointBox,
|
|
95
|
+
draw2PointBox
|
|
96
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
const { registerTool, sendCommand } = require('../../core/server');
|
|
3
|
+
const perspLessons = require('./perspective_lessons');
|
|
4
|
+
|
|
5
|
+
const hexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/).describe('Hex color e.g. #ff0000');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Register all perspective tools on the MCP server.
|
|
9
|
+
* @param {McpServer} server
|
|
10
|
+
*/
|
|
11
|
+
function register(server) {
|
|
12
|
+
registerTool(server, 'art_tree_lesson_persp_1point',
|
|
13
|
+
'Lesson: Perspective - 1-Point Perspective (Horizon, 1 VP, Front vs Receding)',
|
|
14
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#2196f3') },
|
|
15
|
+
async (p) => {
|
|
16
|
+
const commands = perspLessons.lesson1PointPerspective(p.canvasSize, p.color);
|
|
17
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
18
|
+
return { content: [{ type: 'text', text: `✓ 1-Point Perspective lesson completed` }] };
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
registerTool(server, 'art_tree_lesson_persp_2point',
|
|
22
|
+
'Lesson: Perspective - 2-Point Perspective (Horizon, 2 VPs)',
|
|
23
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#4caf50') },
|
|
24
|
+
async (p) => {
|
|
25
|
+
const commands = perspLessons.lesson2PointPerspective(p.canvasSize, p.color);
|
|
26
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
27
|
+
return { content: [{ type: 'text', text: `✓ 2-Point Perspective lesson completed` }] };
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
registerTool(server, 'art_tree_lesson_persp_3point',
|
|
31
|
+
'Lesson: Perspective - 3-Point Perspective (Bird\'s eye view)',
|
|
32
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#ff9800') },
|
|
33
|
+
async (p) => {
|
|
34
|
+
const commands = perspLessons.lesson3PointPerspective(p.canvasSize, p.color);
|
|
35
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
36
|
+
return { content: [{ type: 'text', text: `✓ 3-Point Perspective lesson completed` }] };
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
registerTool(server, 'art_tree_lesson_persp_foreshorten',
|
|
40
|
+
'Lesson: Perspective - Foreshortening (Depth foreshortening)',
|
|
41
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#9c27b0') },
|
|
42
|
+
async (p) => {
|
|
43
|
+
const commands = perspLessons.lessonForeshortening(p.canvasSize, p.color);
|
|
44
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
45
|
+
return { content: [{ type: 'text', text: `✓ Foreshortening lesson completed` }] };
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
registerTool(server, 'art_tree_lesson_persp_catalog',
|
|
49
|
+
'Get the catalog of all available Perspective lessons',
|
|
50
|
+
{},
|
|
51
|
+
() => {
|
|
52
|
+
const catalog = perspLessons.getPerspectiveLessonCatalog();
|
|
53
|
+
const text = catalog.map(l =>
|
|
54
|
+
` • ${l.id}: ${l.name} — ${l.description}`
|
|
55
|
+
).join('\n');
|
|
56
|
+
return { content: [{ type: 'text', text: `📚 Perspective Lessons:\n\n${text}` }] };
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = { register };
|
|
@@ -182,9 +182,24 @@ function drawGrid(startX, startY, cols, rows, cellSize, color) {
|
|
|
182
182
|
return commands;
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Draw a continuous polyline from a list of points.
|
|
187
|
+
* @param {Array<{x: number, y: number}>} points - Array of vertices
|
|
188
|
+
* @param {string} color - Hex color
|
|
189
|
+
* @returns {Array<Object>} Array of Command payloads
|
|
190
|
+
*/
|
|
191
|
+
function drawPolyline(points, color) {
|
|
192
|
+
const commands = [];
|
|
193
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
194
|
+
commands.push(drawLine(points[i].x, points[i].y, points[i+1].x, points[i+1].y, color));
|
|
195
|
+
}
|
|
196
|
+
return commands;
|
|
197
|
+
}
|
|
198
|
+
|
|
185
199
|
module.exports = {
|
|
186
200
|
PRIMITIVE_ACTIONS,
|
|
187
201
|
drawLine,
|
|
202
|
+
drawPolyline,
|
|
188
203
|
drawSquare,
|
|
189
204
|
drawRectangle,
|
|
190
205
|
drawCircle,
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const skyShapes1 = require('./sky_shapes_1');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lesson 1: Sun Shapes (Hình dạng mặt trời)
|
|
5
|
+
* Compares Basic, Soft, and Rays
|
|
6
|
+
*/
|
|
7
|
+
function lessonSunShapes(canvasSize = 32) {
|
|
8
|
+
const commands = [];
|
|
9
|
+
const y = Math.floor(canvasSize / 2);
|
|
10
|
+
const r = Math.floor(canvasSize * 0.15);
|
|
11
|
+
|
|
12
|
+
// Left: Basic
|
|
13
|
+
commands.push(...skyShapes1.drawSunBasic(Math.floor(canvasSize * 0.2), y, r));
|
|
14
|
+
// Center: Soft
|
|
15
|
+
commands.push(...skyShapes1.drawSunSoft(Math.floor(canvasSize * 0.5), y, r));
|
|
16
|
+
// Right: Rays
|
|
17
|
+
commands.push(...skyShapes1.drawSunRays(Math.floor(canvasSize * 0.8), y, r));
|
|
18
|
+
|
|
19
|
+
return commands;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Lesson 2: Sunset Colors (Màu sắc buổi chiều)
|
|
24
|
+
* Shows the progression of colors from blue to yellow
|
|
25
|
+
*/
|
|
26
|
+
function lessonSunsetColors(canvasSize = 32) {
|
|
27
|
+
const commands = [];
|
|
28
|
+
|
|
29
|
+
// The classic sunset gradient: Blue -> Purple -> Pink -> Orange -> Yellow (top to bottom)
|
|
30
|
+
const sunsetPalette = [
|
|
31
|
+
'#3f51b5', // Blue
|
|
32
|
+
'#9c27b0', // Purple
|
|
33
|
+
'#e91e63', // Pink
|
|
34
|
+
'#ff9800', // Orange
|
|
35
|
+
'#ffeb3b' // Yellow
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
commands.push(...skyShapes1.drawSkyGradient(0, 0, canvasSize, canvasSize, sunsetPalette));
|
|
39
|
+
|
|
40
|
+
return commands;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Lesson 3: Sunset Sky (Bầu trời hoàng hôn)
|
|
45
|
+
* Combines the gradient sky with a soft sun setting at the bottom
|
|
46
|
+
*/
|
|
47
|
+
function lessonSunsetSky(canvasSize = 32) {
|
|
48
|
+
const commands = [];
|
|
49
|
+
|
|
50
|
+
// 1. Draw the Sky
|
|
51
|
+
const sunsetPalette = [
|
|
52
|
+
'#1a237e', // Dark Blue
|
|
53
|
+
'#512da8', // Deep Purple
|
|
54
|
+
'#c2185b', // Magenta
|
|
55
|
+
'#ff5722', // Deep Orange
|
|
56
|
+
'#ffb300' // Amber
|
|
57
|
+
];
|
|
58
|
+
commands.push(...skyShapes1.drawSkyGradient(0, 0, canvasSize, canvasSize, sunsetPalette));
|
|
59
|
+
|
|
60
|
+
// 2. Draw the Sun (Soft glowing sun near the bottom)
|
|
61
|
+
const cx = Math.floor(canvasSize / 2);
|
|
62
|
+
const cy = Math.floor(canvasSize * 0.8);
|
|
63
|
+
const r = Math.floor(canvasSize * 0.15);
|
|
64
|
+
commands.push(...skyShapes1.drawSunSoft(cx, cy, r));
|
|
65
|
+
|
|
66
|
+
return commands;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function getSkyLesson1Catalog() {
|
|
70
|
+
return [
|
|
71
|
+
{ id: 'sky_sun_shapes', name: 'Sun Shapes', description: 'Basic vs Soft vs Rays', fn: lessonSunShapes },
|
|
72
|
+
{ id: 'sky_sunset_colors', name: 'Sunset Colors', description: 'Gradient bands from Blue to Yellow', fn: lessonSunsetColors },
|
|
73
|
+
{ id: 'sky_sunset_sky', name: 'Sunset Sky', description: 'Combining gradient sky with a soft sun', fn: lessonSunsetSky },
|
|
74
|
+
];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = {
|
|
78
|
+
lessonSunShapes,
|
|
79
|
+
lessonSunsetColors,
|
|
80
|
+
lessonSunsetSky,
|
|
81
|
+
getSkyLesson1Catalog
|
|
82
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const shapes = require('./shapes');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Draws a basic solid sun
|
|
5
|
+
*/
|
|
6
|
+
function drawSunBasic(cx, cy, r) {
|
|
7
|
+
return [shapes.drawCircle(cx, cy, r, '#ffeb3b', true)];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Draws a soft glowing sun using concentric circles with decreasing opacity
|
|
12
|
+
* Since we don't have true opacity in standard shapes yet, we use color stepping
|
|
13
|
+
* (white -> light yellow -> yellow -> orange)
|
|
14
|
+
*/
|
|
15
|
+
function drawSunSoft(cx, cy, r) {
|
|
16
|
+
const commands = [];
|
|
17
|
+
commands.push(shapes.drawCircle(cx, cy, Math.floor(r * 1.5), '#ffe082', true)); // Outer glow
|
|
18
|
+
commands.push(shapes.drawCircle(cx, cy, Math.floor(r * 1.2), '#ffeb3b', true)); // Mid glow
|
|
19
|
+
commands.push(shapes.drawCircle(cx, cy, Math.floor(r * 0.8), '#fff59d', true)); // Inner glow
|
|
20
|
+
commands.push(shapes.drawCircle(cx, cy, Math.floor(r * 0.4), '#ffffff', true)); // Core
|
|
21
|
+
return commands;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Draws a sun with distinct rays
|
|
26
|
+
*/
|
|
27
|
+
function drawSunRays(cx, cy, r) {
|
|
28
|
+
const commands = [];
|
|
29
|
+
const color = '#ffeb3b';
|
|
30
|
+
// Central body
|
|
31
|
+
commands.push(shapes.drawCircle(cx, cy, r, color, true));
|
|
32
|
+
|
|
33
|
+
// Outer rays
|
|
34
|
+
const rayL = Math.floor(r * 2);
|
|
35
|
+
for (let i = 0; i < 12; i++) {
|
|
36
|
+
const a = (i / 12) * 2 * Math.PI;
|
|
37
|
+
const x1 = Math.round(cx + (r + 2) * Math.cos(a));
|
|
38
|
+
const y1 = Math.round(cy + (r + 2) * Math.sin(a));
|
|
39
|
+
const x2 = Math.round(cx + rayL * Math.cos(a));
|
|
40
|
+
const y2 = Math.round(cy + rayL * Math.sin(a));
|
|
41
|
+
commands.push(shapes.drawLine(x1, y1, x2, y2, color));
|
|
42
|
+
}
|
|
43
|
+
return commands;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Draws a simulated gradient sky using horizontal bands of color
|
|
48
|
+
* @param {Array<string>} colors - Array of hex colors from top to bottom
|
|
49
|
+
*/
|
|
50
|
+
function drawSkyGradient(x, y, w, h, colors) {
|
|
51
|
+
const commands = [];
|
|
52
|
+
const bandHeight = Math.ceil(h / colors.length);
|
|
53
|
+
|
|
54
|
+
for (let i = 0; i < colors.length; i++) {
|
|
55
|
+
const bandY = y + i * bandHeight;
|
|
56
|
+
// To ensure we cover the total height, the last band fills the rest
|
|
57
|
+
const bh = (i === colors.length - 1) ? (y + h - bandY) : bandHeight;
|
|
58
|
+
commands.push(shapes.drawRectangle(x, bandY, w, bh, colors[i], true));
|
|
59
|
+
}
|
|
60
|
+
return commands;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = {
|
|
64
|
+
drawSunBasic,
|
|
65
|
+
drawSunSoft,
|
|
66
|
+
drawSunRays,
|
|
67
|
+
drawSkyGradient
|
|
68
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
const { registerTool, sendCommand } = require('../../core/server');
|
|
3
|
+
const skyLessons1 = require('./sky_lessons_1');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Register all sky and sun tools on the MCP server.
|
|
7
|
+
* @param {McpServer} server
|
|
8
|
+
*/
|
|
9
|
+
function register(server) {
|
|
10
|
+
// Phase 1 Tools
|
|
11
|
+
registerTool(server, 'art_tree_lesson_sky_sun_shapes',
|
|
12
|
+
'Lesson: Sun & Sky - Compare Basic, Soft, and Rays sun shapes',
|
|
13
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32) },
|
|
14
|
+
async (p) => {
|
|
15
|
+
const commands = skyLessons1.lessonSunShapes(p.canvasSize);
|
|
16
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
17
|
+
return { content: [{ type: 'text', text: `✓ Sun Shapes lesson completed` }] };
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
registerTool(server, 'art_tree_lesson_sky_sunset_colors',
|
|
21
|
+
'Lesson: Sun & Sky - Draw a sunset gradient (Blue to Yellow)',
|
|
22
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32) },
|
|
23
|
+
async (p) => {
|
|
24
|
+
const commands = skyLessons1.lessonSunsetColors(p.canvasSize);
|
|
25
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
26
|
+
return { content: [{ type: 'text', text: `✓ Sunset Colors lesson completed` }] };
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
registerTool(server, 'art_tree_lesson_sky_sunset_sky',
|
|
30
|
+
'Lesson: Sun & Sky - Combine gradient sky with a soft setting sun',
|
|
31
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32) },
|
|
32
|
+
async (p) => {
|
|
33
|
+
const commands = skyLessons1.lessonSunsetSky(p.canvasSize);
|
|
34
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
35
|
+
return { content: [{ type: 'text', text: `✓ Sunset Sky lesson completed` }] };
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
registerTool(server, 'art_tree_lesson_sky_catalog',
|
|
39
|
+
'Get the catalog of all available Sun & Sky lessons',
|
|
40
|
+
{},
|
|
41
|
+
() => {
|
|
42
|
+
const cat1 = skyLessons1.getSkyLesson1Catalog();
|
|
43
|
+
const catalog = [...cat1];
|
|
44
|
+
const text = catalog.map(l =>
|
|
45
|
+
` • ${l.id}: ${l.name} — ${l.description}`
|
|
46
|
+
).join('\n');
|
|
47
|
+
return { content: [{ type: 'text', text: `📚 Sun & Sky Lessons:\n\n${text}` }] };
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = { register };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
const shapes = require('./shapes');
|
|
2
|
+
const structureShapes = require('./structure_shapes');
|
|
3
|
+
const shapes3d = require('./3d_shapes');
|
|
4
|
+
const ellipseShapes = require('./ellipse_shapes');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Lesson: XYZ Axes (Vertical, Horizontal, Depth)
|
|
8
|
+
*/
|
|
9
|
+
function lessonXYZAxes(canvasSize = 32, color = '#2196f3') {
|
|
10
|
+
const commands = [];
|
|
11
|
+
const cx = Math.floor(canvasSize / 2);
|
|
12
|
+
const cy = Math.floor(canvasSize / 2);
|
|
13
|
+
const length = Math.floor(canvasSize * 0.4);
|
|
14
|
+
|
|
15
|
+
// Draw the axes
|
|
16
|
+
commands.push(...structureShapes.drawXYZAxes(cx, cy, length));
|
|
17
|
+
|
|
18
|
+
// Draw a simple box aligned with these axes to show how objects follow them
|
|
19
|
+
const w = Math.floor(length * 0.5);
|
|
20
|
+
const h = Math.floor(length * 0.5);
|
|
21
|
+
const d = Math.floor(length * 0.5);
|
|
22
|
+
// Using a lighter color for the reference box
|
|
23
|
+
commands.push(...shapes3d.drawWireframeBox(cx + Math.floor(w/2), cy - Math.floor(h/2), w, h, d, '#e0e0e0'));
|
|
24
|
+
|
|
25
|
+
return commands;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Lesson: Complex Structure Analysis (e.g. A Bottle)
|
|
30
|
+
*/
|
|
31
|
+
function lessonComplexStructure(canvasSize = 32, color = '#9c27b0') {
|
|
32
|
+
const commands = [];
|
|
33
|
+
const cx = Math.floor(canvasSize / 2);
|
|
34
|
+
const cy = Math.floor(canvasSize / 2);
|
|
35
|
+
const totalHeight = Math.floor(canvasSize * 0.8);
|
|
36
|
+
|
|
37
|
+
// Draw bottle structure showing cross-sections and central axis
|
|
38
|
+
commands.push(...structureShapes.drawBottleAnatomy(cx, cy, totalHeight, color));
|
|
39
|
+
|
|
40
|
+
return commands;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Lesson: Axis Orientation (Object pointing along different axes)
|
|
45
|
+
*/
|
|
46
|
+
function lessonAxisOrientation(canvasSize = 32, color = '#00bcd4') {
|
|
47
|
+
const commands = [];
|
|
48
|
+
const r = Math.floor(canvasSize * 0.1);
|
|
49
|
+
const h = Math.floor(canvasSize * 0.25);
|
|
50
|
+
const midY = Math.floor(canvasSize / 2);
|
|
51
|
+
|
|
52
|
+
// 1. Standing Cylinder (Oriented along Y axis)
|
|
53
|
+
const q1 = Math.floor(canvasSize * 0.2);
|
|
54
|
+
// Draw vertical axis
|
|
55
|
+
commands.push(shapes.drawLine(q1, midY - h + 5, q1, midY + 5, '#4caf50'));
|
|
56
|
+
commands.push(shapes.drawEllipse(q1, midY, r, Math.floor(r * 0.3), color, false)); // Base
|
|
57
|
+
commands.push(shapes.drawEllipse(q1, midY - h, r, Math.floor(r * 0.3), color, false)); // Top
|
|
58
|
+
commands.push(shapes.drawLine(q1 - r, midY, q1 - r, midY - h, color));
|
|
59
|
+
commands.push(shapes.drawLine(q1 + r, midY, q1 + r, midY - h, color));
|
|
60
|
+
|
|
61
|
+
// 2. Lying Cylinder (Oriented along X axis)
|
|
62
|
+
const q2 = Math.floor(canvasSize * 0.5);
|
|
63
|
+
// Draw horizontal axis
|
|
64
|
+
commands.push(shapes.drawLine(q2 - 5, midY + h - r, q2 + h + 5, midY + h - r, '#f44336'));
|
|
65
|
+
commands.push(shapes.drawEllipse(q2, midY + h - r, Math.floor(r * 0.3), r, color, false)); // Left base
|
|
66
|
+
commands.push(shapes.drawEllipse(q2 + h, midY + h - r, Math.floor(r * 0.3), r, color, false)); // Right base
|
|
67
|
+
commands.push(shapes.drawLine(q2, midY + h - r - r, q2 + h, midY + h - r - r, color));
|
|
68
|
+
commands.push(shapes.drawLine(q2, midY + h - r + r, q2 + h, midY + h - r + r, color));
|
|
69
|
+
|
|
70
|
+
// 3. Tilted/Depth Cylinder (Oriented along an arbitrary angle / Z axis)
|
|
71
|
+
const q3 = Math.floor(canvasSize * 0.75);
|
|
72
|
+
const angle = Math.PI / 6; // 30 degrees tilt
|
|
73
|
+
// Draw tilted axis
|
|
74
|
+
const dx = Math.floor(h * Math.cos(angle));
|
|
75
|
+
const dy = Math.floor(h * Math.sin(angle));
|
|
76
|
+
commands.push(shapes.drawLine(q3 - dx/2 - 5, midY - dy/2 - 5, q3 + dx/2 + 5, midY + dy/2 + 5, '#2196f3'));
|
|
77
|
+
// Draw rotated ellipses for bases
|
|
78
|
+
commands.push(...ellipseShapes.drawRotatedEllipse(q3 - dx/2, midY - dy/2, Math.floor(r * 0.3), r, angle, color));
|
|
79
|
+
commands.push(...ellipseShapes.drawRotatedEllipse(q3 + dx/2, midY + dy/2, Math.floor(r * 0.3), r, angle, color));
|
|
80
|
+
// Tangent lines for tilted cylinder (approximate)
|
|
81
|
+
const tx = Math.floor(r * Math.sin(angle));
|
|
82
|
+
const ty = Math.floor(r * Math.cos(angle));
|
|
83
|
+
commands.push(shapes.drawLine(q3 - dx/2 - tx, midY - dy/2 + ty, q3 + dx/2 - tx, midY + dy/2 + ty, color));
|
|
84
|
+
commands.push(shapes.drawLine(q3 - dx/2 + tx, midY - dy/2 - ty, q3 + dx/2 + tx, midY + dy/2 - ty, color));
|
|
85
|
+
|
|
86
|
+
return commands;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getStructureLessonCatalog() {
|
|
90
|
+
return [
|
|
91
|
+
{ id: 'structure_xyz', name: 'XYZ Axes', description: 'Understanding 3D space directions (Vertical, Horizontal, Depth)', fn: lessonXYZAxes },
|
|
92
|
+
{ id: 'structure_bottle', name: 'Complex Structure (Bottle)', description: 'Analyzing an object using a central axis and multiple cross-sections', fn: lessonComplexStructure },
|
|
93
|
+
{ id: 'structure_orientation', name: 'Axis Orientation', description: 'Identifying which axis an object is oriented along', fn: lessonAxisOrientation },
|
|
94
|
+
];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = {
|
|
98
|
+
lessonXYZAxes,
|
|
99
|
+
lessonComplexStructure,
|
|
100
|
+
lessonAxisOrientation,
|
|
101
|
+
getStructureLessonCatalog
|
|
102
|
+
};
|