@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,146 @@
1
+ const shapes = require('./shapes');
2
+ const lightShapes = require('./light_shapes');
3
+
4
+ // Master function to draw the Teapot at a specific analytical step
5
+ function drawTeapotAnalysis(cx, cy, scale, step, color = '#2196f3') {
6
+ const commands = [];
7
+ const r = Math.floor(scale * 0.3); // Main body radius
8
+
9
+ // Base outlines for the Teapot used across multiple steps
10
+ const drawBaseOutline = (c) => {
11
+ // Body (Sphere)
12
+ commands.push(shapes.drawCircle(cx, cy, r, c, false));
13
+ // Base/Foot
14
+ commands.push(shapes.drawLine(cx - Math.floor(r*0.6), cy + r, cx + Math.floor(r*0.6), cy + r, c));
15
+ commands.push(shapes.drawLine(cx - Math.floor(r*0.6), cy + r, cx - Math.floor(r*0.7), cy + r + 5, c));
16
+ commands.push(shapes.drawLine(cx + Math.floor(r*0.6), cy + r, cx + Math.floor(r*0.7), cy + r + 5, c));
17
+ commands.push(shapes.drawLine(cx - Math.floor(r*0.7), cy + r + 5, cx + Math.floor(r*0.7), cy + r + 5, c));
18
+ // Lid
19
+ commands.push(shapes.drawLine(cx - Math.floor(r*0.6), cy - r, cx + Math.floor(r*0.6), cy - r, c));
20
+ commands.push(shapes.drawEllipse(cx, cy - r - 5, Math.floor(r*0.6), Math.floor(r*0.2), c, false));
21
+ commands.push(shapes.drawCircle(cx, cy - r - 15, Math.floor(r*0.15), c, false)); // Knob
22
+ // Spout (Left)
23
+ commands.push(shapes.drawLine(cx - r, cy, cx - Math.floor(r*1.8), cy - Math.floor(r*0.8), c));
24
+ commands.push(shapes.drawLine(cx - Math.floor(r*0.8), cy + Math.floor(r*0.4), cx - Math.floor(r*1.5), cy - Math.floor(r*0.6), c));
25
+ commands.push(shapes.drawLine(cx - Math.floor(r*1.8), cy - Math.floor(r*0.8), cx - Math.floor(r*1.5), cy - Math.floor(r*0.6), c));
26
+ // Handle (Right)
27
+ const handlePts1 = [], handlePts2 = [];
28
+ for(let i=0; i<=10; i++) {
29
+ const a = -Math.PI/2 + (i/10)*Math.PI; // -90 to 90
30
+ handlePts1.push({x: Math.round(cx + r + Math.floor(r*0.8)*Math.cos(a)), y: Math.round(cy - Math.floor(r*0.2) + Math.floor(r*0.6)*Math.sin(a))});
31
+ handlePts2.push({x: Math.round(cx + r + Math.floor(r*0.5)*Math.cos(a)), y: Math.round(cy - Math.floor(r*0.2) + Math.floor(r*0.6)*Math.sin(a))});
32
+ }
33
+ commands.push(shapes.drawPolyline(handlePts1, c));
34
+ commands.push(shapes.drawPolyline(handlePts2, c));
35
+ };
36
+
37
+ switch (step) {
38
+ case 1: // Silhouette (Flat black)
39
+ // Since we can't easily union-fill all paths, we simulate it by drawing the base thick
40
+ // For this, we just draw the outline in solid black with no inner details
41
+ drawBaseOutline('#000000');
42
+ break;
43
+
44
+ case 2: // Proportions (Bounding box)
45
+ drawBaseOutline('#e0e0e0');
46
+ // Bounding box
47
+ const minX = cx - Math.floor(r*1.8) - 10;
48
+ const maxX = cx + Math.floor(r*1.8) + 10;
49
+ const minY = cy - r - 30;
50
+ const maxY = cy + r + 15;
51
+ commands.push(shapes.drawRectangle(minX, minY, maxX - minX, maxY - minY, color, false));
52
+ // Center lines of bounding box
53
+ commands.push(shapes.drawLine(cx, minY, cx, maxY, '#ff9800'));
54
+ commands.push(shapes.drawLine(minX, cy, maxX, cy, '#ff9800'));
55
+ break;
56
+
57
+ case 3: // Axis (X, Y, Z structural lines)
58
+ drawBaseOutline('#e0e0e0');
59
+ // Y axis
60
+ commands.push(shapes.drawLine(cx, cy - r - 40, cx, cy + r + 40, '#f44336'));
61
+ // X axis
62
+ commands.push(shapes.drawLine(cx - r - 40, cy, cx + r + 40, cy, '#4caf50'));
63
+ // Z axis (depth, diagonal)
64
+ commands.push(shapes.drawLine(cx - Math.floor(r*0.5), cy + Math.floor(r*0.5), cx + Math.floor(r*0.5), cy - Math.floor(r*0.5), '#2196f3'));
65
+ break;
66
+
67
+ case 4: // Main Masses (Primitives)
68
+ drawBaseOutline('#e0e0e0');
69
+ // Highlight the sphere
70
+ commands.push(shapes.drawCircle(cx, cy, r, '#e91e63', false));
71
+ // Highlight the spout cylinder/cone
72
+ commands.push(shapes.drawLine(cx - r, cy, cx - Math.floor(r*1.8), cy - Math.floor(r*0.8), '#00bcd4'));
73
+ commands.push(shapes.drawLine(cx - Math.floor(r*0.8), cy + Math.floor(r*0.4), cx - Math.floor(r*1.5), cy - Math.floor(r*0.6), '#00bcd4'));
74
+ break;
75
+
76
+ case 5: // Cross-sections
77
+ drawBaseOutline('#e0e0e0');
78
+ // Slicing ellipses on the body
79
+ commands.push(shapes.drawEllipse(cx, cy, r, Math.floor(r*0.3), '#9c27b0', false));
80
+ commands.push(shapes.drawEllipse(cx, cy - Math.floor(r*0.5), Math.floor(r*0.86), Math.floor(r*0.25), '#9c27b0', false));
81
+ commands.push(shapes.drawEllipse(cx, cy + Math.floor(r*0.5), Math.floor(r*0.86), Math.floor(r*0.25), '#9c27b0', false));
82
+ break;
83
+
84
+ case 6: // Transformations (Bend, Taper)
85
+ drawBaseOutline('#e0e0e0');
86
+ // Highlight bending spout
87
+ commands.push(shapes.drawEllipse(cx - Math.floor(r*1.4), cy - Math.floor(r*0.4), Math.floor(r*0.4), Math.floor(r*0.2), '#ff9800', false));
88
+ // Tapering lid
89
+ commands.push(shapes.drawLine(cx - Math.floor(r*0.6), cy - r, cx, cy - r - 15, '#ff9800'));
90
+ commands.push(shapes.drawLine(cx + Math.floor(r*0.6), cy - r, cx, cy - r - 15, '#ff9800'));
91
+ break;
92
+
93
+ case 7: // Secondary parts
94
+ drawBaseOutline('#e0e0e0');
95
+ // Boldly highlight spout and handle
96
+ commands.push(shapes.drawLine(cx - r, cy, cx - Math.floor(r*1.8), cy - Math.floor(r*0.8), '#ff5722'));
97
+ commands.push(shapes.drawLine(cx - Math.floor(r*0.8), cy + Math.floor(r*0.4), cx - Math.floor(r*1.5), cy - Math.floor(r*0.6), '#ff5722'));
98
+ const hPts = [];
99
+ for(let i=0; i<=10; i++) {
100
+ const a = -Math.PI/2 + (i/10)*Math.PI;
101
+ hPts.push({x: Math.round(cx + r + Math.floor(r*0.8)*Math.cos(a)), y: Math.round(cy - Math.floor(r*0.2) + Math.floor(r*0.6)*Math.sin(a))});
102
+ }
103
+ commands.push(shapes.drawPolyline(hPts, '#3f51b5'));
104
+ break;
105
+
106
+ case 8: // Surfaces (Convex, Concave)
107
+ drawBaseOutline('#e0e0e0');
108
+ // Convex body
109
+ commands.push(shapes.drawCircle(cx, cy, r, '#8bc34a', false));
110
+ // Concave inner handle space
111
+ commands.push(shapes.drawEllipse(cx + r + Math.floor(r*0.2), cy - Math.floor(r*0.2), Math.floor(r*0.3), Math.floor(r*0.5), '#00bcd4', false));
112
+ break;
113
+
114
+ case 9: // Perspective
115
+ drawBaseOutline(color);
116
+ // Horizon line above
117
+ const horizonY = cy - r - 50;
118
+ const vpX = cx + Math.floor(r*2);
119
+ commands.push(shapes.drawLine(0, horizonY, scale*2, horizonY, '#9e9e9e'));
120
+ commands.push(shapes.drawCircle(vpX, horizonY, 3, '#ff0000', true));
121
+ // Orthogonals from top and bottom to VP
122
+ commands.push(shapes.drawLine(cx, cy - r, vpX, horizonY, '#bdbdbd'));
123
+ commands.push(shapes.drawLine(cx, cy + r, vpX, horizonY, '#bdbdbd'));
124
+ break;
125
+
126
+ case 10: // Lighting
127
+ // Re-use shaded sphere for the main body
128
+ commands.push(...lightShapes.drawShadedSphere(cx, cy, r, -Math.PI*0.75));
129
+ // We skip the detailed spout/handle shading to stay within line limits,
130
+ // but draw them solidly over the sphere to complete the form
131
+ const c = '#546e7a';
132
+ commands.push(shapes.drawLine(cx - r, cy, cx - Math.floor(r*1.8), cy - Math.floor(r*0.8), c));
133
+ commands.push(shapes.drawLine(cx - Math.floor(r*0.8), cy + Math.floor(r*0.4), cx - Math.floor(r*1.5), cy - Math.floor(r*0.6), c));
134
+ break;
135
+
136
+ default:
137
+ drawBaseOutline(color);
138
+ break;
139
+ }
140
+
141
+ return commands;
142
+ }
143
+
144
+ module.exports = {
145
+ drawTeapotAnalysis
146
+ };
@@ -0,0 +1,49 @@
1
+ const { z } = require('zod');
2
+ const { registerTool, sendCommand } = require('../../core/server');
3
+ const analysisLessons = require('./analysis_lessons');
4
+
5
+ const hexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/).describe('Hex color e.g. #ff0000');
6
+
7
+ /**
8
+ * Register all analysis tools on the MCP server.
9
+ * @param {McpServer} server
10
+ */
11
+ function register(server) {
12
+ registerTool(server, 'art_tree_lesson_analysis_step',
13
+ 'Lesson: Capstone - 10-Step Object Analysis of a Teapot. Pass step (1 to 10).',
14
+ {
15
+ canvasSize: z.number().int().min(16).max(64).default(32),
16
+ step: z.number().int().min(1).max(10).describe('The analysis step to view (1=Silhouette, 2=Proportions, ..., 10=Lighting)'),
17
+ color: hexColor.default('#2196f3')
18
+ },
19
+ async (p) => {
20
+ const commands = analysisLessons.lessonObjectAnalysis(p.canvasSize, p.step, p.color);
21
+ await Promise.all(commands.map(cmd => sendCommand(cmd)));
22
+ const steps = [
23
+ "1. Silhouette (Hình bao ngoài)",
24
+ "2. Proportions (Tỉ lệ)",
25
+ "3. Axis (Trục)",
26
+ "4. Main masses (Khối lớn)",
27
+ "5. Cross-sections (Mặt cắt)",
28
+ "6. Transformations (Biến dạng)",
29
+ "7. Secondary parts (Các bộ phận phụ)",
30
+ "8. Surfaces (Bề mặt)",
31
+ "9. Perspective (Phối cảnh)",
32
+ "10. Lighting (Ánh sáng)"
33
+ ];
34
+ return { content: [{ type: 'text', text: `✓ Step ${p.step} Analysis completed: ${steps[p.step - 1]}` }] };
35
+ });
36
+
37
+ registerTool(server, 'art_tree_lesson_analysis_catalog',
38
+ 'Get the catalog of all available Analysis lessons',
39
+ {},
40
+ () => {
41
+ const catalog = analysisLessons.getAnalysisLessonCatalog();
42
+ const text = catalog.map(l =>
43
+ ` • ${l.id}: ${l.name} — ${l.description}`
44
+ ).join('\n');
45
+ return { content: [{ type: 'text', text: `📚 Analysis Lessons:\n\n${text}` }] };
46
+ });
47
+ }
48
+
49
+ module.exports = { register };
@@ -0,0 +1,65 @@
1
+ const csShapes = require('./cross_section_shapes');
2
+
3
+ /**
4
+ * Lesson: Bottle Sections (Base, Body, Shoulder, Neck, Mouth)
5
+ */
6
+ function lessonBottleSections(canvasSize = 32, color = '#2196f3') {
7
+ const cx = Math.floor(canvasSize / 2);
8
+ const totalH = Math.floor(canvasSize * 0.8);
9
+ const topY = Math.floor(canvasSize * 0.1);
10
+ const bottomY = topY + totalH;
11
+
12
+ const sections = [
13
+ { y: topY, r: Math.floor(canvasSize * 0.08) }, // Mouth (slightly open)
14
+ { y: topY + Math.floor(totalH * 0.1), r: Math.floor(canvasSize * 0.06) }, // Neck (narrow)
15
+ { y: topY + Math.floor(totalH * 0.3), r: Math.floor(canvasSize * 0.2) }, // Shoulder (shrinking to body)
16
+ { y: topY + Math.floor(totalH * 0.6), r: Math.floor(canvasSize * 0.25) }, // Body (wide)
17
+ { y: bottomY, r: Math.floor(canvasSize * 0.25) } // Base (wide)
18
+ ];
19
+
20
+ return csShapes.drawLoftedForm(cx, topY, bottomY, sections, color, true);
21
+ }
22
+
23
+ /**
24
+ * Lesson: Glass Sections (Base, Mid, Mouth)
25
+ */
26
+ function lessonGlassSections(canvasSize = 32, color = '#9c27b0') {
27
+ const cx = Math.floor(canvasSize / 2);
28
+ const totalH = Math.floor(canvasSize * 0.6);
29
+ const topY = Math.floor(canvasSize * 0.2);
30
+ const bottomY = topY + totalH;
31
+
32
+ const sections = [
33
+ { y: topY, r: Math.floor(canvasSize * 0.3) }, // Mouth (wide)
34
+ { y: topY + Math.floor(totalH * 0.5), r: Math.floor(canvasSize * 0.2) }, // Mid body (widening)
35
+ { y: bottomY, r: Math.floor(canvasSize * 0.15) } // Base (small)
36
+ ];
37
+
38
+ return csShapes.drawLoftedForm(cx, topY, bottomY, sections, color, true);
39
+ }
40
+
41
+ /**
42
+ * Lesson: Head Sections (Cranium, Face, Jaw)
43
+ */
44
+ function lessonHeadSections(canvasSize = 32, color = '#ff9800') {
45
+ const cx = Math.floor(canvasSize / 2);
46
+ const cy = Math.floor(canvasSize / 2);
47
+ const height = Math.floor(canvasSize * 0.8);
48
+
49
+ return csShapes.drawHeadStructure(cx, cy, height, color);
50
+ }
51
+
52
+ function getCrossSectionLessonCatalog() {
53
+ return [
54
+ { id: 'cs_bottle', name: 'Bottle Cross-sections', description: 'Base, body, shoulder, neck, mouth', fn: lessonBottleSections },
55
+ { id: 'cs_glass', name: 'Glass Cross-sections', description: 'Base, body, mouth', fn: lessonGlassSections },
56
+ { id: 'cs_head', name: 'Head Cross-sections', description: 'Cranium, face, jaw', fn: lessonHeadSections },
57
+ ];
58
+ }
59
+
60
+ module.exports = {
61
+ lessonBottleSections,
62
+ lessonGlassSections,
63
+ lessonHeadSections,
64
+ getCrossSectionLessonCatalog
65
+ };
@@ -0,0 +1,84 @@
1
+ const shapes = require('./shapes');
2
+ const ellipseShapes = require('./ellipse_shapes');
3
+
4
+ /**
5
+ * Draw a lofted 3D form by providing a central axis and cross-sections.
6
+ * @param {number} cx Center X
7
+ * @param {number} topY Top Y of the axis
8
+ * @param {number} bottomY Bottom Y of the axis
9
+ * @param {Array<{y: number, r: number}>} sections Slices sorted from top to bottom (or bottom to top)
10
+ * @param {string} color
11
+ * @param {boolean} drawAxis Whether to draw the central vertical axis
12
+ */
13
+ function drawLoftedForm(cx, topY, bottomY, sections, color, drawAxis = true) {
14
+ const commands = [];
15
+
16
+ if (drawAxis) {
17
+ commands.push(shapes.drawLine(cx, topY - 5, cx, bottomY + 5, '#bdbdbd'));
18
+ }
19
+
20
+ // Sort sections by y to ensure we can connect them properly
21
+ const sorted = [...sections].sort((a, b) => a.y - b.y);
22
+
23
+ // Draw ellipses and connect edges
24
+ for (let i = 0; i < sorted.length; i++) {
25
+ const sec = sorted[i];
26
+ const ry = Math.max(2, Math.floor(sec.r * 0.3)); // perspective foreshortening
27
+
28
+ // Draw cross-section ellipse
29
+ commands.push(shapes.drawEllipse(cx, sec.y, sec.r, ry, color, false));
30
+
31
+ // Connect to next section's edges
32
+ if (i < sorted.length - 1) {
33
+ const nextSec = sorted[i + 1];
34
+ // Left contour
35
+ commands.push(shapes.drawLine(cx - sec.r, sec.y, cx - nextSec.r, nextSec.y, color));
36
+ // Right contour
37
+ commands.push(shapes.drawLine(cx + sec.r, sec.y, cx + nextSec.r, nextSec.y, color));
38
+ }
39
+ }
40
+
41
+ return commands;
42
+ }
43
+
44
+ /**
45
+ * Draw the structural volume of a head using cross sections
46
+ */
47
+ function drawHeadStructure(cx, cy, height, color) {
48
+ const commands = [];
49
+
50
+ const topY = cy - Math.floor(height / 2);
51
+ const bottomY = cy + Math.floor(height / 2);
52
+
53
+ // 1. Cranium (large sphere at the top)
54
+ const craniumR = Math.floor(height * 0.35);
55
+ const craniumY = topY + craniumR;
56
+ commands.push(shapes.drawCircle(cx, craniumY, craniumR, color, false));
57
+
58
+ // Cranium cross sections (horizontal and vertical)
59
+ commands.push(shapes.drawEllipse(cx, craniumY, craniumR, Math.floor(craniumR * 0.4), '#03a9f4', false));
60
+ commands.push(shapes.drawEllipse(cx, craniumY, Math.floor(craniumR * 0.3), craniumR, '#03a9f4', false));
61
+
62
+ // 2. Face & Jaw (narrowing down)
63
+ const jawR = Math.floor(craniumR * 0.6);
64
+ const jawY = bottomY - Math.floor(jawR * 0.5);
65
+
66
+ // Jaw cross section
67
+ commands.push(shapes.drawEllipse(cx, jawY, jawR, Math.floor(jawR * 0.3), '#e91e63', false));
68
+
69
+ // Connect Cranium to Jaw (Cheekbones/Jawline)
70
+ // Left side
71
+ commands.push(shapes.drawLine(cx - craniumR, craniumY, cx - jawR, jawY, color));
72
+ // Right side
73
+ commands.push(shapes.drawLine(cx + craniumR, craniumY, cx + jawR, jawY, color));
74
+
75
+ // Central facial axis (Center line of the face)
76
+ commands.push(shapes.drawLine(cx, topY, cx, bottomY, '#4caf50'));
77
+
78
+ return commands;
79
+ }
80
+
81
+ module.exports = {
82
+ drawLoftedForm,
83
+ drawHeadStructure
84
+ };
@@ -0,0 +1,51 @@
1
+ const { z } = require('zod');
2
+ const { registerTool, sendCommand } = require('../../core/server');
3
+ const csLessons = require('./cross_section_lessons');
4
+
5
+ const hexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/).describe('Hex color e.g. #ff0000');
6
+
7
+ /**
8
+ * Register all cross-section tools on the MCP server.
9
+ * @param {McpServer} server
10
+ */
11
+ function register(server) {
12
+ registerTool(server, 'art_tree_lesson_cs_bottle',
13
+ 'Lesson: Cross-sections - Bottle Analysis (Base, body, shoulder, neck, mouth)',
14
+ { canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#2196f3') },
15
+ async (p) => {
16
+ const commands = csLessons.lessonBottleSections(p.canvasSize, p.color);
17
+ await Promise.all(commands.map(cmd => sendCommand(cmd)));
18
+ return { content: [{ type: 'text', text: `✓ Bottle Cross-sections lesson completed` }] };
19
+ });
20
+
21
+ registerTool(server, 'art_tree_lesson_cs_glass',
22
+ 'Lesson: Cross-sections - Glass Analysis (Base, mid, mouth)',
23
+ { canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#9c27b0') },
24
+ async (p) => {
25
+ const commands = csLessons.lessonGlassSections(p.canvasSize, p.color);
26
+ await Promise.all(commands.map(cmd => sendCommand(cmd)));
27
+ return { content: [{ type: 'text', text: `✓ Glass Cross-sections lesson completed` }] };
28
+ });
29
+
30
+ registerTool(server, 'art_tree_lesson_cs_head',
31
+ 'Lesson: Cross-sections - Head Analysis (Cranium, face, jaw)',
32
+ { canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#ff9800') },
33
+ async (p) => {
34
+ const commands = csLessons.lessonHeadSections(p.canvasSize, p.color);
35
+ await Promise.all(commands.map(cmd => sendCommand(cmd)));
36
+ return { content: [{ type: 'text', text: `✓ Head Cross-sections lesson completed` }] };
37
+ });
38
+
39
+ registerTool(server, 'art_tree_lesson_cs_catalog',
40
+ 'Get the catalog of all available Cross-section lessons',
41
+ {},
42
+ () => {
43
+ const catalog = csLessons.getCrossSectionLessonCatalog();
44
+ const text = catalog.map(l =>
45
+ ` • ${l.id}: ${l.name} — ${l.description}`
46
+ ).join('\n');
47
+ return { content: [{ type: 'text', text: `📚 Cross-section Lessons:\n\n${text}` }] };
48
+ });
49
+ }
50
+
51
+ module.exports = { register };
@@ -0,0 +1,138 @@
1
+ const shapes = require('./shapes');
2
+ const curveShapes = require('./curve_shapes');
3
+
4
+ /**
5
+ * Lesson 1: Curve Types (C, S, Convex, Concave)
6
+ */
7
+ function lessonCurveTypes(canvasSize = 32, color = '#2196f3') {
8
+ const margin = Math.floor(canvasSize * 0.1);
9
+ const commands = [];
10
+
11
+ const w = Math.floor(canvasSize / 2) - margin * 2;
12
+ const h = Math.floor(canvasSize / 2) - margin * 2;
13
+
14
+ // C Curve (Top Left)
15
+ const cP0 = { x: margin + w, y: margin };
16
+ const cP1 = { x: margin - w, y: margin + Math.floor(h/2) }; // Pulls curve left
17
+ const cP2 = { x: margin + w, y: margin + h };
18
+ commands.push(...curveShapes.drawQuadraticCurve(cP0, cP1, cP2, color));
19
+
20
+ // S Curve (Top Right)
21
+ const sP0 = { x: canvasSize - margin, y: margin };
22
+ const sP1 = { x: canvasSize - margin - w*2, y: margin };
23
+ const sP2 = { x: canvasSize - margin + w, y: margin + h };
24
+ const sP3 = { x: canvasSize - margin - w, y: margin + h };
25
+ commands.push(...curveShapes.drawCubicCurve(sP0, sP1, sP2, sP3, '#e91e63'));
26
+
27
+ // Convex (Bottom Left, viewed from bottom)
28
+ const cvxP0 = { x: margin, y: canvasSize - margin };
29
+ const cvxP1 = { x: margin + Math.floor(w/2), y: canvasSize - margin - h };
30
+ const cvxP2 = { x: margin + w, y: canvasSize - margin };
31
+ commands.push(...curveShapes.drawQuadraticCurve(cvxP0, cvxP1, cvxP2, '#4caf50'));
32
+
33
+ // Concave (Bottom Right, viewed from bottom)
34
+ const cnvP0 = { x: canvasSize - margin - w, y: canvasSize - margin - Math.floor(h/2) };
35
+ const cnvP1 = { x: canvasSize - margin - Math.floor(w/2), y: canvasSize - margin + h };
36
+ const cnvP2 = { x: canvasSize - margin, y: canvasSize - margin - Math.floor(h/2) };
37
+ commands.push(...curveShapes.drawQuadraticCurve(cnvP0, cnvP1, cnvP2, '#ff9800'));
38
+
39
+ return commands;
40
+ }
41
+
42
+ /**
43
+ * Lesson 2: Curve Topology (Closed, Open, Symmetrical, Asymmetrical)
44
+ */
45
+ function lessonCurveTopology(canvasSize = 32, color = '#9c27b0') {
46
+ const mid = Math.floor(canvasSize / 2);
47
+ const q = Math.floor(canvasSize / 4);
48
+ const commands = [];
49
+
50
+ // Closed Curve (Circle-like using Bezier for demonstration)
51
+ // Since we already have drawCircle, we just draw a circle to represent a closed curve
52
+ commands.push(shapes.drawCircle(q, q, Math.floor(q*0.8), color, false));
53
+
54
+ // Open Curve (Quadratic)
55
+ commands.push(...curveShapes.drawQuadraticCurve(
56
+ { x: canvasSize - q*1.5, y: q },
57
+ { x: canvasSize - q*0.5, y: q - q/2 },
58
+ { x: canvasSize - q*0.5, y: q + q/2 },
59
+ '#3f51b5'
60
+ ));
61
+
62
+ // Symmetrical Curve (Parabola-like)
63
+ commands.push(...curveShapes.drawQuadraticCurve(
64
+ { x: q*0.5, y: canvasSize - q*0.5 },
65
+ { x: q, y: canvasSize - q*1.5 },
66
+ { x: q*1.5, y: canvasSize - q*0.5 },
67
+ '#00bcd4'
68
+ ));
69
+
70
+ // Asymmetrical Curve
71
+ commands.push(...curveShapes.drawQuadraticCurve(
72
+ { x: canvasSize - q*1.5, y: canvasSize - q*0.5 },
73
+ { x: canvasSize - q*0.2, y: canvasSize - q*1.8 }, // Pull point is skewed
74
+ { x: canvasSize - q*0.5, y: canvasSize - q*0.5 },
75
+ '#ff5722'
76
+ ));
77
+
78
+ return commands;
79
+ }
80
+
81
+ /**
82
+ * Lesson 3: Curve Properties (Start, Direction, Curvature, Inflection, Transition)
83
+ */
84
+ function lessonCurveProperties(canvasSize = 32, color = '#607d8b') {
85
+ const margin = Math.floor(canvasSize * 0.1);
86
+ const commands = [];
87
+
88
+ // 1. Start point and Direction (Top Left Curve)
89
+ const p0 = { x: margin, y: margin * 2 };
90
+ const p1 = { x: margin + canvasSize*0.3, y: margin };
91
+ const p2 = { x: margin + canvasSize*0.3, y: margin + canvasSize*0.3 };
92
+ commands.push(...curveShapes.drawQuadraticCurve(p0, p1, p2, color));
93
+ commands.push(curveShapes.drawStartMarker(p0.x, p0.y, '#00ff00')); // Green dot at start
94
+ commands.push(...curveShapes.drawDirectionArrow(p0, p1, p2, null, '#ff0000')); // Red arrow
95
+
96
+ // 2. Curvature comparison (Bottom Left)
97
+ // Strong vs Weak curve with same endpoints
98
+ const bp0 = { x: margin, y: canvasSize - margin };
99
+ const bp2 = { x: margin + canvasSize*0.4, y: canvasSize - margin };
100
+ // Weak curve
101
+ commands.push(...curveShapes.drawQuadraticCurve(bp0, { x: margin + canvasSize*0.2, y: canvasSize - margin - canvasSize*0.1 }, bp2, '#8bc34a'));
102
+ // Strong curve
103
+ commands.push(...curveShapes.drawQuadraticCurve(bp0, { x: margin + canvasSize*0.2, y: canvasSize - margin - canvasSize*0.4 }, bp2, '#388e3c'));
104
+
105
+ // 3. Inflection point (Top Right S Curve)
106
+ const sP0 = { x: canvasSize - margin - canvasSize*0.3, y: margin };
107
+ const sP1 = { x: canvasSize - margin, y: margin };
108
+ const sP2 = { x: canvasSize - margin - canvasSize*0.4, y: margin + canvasSize*0.3 };
109
+ const sP3 = { x: canvasSize - margin, y: margin + canvasSize*0.3 };
110
+ commands.push(...curveShapes.drawCubicCurve(sP0, sP1, sP2, sP3, '#03a9f4'));
111
+ // Inflection point roughly at t=0.5
112
+ const inflection = curveShapes.cubicBezier(sP0, sP1, sP2, sP3, 0.5);
113
+ commands.push(curveShapes.drawInflectionMarker(inflection.x, inflection.y, '#ff9800'));
114
+
115
+ // 4. Transition (Bottom Right - line transitioning to curve)
116
+ const startLine = { x: canvasSize - margin - canvasSize*0.3, y: canvasSize - margin - canvasSize*0.2 };
117
+ const transPt = { x: canvasSize - margin - canvasSize*0.1, y: canvasSize - margin - canvasSize*0.2 };
118
+ commands.push(shapes.drawLine(startLine.x, startLine.y, transPt.x, transPt.y, '#9e9e9e'));
119
+ // Curve starting tangentially
120
+ commands.push(...curveShapes.drawQuadraticCurve(transPt, { x: canvasSize - margin, y: canvasSize - margin - canvasSize*0.2 }, { x: canvasSize - margin, y: canvasSize - margin }, '#795548'));
121
+
122
+ return commands;
123
+ }
124
+
125
+ function getCurveLessonCatalog() {
126
+ return [
127
+ { id: 'curve_types', name: 'Curve Types', description: 'C, S, Convex, and Concave curves', fn: lessonCurveTypes },
128
+ { id: 'curve_topology', name: 'Curve Topology', description: 'Closed, Open, Symmetrical, Asymmetrical curves', fn: lessonCurveTopology },
129
+ { id: 'curve_properties', name: 'Curve Properties', description: 'Start point, Direction, Curvature, Inflection, and Transitions', fn: lessonCurveProperties },
130
+ ];
131
+ }
132
+
133
+ module.exports = {
134
+ lessonCurveTypes,
135
+ lessonCurveTopology,
136
+ lessonCurveProperties,
137
+ getCurveLessonCatalog
138
+ };
@@ -0,0 +1,90 @@
1
+ const shapes = require('./shapes');
2
+
3
+ /**
4
+ * Draw a marker at the start of a curve
5
+ */
6
+ function drawStartMarker(x, y, color = '#00ff00') {
7
+ return shapes.drawCircle(x, y, 2, color, true);
8
+ }
9
+
10
+ /**
11
+ * Draw an inflection point marker
12
+ */
13
+ function drawInflectionMarker(x, y, color = '#ff0000') {
14
+ return shapes.drawCircle(x, y, 2, color, true);
15
+ }
16
+
17
+ /**
18
+ * Calculate quadratic bezier point
19
+ */
20
+ function quadraticBezier(p0, p1, p2, t) {
21
+ const x = Math.round((1 - t) * (1 - t) * p0.x + 2 * (1 - t) * t * p1.x + t * t * p2.x);
22
+ const y = Math.round((1 - t) * (1 - t) * p0.y + 2 * (1 - t) * t * p1.y + t * t * p2.y);
23
+ return { x, y };
24
+ }
25
+
26
+ /**
27
+ * Draw a quadratic bezier curve as a polyline
28
+ */
29
+ function drawQuadraticCurve(p0, p1, p2, color, segments = 20) {
30
+ const points = [];
31
+ for (let i = 0; i <= segments; i++) {
32
+ const t = i / segments;
33
+ points.push(quadraticBezier(p0, p1, p2, t));
34
+ }
35
+ return shapes.drawPolyline(points, color);
36
+ }
37
+
38
+ /**
39
+ * Calculate cubic bezier point
40
+ */
41
+ function cubicBezier(p0, p1, p2, p3, t) {
42
+ const x = Math.round(Math.pow(1 - t, 3) * p0.x + 3 * Math.pow(1 - t, 2) * t * p1.x + 3 * (1 - t) * t * t * p2.x + t * t * t * p3.x);
43
+ const y = Math.round(Math.pow(1 - t, 3) * p0.y + 3 * Math.pow(1 - t, 2) * t * p1.y + 3 * (1 - t) * t * t * p2.y + t * t * t * p3.y);
44
+ return { x, y };
45
+ }
46
+
47
+ /**
48
+ * Draw a cubic bezier curve as a polyline
49
+ */
50
+ function drawCubicCurve(p0, p1, p2, p3, color, segments = 30) {
51
+ const points = [];
52
+ for (let i = 0; i <= segments; i++) {
53
+ const t = i / segments;
54
+ points.push(cubicBezier(p0, p1, p2, p3, t));
55
+ }
56
+ return shapes.drawPolyline(points, color);
57
+ }
58
+
59
+ /**
60
+ * Draw a direction arrow roughly along the curve at t=0.5
61
+ */
62
+ function drawDirectionArrow(p0, p1, p2, p3 = null, color = '#ff0000') {
63
+ const commands = [];
64
+ const t = 0.5;
65
+ const pt = p3 ? cubicBezier(p0, p1, p2, p3, t) : quadraticBezier(p0, p1, p2, t);
66
+ const ptNext = p3 ? cubicBezier(p0, p1, p2, p3, t + 0.1) : quadraticBezier(p0, p1, p2, t + 0.1);
67
+
68
+ // Calculate angle
69
+ const angle = Math.atan2(ptNext.y - pt.y, ptNext.x - pt.x);
70
+
71
+ // Draw arrow head
72
+ const arrowLen = 4;
73
+ const a1 = angle + Math.PI * 0.8;
74
+ const a2 = angle - Math.PI * 0.8;
75
+
76
+ commands.push(shapes.drawLine(pt.x, pt.y, Math.round(pt.x + arrowLen * Math.cos(a1)), Math.round(pt.y + arrowLen * Math.sin(a1)), color));
77
+ commands.push(shapes.drawLine(pt.x, pt.y, Math.round(pt.x + arrowLen * Math.cos(a2)), Math.round(pt.y + arrowLen * Math.sin(a2)), color));
78
+
79
+ return commands;
80
+ }
81
+
82
+ module.exports = {
83
+ drawStartMarker,
84
+ drawInflectionMarker,
85
+ quadraticBezier,
86
+ drawQuadraticCurve,
87
+ cubicBezier,
88
+ drawCubicCurve,
89
+ drawDirectionArrow
90
+ };
@@ -0,0 +1,51 @@
1
+ const { z } = require('zod');
2
+ const { registerTool, sendCommand } = require('../../core/server');
3
+ const curveLessons = require('./curve_lessons');
4
+
5
+ const hexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/).describe('Hex color e.g. #ff0000');
6
+
7
+ /**
8
+ * Register all advanced curve tools on the MCP server.
9
+ * @param {McpServer} server
10
+ */
11
+ function register(server) {
12
+ registerTool(server, 'art_tree_lesson_curve_types',
13
+ 'Lesson: Advanced Curves - Types (C, S, Convex, Concave)',
14
+ { canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#2196f3') },
15
+ async (p) => {
16
+ const commands = curveLessons.lessonCurveTypes(p.canvasSize, p.color);
17
+ await Promise.all(commands.map(cmd => sendCommand(cmd)));
18
+ return { content: [{ type: 'text', text: `✓ Curve Types lesson completed` }] };
19
+ });
20
+
21
+ registerTool(server, 'art_tree_lesson_curve_topology',
22
+ 'Lesson: Advanced Curves - Topology (Closed, Open, Symmetrical, Asymmetrical)',
23
+ { canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#9c27b0') },
24
+ async (p) => {
25
+ const commands = curveLessons.lessonCurveTopology(p.canvasSize, p.color);
26
+ await Promise.all(commands.map(cmd => sendCommand(cmd)));
27
+ return { content: [{ type: 'text', text: `✓ Curve Topology lesson completed` }] };
28
+ });
29
+
30
+ registerTool(server, 'art_tree_lesson_curve_properties',
31
+ 'Lesson: Advanced Curves - Properties (Start, Direction, Curvature, Inflection, Transitions)',
32
+ { canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#607d8b') },
33
+ async (p) => {
34
+ const commands = curveLessons.lessonCurveProperties(p.canvasSize, p.color);
35
+ await Promise.all(commands.map(cmd => sendCommand(cmd)));
36
+ return { content: [{ type: 'text', text: `✓ Curve Properties lesson completed` }] };
37
+ });
38
+
39
+ registerTool(server, 'art_tree_lesson_curve_catalog',
40
+ 'Get the catalog of all available Advanced Curve lessons',
41
+ {},
42
+ () => {
43
+ const catalog = curveLessons.getCurveLessonCatalog();
44
+ const text = catalog.map(l =>
45
+ ` • ${l.id}: ${l.name} — ${l.description}`
46
+ ).join('\n');
47
+ return { content: [{ type: 'text', text: `📚 Advanced Curve Lessons:\n\n${text}` }] };
48
+ });
49
+ }
50
+
51
+ module.exports = { register };