@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.
Files changed (56) hide show
  1. package/domains/art-tree/3d_lessons.js +114 -0
  2. package/domains/art-tree/3d_shapes.js +116 -0
  3. package/domains/art-tree/3d_tools.js +60 -0
  4. package/domains/art-tree/advanced_lessons.js +124 -0
  5. package/domains/art-tree/advanced_shapes.js +73 -0
  6. package/domains/art-tree/advanced_tools.js +69 -0
  7. package/domains/art-tree/analysis_lessons.js +32 -0
  8. package/domains/art-tree/analysis_shapes.js +146 -0
  9. package/domains/art-tree/analysis_tools.js +49 -0
  10. package/domains/art-tree/cross_section_lessons.js +65 -0
  11. package/domains/art-tree/cross_section_shapes.js +84 -0
  12. package/domains/art-tree/cross_section_tools.js +51 -0
  13. package/domains/art-tree/curve_lessons.js +138 -0
  14. package/domains/art-tree/curve_shapes.js +90 -0
  15. package/domains/art-tree/curve_tools.js +51 -0
  16. package/domains/art-tree/ellipse_lessons.js +105 -0
  17. package/domains/art-tree/ellipse_shapes.js +63 -0
  18. package/domains/art-tree/ellipse_tools.js +60 -0
  19. package/domains/art-tree/hidden_lessons.js +55 -0
  20. package/domains/art-tree/hidden_shapes.js +97 -0
  21. package/domains/art-tree/hidden_tools.js +51 -0
  22. package/domains/art-tree/index.js +32 -0
  23. package/domains/art-tree/layer_lessons.js +32 -0
  24. package/domains/art-tree/layer_shapes.js +117 -0
  25. package/domains/art-tree/layer_tools.js +43 -0
  26. package/domains/art-tree/lessons.js +119 -7
  27. package/domains/art-tree/light_lessons.js +92 -0
  28. package/domains/art-tree/light_shapes.js +136 -0
  29. package/domains/art-tree/light_tools.js +49 -0
  30. package/domains/art-tree/material_lessons.js +71 -0
  31. package/domains/art-tree/material_shapes.js +130 -0
  32. package/domains/art-tree/material_tools.js +49 -0
  33. package/domains/art-tree/perspective_lessons.js +138 -0
  34. package/domains/art-tree/perspective_shapes.js +96 -0
  35. package/domains/art-tree/perspective_tools.js +60 -0
  36. package/domains/art-tree/shapes.js +15 -0
  37. package/domains/art-tree/sky_lessons_1.js +82 -0
  38. package/domains/art-tree/sky_shapes_1.js +68 -0
  39. package/domains/art-tree/sky_tools.js +51 -0
  40. package/domains/art-tree/structure_lessons.js +102 -0
  41. package/domains/art-tree/structure_shapes.js +74 -0
  42. package/domains/art-tree/structure_tools.js +51 -0
  43. package/domains/art-tree/surface_lessons.js +112 -0
  44. package/domains/art-tree/surface_shapes.js +87 -0
  45. package/domains/art-tree/surface_tools.js +60 -0
  46. package/domains/art-tree/tools.js +69 -15
  47. package/domains/art-tree/transform_lessons_1.js +108 -0
  48. package/domains/art-tree/transform_lessons_2.js +102 -0
  49. package/domains/art-tree/transform_shapes.js +109 -0
  50. package/domains/art-tree/transform_tools.js +104 -0
  51. package/domains/art-tree/vocab_lessons.js +32 -0
  52. package/domains/art-tree/vocab_shapes.js +116 -0
  53. package/domains/art-tree/vocab_tools.js +41 -0
  54. package/package.json +1 -1
  55. package/tools/index.js +2 -0
  56. 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
+ };