@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,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 };
@@ -0,0 +1,105 @@
1
+ const shapes = require('./shapes');
2
+ const ellipseShapes = require('./ellipse_shapes');
3
+
4
+ /**
5
+ * Lesson: Ellipse Orientations (Horizontal, Vertical, Tilted)
6
+ */
7
+ function lessonEllipseOrientations(canvasSize = 32, color = '#2196f3') {
8
+ const commands = [];
9
+ const midY = Math.floor(canvasSize / 2);
10
+ const qX = Math.floor(canvasSize / 4);
11
+ const rLong = Math.floor(canvasSize * 0.2);
12
+ const rShort = Math.floor(canvasSize * 0.1);
13
+
14
+ // Horizontal (Left)
15
+ commands.push(shapes.drawEllipse(qX, midY, rLong, rShort, color, false));
16
+
17
+ // Vertical (Middle)
18
+ commands.push(shapes.drawEllipse(qX * 2, midY, rShort, rLong, '#e91e63', false));
19
+
20
+ // Tilted (Right) - 45 degrees
21
+ commands.push(...ellipseShapes.drawRotatedEllipse(qX * 3, midY, rLong, rShort, Math.PI / 4, '#4caf50'));
22
+
23
+ return commands;
24
+ }
25
+
26
+ /**
27
+ * Lesson: Ellipse Proportions (Wide vs Narrow)
28
+ */
29
+ function lessonEllipseProportions(canvasSize = 32, color = '#ff9800') {
30
+ const commands = [];
31
+ const midY = Math.floor(canvasSize / 2);
32
+ const qX = Math.floor(canvasSize / 4);
33
+ const rx = Math.floor(canvasSize * 0.2);
34
+
35
+ // Wide Ellipse (Closer to a circle)
36
+ commands.push(shapes.drawEllipse(qX, midY, rx, Math.floor(rx * 0.8), color, false));
37
+
38
+ // Medium Ellipse
39
+ commands.push(shapes.drawEllipse(qX * 2, midY, rx, Math.floor(rx * 0.4), '#9c27b0', false));
40
+
41
+ // Narrow Ellipse (Almost a line)
42
+ commands.push(shapes.drawEllipse(qX * 3, midY, rx, Math.floor(rx * 0.1), '#f44336', false));
43
+
44
+ return commands;
45
+ }
46
+
47
+ /**
48
+ * Lesson: Ellipse Anatomy (Symmetry, Major/Minor axes, Center)
49
+ */
50
+ function lessonEllipseAnatomy(canvasSize = 32, color = '#00bcd4') {
51
+ const commands = [];
52
+ const cx = Math.floor(canvasSize / 2);
53
+ const cy = Math.floor(canvasSize / 2);
54
+ const rx = Math.floor(canvasSize * 0.35);
55
+ const ry = Math.floor(canvasSize * 0.2);
56
+ const angle = Math.PI / 6; // 30 degrees tilt to show axes clearly
57
+
58
+ // Draw ellipse
59
+ commands.push(...ellipseShapes.drawRotatedEllipse(cx, cy, rx, ry, angle, color));
60
+
61
+ // Draw anatomy (axes and center)
62
+ commands.push(...ellipseShapes.drawEllipseAxes(cx, cy, rx, ry, angle));
63
+
64
+ return commands;
65
+ }
66
+
67
+ /**
68
+ * Lesson: Coaxial Ellipses (Multiple ellipses on the same axis)
69
+ */
70
+ function lessonCoaxialEllipses(canvasSize = 32, color = '#795548') {
71
+ const commands = [];
72
+ const cx = Math.floor(canvasSize / 2);
73
+ const rx = Math.floor(canvasSize * 0.3);
74
+
75
+ // Central axis line
76
+ commands.push(shapes.drawLine(cx, Math.floor(canvasSize * 0.1), cx, canvasSize - Math.floor(canvasSize * 0.1), '#bdbdbd'));
77
+
78
+ // Top ellipse (narrower, viewed from steeper angle)
79
+ commands.push(shapes.drawEllipse(cx, Math.floor(canvasSize * 0.2), rx, Math.floor(rx * 0.2), color, false));
80
+
81
+ // Middle ellipse
82
+ commands.push(shapes.drawEllipse(cx, Math.floor(canvasSize * 0.5), rx, Math.floor(rx * 0.3), color, false));
83
+
84
+ // Bottom ellipse (wider, viewed from less steep angle)
85
+ commands.push(shapes.drawEllipse(cx, canvasSize - Math.floor(canvasSize * 0.2), rx, Math.floor(rx * 0.4), color, false));
86
+
87
+ return commands;
88
+ }
89
+
90
+ function getEllipseLessonCatalog() {
91
+ return [
92
+ { id: 'ellipse_orientations', name: 'Ellipse Orientations', description: 'Horizontal, Vertical, and Tilted ellipses', fn: lessonEllipseOrientations },
93
+ { id: 'ellipse_proportions', name: 'Ellipse Proportions', description: 'Wide vs Narrow ellipses', fn: lessonEllipseProportions },
94
+ { id: 'ellipse_anatomy', name: 'Ellipse Anatomy', description: 'Symmetry, Major/Minor axes, and Center', fn: lessonEllipseAnatomy },
95
+ { id: 'ellipse_coaxial', name: 'Coaxial Ellipses', description: 'Multiple ellipses sharing the same axis', fn: lessonCoaxialEllipses },
96
+ ];
97
+ }
98
+
99
+ module.exports = {
100
+ lessonEllipseOrientations,
101
+ lessonEllipseProportions,
102
+ lessonEllipseAnatomy,
103
+ lessonCoaxialEllipses,
104
+ getEllipseLessonCatalog
105
+ };
@@ -0,0 +1,63 @@
1
+ const shapes = require('./shapes');
2
+
3
+ /**
4
+ * Draw a rotated ellipse using a polygon approximation
5
+ * @param {number} cx Center X
6
+ * @param {number} cy Center Y
7
+ * @param {number} rx X radius (major axis if rx > ry)
8
+ * @param {number} ry Y radius
9
+ * @param {number} angle Rotation angle in radians
10
+ * @param {string} color Hex color
11
+ * @param {number} segments Number of polygon segments
12
+ */
13
+ function drawRotatedEllipse(cx, cy, rx, ry, angle, color, segments = 36) {
14
+ const points = [];
15
+ const cosA = Math.cos(angle);
16
+ const sinA = Math.sin(angle);
17
+
18
+ for (let i = 0; i <= segments; i++) {
19
+ const t = (i / segments) * 2 * Math.PI;
20
+ const px = rx * Math.cos(t);
21
+ const py = ry * Math.sin(t);
22
+
23
+ const x = Math.round(cx + px * cosA - py * sinA);
24
+ const y = Math.round(cy + px * sinA + py * cosA);
25
+ points.push({ x, y });
26
+ }
27
+
28
+ return shapes.drawPolyline(points, color);
29
+ }
30
+
31
+ /**
32
+ * Draw major and minor axes for an ellipse
33
+ */
34
+ function drawEllipseAxes(cx, cy, rx, ry, angle = 0) {
35
+ const commands = [];
36
+ const cosA = Math.cos(angle);
37
+ const sinA = Math.sin(angle);
38
+
39
+ // Major/Minor Axis calculation based on rotation
40
+ const x1 = Math.round(cx - rx * cosA);
41
+ const y1 = Math.round(cy - rx * sinA);
42
+ const x2 = Math.round(cx + rx * cosA);
43
+ const y2 = Math.round(cy + rx * sinA);
44
+
45
+ const y3_x = Math.round(cx + ry * sinA);
46
+ const y3_y = Math.round(cy - ry * cosA);
47
+ const y4_x = Math.round(cx - ry * sinA);
48
+ const y4_y = Math.round(cy + ry * cosA);
49
+
50
+ // Red for Major, Blue for Minor
51
+ commands.push(shapes.drawLine(x1, y1, x2, y2, '#ff0000'));
52
+ commands.push(shapes.drawLine(y3_x, y3_y, y4_x, y4_y, '#2196f3'));
53
+
54
+ // Center dot
55
+ commands.push(shapes.drawCircle(cx, cy, 1, '#000000', true));
56
+
57
+ return commands;
58
+ }
59
+
60
+ module.exports = {
61
+ drawRotatedEllipse,
62
+ drawEllipseAxes
63
+ };
@@ -0,0 +1,60 @@
1
+ const { z } = require('zod');
2
+ const { registerTool, sendCommand } = require('../../core/server');
3
+ const ellipseLessons = require('./ellipse_lessons');
4
+
5
+ const hexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/).describe('Hex color e.g. #ff0000');
6
+
7
+ /**
8
+ * Register all ellipse tools on the MCP server.
9
+ * @param {McpServer} server
10
+ */
11
+ function register(server) {
12
+ registerTool(server, 'art_tree_lesson_ellipse_orientations',
13
+ 'Lesson: Ellipses - Orientations (Horizontal, Vertical, Tilted)',
14
+ { canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#2196f3') },
15
+ async (p) => {
16
+ const commands = ellipseLessons.lessonEllipseOrientations(p.canvasSize, p.color);
17
+ await Promise.all(commands.map(cmd => sendCommand(cmd)));
18
+ return { content: [{ type: 'text', text: `✓ Ellipse Orientations lesson completed` }] };
19
+ });
20
+
21
+ registerTool(server, 'art_tree_lesson_ellipse_proportions',
22
+ 'Lesson: Ellipses - Proportions (Wide vs Narrow)',
23
+ { canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#ff9800') },
24
+ async (p) => {
25
+ const commands = ellipseLessons.lessonEllipseProportions(p.canvasSize, p.color);
26
+ await Promise.all(commands.map(cmd => sendCommand(cmd)));
27
+ return { content: [{ type: 'text', text: `✓ Ellipse Proportions lesson completed` }] };
28
+ });
29
+
30
+ registerTool(server, 'art_tree_lesson_ellipse_anatomy',
31
+ 'Lesson: Ellipses - Anatomy (Symmetry, Axes, Center)',
32
+ { canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#00bcd4') },
33
+ async (p) => {
34
+ const commands = ellipseLessons.lessonEllipseAnatomy(p.canvasSize, p.color);
35
+ await Promise.all(commands.map(cmd => sendCommand(cmd)));
36
+ return { content: [{ type: 'text', text: `✓ Ellipse Anatomy lesson completed` }] };
37
+ });
38
+
39
+ registerTool(server, 'art_tree_lesson_coaxial_ellipses',
40
+ 'Lesson: Ellipses - Coaxial (Multiple ellipses on the same axis)',
41
+ { canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#795548') },
42
+ async (p) => {
43
+ const commands = ellipseLessons.lessonCoaxialEllipses(p.canvasSize, p.color);
44
+ await Promise.all(commands.map(cmd => sendCommand(cmd)));
45
+ return { content: [{ type: 'text', text: `✓ Coaxial Ellipses lesson completed` }] };
46
+ });
47
+
48
+ registerTool(server, 'art_tree_lesson_ellipse_catalog',
49
+ 'Get the catalog of all available Ellipse lessons',
50
+ {},
51
+ () => {
52
+ const catalog = ellipseLessons.getEllipseLessonCatalog();
53
+ const text = catalog.map(l =>
54
+ ` • ${l.id}: ${l.name} — ${l.description}`
55
+ ).join('\n');
56
+ return { content: [{ type: 'text', text: `📚 Ellipse Lessons:\n\n${text}` }] };
57
+ });
58
+ }
59
+
60
+ module.exports = { register };