@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.
Files changed (40) 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/cross_section_lessons.js +65 -0
  8. package/domains/art-tree/cross_section_shapes.js +84 -0
  9. package/domains/art-tree/cross_section_tools.js +51 -0
  10. package/domains/art-tree/curve_lessons.js +138 -0
  11. package/domains/art-tree/curve_shapes.js +90 -0
  12. package/domains/art-tree/curve_tools.js +51 -0
  13. package/domains/art-tree/ellipse_lessons.js +105 -0
  14. package/domains/art-tree/ellipse_shapes.js +63 -0
  15. package/domains/art-tree/ellipse_tools.js +60 -0
  16. package/domains/art-tree/hidden_lessons.js +55 -0
  17. package/domains/art-tree/hidden_shapes.js +97 -0
  18. package/domains/art-tree/hidden_tools.js +51 -0
  19. package/domains/art-tree/index.js +22 -0
  20. package/domains/art-tree/lessons.js +112 -0
  21. package/domains/art-tree/light_lessons.js +92 -0
  22. package/domains/art-tree/light_shapes.js +136 -0
  23. package/domains/art-tree/light_tools.js +49 -0
  24. package/domains/art-tree/perspective_lessons.js +138 -0
  25. package/domains/art-tree/perspective_shapes.js +96 -0
  26. package/domains/art-tree/perspective_tools.js +60 -0
  27. package/domains/art-tree/shapes.js +15 -0
  28. package/domains/art-tree/structure_lessons.js +102 -0
  29. package/domains/art-tree/structure_shapes.js +74 -0
  30. package/domains/art-tree/structure_tools.js +51 -0
  31. package/domains/art-tree/surface_lessons.js +112 -0
  32. package/domains/art-tree/surface_shapes.js +87 -0
  33. package/domains/art-tree/surface_tools.js +60 -0
  34. package/domains/art-tree/tools.js +54 -0
  35. package/domains/art-tree/transform_lessons_1.js +108 -0
  36. package/domains/art-tree/transform_lessons_2.js +102 -0
  37. package/domains/art-tree/transform_shapes.js +109 -0
  38. package/domains/art-tree/transform_tools.js +104 -0
  39. package/package.json +1 -1
  40. package/tools/workspace.js +14 -4
@@ -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 (Sự rút ngắn theo chiều sâu)
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 (Rút ngắn theo chiều sâu)',
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,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
+ };
@@ -0,0 +1,74 @@
1
+ const shapes = require('./shapes');
2
+ const ellipseShapes = require('./ellipse_shapes');
3
+
4
+ /**
5
+ * Draw a 3D coordinate system (X, Y, Z axes) originating from a center point
6
+ */
7
+ function drawXYZAxes(cx, cy, length) {
8
+ const commands = [];
9
+
10
+ // Y-axis (Vertical) - Green
11
+ commands.push(shapes.drawLine(cx, cy, cx, cy - length, '#4caf50'));
12
+
13
+ // X-axis (Horizontal) - Red
14
+ commands.push(shapes.drawLine(cx, cy, cx + length, cy, '#f44336'));
15
+
16
+ // Z-axis (Depth - Oblique projection) - Blue
17
+ const dx = Math.floor(length * 0.7 * Math.cos(Math.PI / 6));
18
+ const dy = Math.floor(length * 0.7 * Math.sin(Math.PI / 6));
19
+ commands.push(shapes.drawLine(cx, cy, cx - dx, cy + dy, '#2196f3'));
20
+
21
+ // Center point
22
+ commands.push(shapes.drawCircle(cx, cy, 2, '#000000', true));
23
+
24
+ return commands;
25
+ }
26
+
27
+ /**
28
+ * Draw a complex bottle structure using cross-sections and central axis
29
+ */
30
+ function drawBottleAnatomy(cx, cy, totalHeight, color) {
31
+ const commands = [];
32
+
33
+ const bottomY = cy + Math.floor(totalHeight / 2);
34
+ const topY = cy - Math.floor(totalHeight / 2);
35
+
36
+ // Central vertical axis
37
+ commands.push(shapes.drawLine(cx, topY - 10, cx, bottomY + 10, '#bdbdbd'));
38
+
39
+ // Define cross sections (y position from bottom, radius)
40
+ const sections = [
41
+ { y: bottomY, r: Math.floor(totalHeight * 0.2) }, // Base
42
+ { y: bottomY - Math.floor(totalHeight * 0.4), r: Math.floor(totalHeight * 0.2) }, // Body
43
+ { y: bottomY - Math.floor(totalHeight * 0.7), r: Math.floor(totalHeight * 0.08) }, // Neck start
44
+ { y: topY, r: Math.floor(totalHeight * 0.08) } // Mouth
45
+ ];
46
+
47
+ // Draw cross section ellipses
48
+ for (const sec of sections) {
49
+ // ry is based on perspective (lower = wider perspective, but keep it simple here)
50
+ const ry = Math.floor(sec.r * 0.3);
51
+ commands.push(shapes.drawEllipse(cx, sec.y, sec.r, ry, color, false));
52
+
53
+ // Cross point on the axis
54
+ commands.push(shapes.drawCircle(cx, sec.y, 1, '#ff0000', true));
55
+ }
56
+
57
+ // Draw outer contour lines
58
+ for (let i = 0; i < sections.length - 1; i++) {
59
+ const s1 = sections[i];
60
+ const s2 = sections[i+1];
61
+
62
+ // Left contour
63
+ commands.push(shapes.drawLine(cx - s1.r, s1.y, cx - s2.r, s2.y, color));
64
+ // Right contour
65
+ commands.push(shapes.drawLine(cx + s1.r, s1.y, cx + s2.r, s2.y, color));
66
+ }
67
+
68
+ return commands;
69
+ }
70
+
71
+ module.exports = {
72
+ drawXYZAxes,
73
+ drawBottleAnatomy
74
+ };
@@ -0,0 +1,51 @@
1
+ const { z } = require('zod');
2
+ const { registerTool, sendCommand } = require('../../core/server');
3
+ const structureLessons = require('./structure_lessons');
4
+
5
+ const hexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/).describe('Hex color e.g. #ff0000');
6
+
7
+ /**
8
+ * Register all structure and axis tools on the MCP server.
9
+ * @param {McpServer} server
10
+ */
11
+ function register(server) {
12
+ registerTool(server, 'art_tree_lesson_structure_xyz',
13
+ 'Lesson: Structure - XYZ Axes (Vertical, Horizontal, Depth directions)',
14
+ { canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#2196f3') },
15
+ async (p) => {
16
+ const commands = structureLessons.lessonXYZAxes(p.canvasSize, p.color);
17
+ await Promise.all(commands.map(cmd => sendCommand(cmd)));
18
+ return { content: [{ type: 'text', text: `✓ XYZ Axes lesson completed` }] };
19
+ });
20
+
21
+ registerTool(server, 'art_tree_lesson_structure_bottle',
22
+ 'Lesson: Structure - Complex Analysis (Bottle with cross-sections)',
23
+ { canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#9c27b0') },
24
+ async (p) => {
25
+ const commands = structureLessons.lessonComplexStructure(p.canvasSize, p.color);
26
+ await Promise.all(commands.map(cmd => sendCommand(cmd)));
27
+ return { content: [{ type: 'text', text: `✓ Complex Structure (Bottle) lesson completed` }] };
28
+ });
29
+
30
+ registerTool(server, 'art_tree_lesson_structure_orientation',
31
+ 'Lesson: Structure - Axis Orientation (Which axis is it oriented along?)',
32
+ { canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#00bcd4') },
33
+ async (p) => {
34
+ const commands = structureLessons.lessonAxisOrientation(p.canvasSize, p.color);
35
+ await Promise.all(commands.map(cmd => sendCommand(cmd)));
36
+ return { content: [{ type: 'text', text: `✓ Axis Orientation lesson completed` }] };
37
+ });
38
+
39
+ registerTool(server, 'art_tree_lesson_structure_catalog',
40
+ 'Get the catalog of all available Structure lessons',
41
+ {},
42
+ () => {
43
+ const catalog = structureLessons.getStructureLessonCatalog();
44
+ const text = catalog.map(l =>
45
+ ` • ${l.id}: ${l.name} — ${l.description}`
46
+ ).join('\n');
47
+ return { content: [{ type: 'text', text: `📚 Structure Lessons:\n\n${text}` }] };
48
+ });
49
+ }
50
+
51
+ module.exports = { register };
@@ -0,0 +1,112 @@
1
+ const shapes = require('./shapes');
2
+ const surfaceShapes = require('./surface_shapes');
3
+
4
+ /**
5
+ * Lesson: Surface Types (Mặt phẳng, lồi, lõm)
6
+ */
7
+ function lessonSurfaceTypes(canvasSize = 32, color = '#2196f3') {
8
+ const commands = [];
9
+ const midY = Math.floor(canvasSize / 2);
10
+ const qX = Math.floor(canvasSize / 4);
11
+ const r = Math.floor(canvasSize * 0.15);
12
+
13
+ // 1. Flat Plane (Mặt phẳng)
14
+ const w = Math.floor(canvasSize * 0.2);
15
+ commands.push(shapes.drawRectangle(qX - w/2, midY - w/2, w, w, '#4caf50', false));
16
+ // Add some grid lines to show it's flat
17
+ commands.push(shapes.drawLine(qX, midY - w/2, qX, midY + w/2, '#4caf50'));
18
+ commands.push(shapes.drawLine(qX - w/2, midY, qX + w/2, midY, '#4caf50'));
19
+
20
+ // 2. Convex Surface (Mặt lồi - e.g. Dome/Sphere)
21
+ // We use an ellipse that curves outwards (downwards)
22
+ commands.push(shapes.drawEllipse(qX * 2, midY, r, r, '#ff9800', false));
23
+ // Cross contour curving DOWN (convex towards viewer)
24
+ commands.push(shapes.drawEllipse(qX * 2, midY + Math.floor(r*0.2), r, Math.floor(r*0.3), '#ff9800', false));
25
+
26
+ // 3. Concave Surface (Mặt lõm - e.g. Bowl)
27
+ // Draw the top rim of a bowl
28
+ commands.push(shapes.drawEllipse(qX * 3, midY, r, Math.floor(r*0.3), '#9c27b0', false));
29
+ // Draw the bottom curve (the inside of the bowl)
30
+ const bowlPoints = [];
31
+ for(let i=0; i<=18; i++) {
32
+ const a = (i/18) * Math.PI; // 0 to 180 degrees (bottom half)
33
+ bowlPoints.push({ x: Math.round(qX * 3 + r * Math.cos(a)), y: Math.round(midY + r * Math.sin(a)) });
34
+ }
35
+ commands.push(shapes.drawPolyline(bowlPoints, '#9c27b0'));
36
+
37
+ return commands;
38
+ }
39
+
40
+ /**
41
+ * Lesson: Edge Types (Cạnh cứng vuông vức vs Cạnh mềm bo góc)
42
+ */
43
+ function lessonEdgeTypes(canvasSize = 32, color = '#ff5722') {
44
+ const commands = [];
45
+ const qX1 = Math.floor(canvasSize * 0.3);
46
+ const qX2 = Math.floor(canvasSize * 0.7);
47
+ const midY = Math.floor(canvasSize / 2);
48
+ const w = Math.floor(canvasSize * 0.25);
49
+
50
+ // 1. Hard Edge (Cạnh cứng - Sharp corners)
51
+ commands.push(shapes.drawRectangle(qX1 - w/2, midY - w/2, w, w, '#e91e63', false));
52
+
53
+ // 2. Soft Edge / Transitional (Cạnh mềm / Mặt chuyển tiếp)
54
+ // Draw a box with rounded corners (fillet)
55
+ const r = Math.floor(w * 0.2); // Corner radius
56
+ const rx = qX2 - w/2;
57
+ const ry = midY - w/2;
58
+
59
+ // Straight segments
60
+ commands.push(shapes.drawLine(rx + r, ry, rx + w - r, ry, '#00bcd4')); // Top
61
+ commands.push(shapes.drawLine(rx + w, ry + r, rx + w, ry + w - r, '#00bcd4')); // Right
62
+ commands.push(shapes.drawLine(rx + r, ry + w, rx + w - r, ry + w, '#00bcd4')); // Bottom
63
+ commands.push(shapes.drawLine(rx, ry + r, rx, ry + w - r, '#00bcd4')); // Left
64
+
65
+ // Rounded corners (approximated with small circles for visual simplicity)
66
+ commands.push(shapes.drawCircle(rx + r, ry + r, r, '#00bcd4', false)); // TL
67
+ commands.push(shapes.drawCircle(rx + w - r, ry + r, r, '#00bcd4', false)); // TR
68
+ commands.push(shapes.drawCircle(rx + w - r, ry + w - r, r, '#00bcd4', false)); // BR
69
+ commands.push(shapes.drawCircle(rx + r, ry + w - r, r, '#00bcd4', false)); // BL
70
+
71
+ return commands;
72
+ }
73
+
74
+ /**
75
+ * Lesson: Cup Analysis (Analyzing surfaces of a cup)
76
+ */
77
+ function lessonCupAnalysis(canvasSize = 32, color = '#607d8b') {
78
+ const cx = Math.floor(canvasSize / 2);
79
+ const cy = Math.floor(canvasSize / 2);
80
+ const r = Math.floor(canvasSize * 0.2);
81
+ const h = Math.floor(canvasSize * 0.4);
82
+
83
+ return surfaceShapes.drawCupAnalysis(cx, cy, r, h);
84
+ }
85
+
86
+ /**
87
+ * Lesson: Chair Analysis (Analyzing surfaces and edges of a chair)
88
+ */
89
+ function lessonChairAnalysis(canvasSize = 32, color = '#795548') {
90
+ const cx = Math.floor(canvasSize / 2);
91
+ const cy = Math.floor(canvasSize / 2);
92
+ const w = Math.floor(canvasSize * 0.3);
93
+
94
+ return surfaceShapes.drawChairAnalysis(cx, cy, w);
95
+ }
96
+
97
+ function getSurfaceLessonCatalog() {
98
+ return [
99
+ { id: 'surface_types', name: 'Surface Types', description: 'Flat, Convex, Concave planes', fn: lessonSurfaceTypes },
100
+ { id: 'edge_types', name: 'Edge Types', description: 'Hard vs Soft (Transitional) edges', fn: lessonEdgeTypes },
101
+ { id: 'surface_cup', name: 'Cup Analysis', description: 'Real object: Convex/Concave walls, Rim, Tube handle', fn: lessonCupAnalysis },
102
+ { id: 'surface_chair', name: 'Chair Analysis', description: 'Real object: Flat planes, Hard edges, Cylindrical legs', fn: lessonChairAnalysis },
103
+ ];
104
+ }
105
+
106
+ module.exports = {
107
+ lessonSurfaceTypes,
108
+ lessonEdgeTypes,
109
+ lessonCupAnalysis,
110
+ lessonChairAnalysis,
111
+ getSurfaceLessonCatalog
112
+ };