@pixel-normal-edit/mcp 2.0.6 → 2.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/domains/art-tree/3d_lessons.js +114 -0
- package/domains/art-tree/3d_shapes.js +116 -0
- package/domains/art-tree/3d_tools.js +60 -0
- package/domains/art-tree/advanced_lessons.js +124 -0
- package/domains/art-tree/advanced_shapes.js +73 -0
- package/domains/art-tree/advanced_tools.js +69 -0
- package/domains/art-tree/analysis_lessons.js +32 -0
- package/domains/art-tree/analysis_shapes.js +146 -0
- package/domains/art-tree/analysis_tools.js +49 -0
- package/domains/art-tree/cross_section_lessons.js +65 -0
- package/domains/art-tree/cross_section_shapes.js +84 -0
- package/domains/art-tree/cross_section_tools.js +51 -0
- package/domains/art-tree/curve_lessons.js +138 -0
- package/domains/art-tree/curve_shapes.js +90 -0
- package/domains/art-tree/curve_tools.js +51 -0
- package/domains/art-tree/ellipse_lessons.js +105 -0
- package/domains/art-tree/ellipse_shapes.js +63 -0
- package/domains/art-tree/ellipse_tools.js +60 -0
- package/domains/art-tree/hidden_lessons.js +55 -0
- package/domains/art-tree/hidden_shapes.js +97 -0
- package/domains/art-tree/hidden_tools.js +51 -0
- package/domains/art-tree/index.js +32 -0
- package/domains/art-tree/layer_lessons.js +32 -0
- package/domains/art-tree/layer_shapes.js +117 -0
- package/domains/art-tree/layer_tools.js +43 -0
- package/domains/art-tree/lessons.js +119 -7
- package/domains/art-tree/light_lessons.js +92 -0
- package/domains/art-tree/light_shapes.js +136 -0
- package/domains/art-tree/light_tools.js +49 -0
- package/domains/art-tree/material_lessons.js +71 -0
- package/domains/art-tree/material_shapes.js +130 -0
- package/domains/art-tree/material_tools.js +49 -0
- package/domains/art-tree/perspective_lessons.js +138 -0
- package/domains/art-tree/perspective_shapes.js +96 -0
- package/domains/art-tree/perspective_tools.js +60 -0
- package/domains/art-tree/shapes.js +15 -0
- package/domains/art-tree/sky_lessons_1.js +82 -0
- package/domains/art-tree/sky_shapes_1.js +68 -0
- package/domains/art-tree/sky_tools.js +51 -0
- package/domains/art-tree/structure_lessons.js +102 -0
- package/domains/art-tree/structure_shapes.js +74 -0
- package/domains/art-tree/structure_tools.js +51 -0
- package/domains/art-tree/surface_lessons.js +112 -0
- package/domains/art-tree/surface_shapes.js +87 -0
- package/domains/art-tree/surface_tools.js +60 -0
- package/domains/art-tree/tools.js +69 -15
- package/domains/art-tree/transform_lessons_1.js +108 -0
- package/domains/art-tree/transform_lessons_2.js +102 -0
- package/domains/art-tree/transform_shapes.js +109 -0
- package/domains/art-tree/transform_tools.js +104 -0
- package/domains/art-tree/vocab_lessons.js +32 -0
- package/domains/art-tree/vocab_shapes.js +116 -0
- package/domains/art-tree/vocab_tools.js +41 -0
- package/package.json +1 -1
- package/tools/index.js +2 -0
- package/tools/layer.js +80 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
const shapes3d = require('./3d_shapes');
|
|
2
|
+
const advShapes = require('./advanced_shapes');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Lesson: Box Properties (Width, Height, Depth, Planes, Edges)
|
|
6
|
+
*/
|
|
7
|
+
function lessonBoxProperties(canvasSize = 32, color = '#2196f3') {
|
|
8
|
+
const commands = [];
|
|
9
|
+
const cx = Math.floor(canvasSize / 2);
|
|
10
|
+
const cy = Math.floor(canvasSize / 2);
|
|
11
|
+
const w = Math.floor(canvasSize * 0.4);
|
|
12
|
+
const h = Math.floor(canvasSize * 0.4);
|
|
13
|
+
const d = Math.floor(canvasSize * 0.3);
|
|
14
|
+
|
|
15
|
+
// Draw wireframe box
|
|
16
|
+
commands.push(...shapes3d.drawWireframeBox(cx, cy, w, h, d, color));
|
|
17
|
+
|
|
18
|
+
// Highlight dimensions
|
|
19
|
+
const fX = cx - Math.floor(w/2);
|
|
20
|
+
const fY = cy - Math.floor(h/2);
|
|
21
|
+
|
|
22
|
+
// Width
|
|
23
|
+
commands.push(...advShapes.drawDistance(fX, fY + h + 2, fX + w, fY + h + 2, '#4caf50'));
|
|
24
|
+
// Height
|
|
25
|
+
commands.push(...advShapes.drawDistance(fX - 2, fY, fX - 2, fY + h, '#ff9800'));
|
|
26
|
+
|
|
27
|
+
// Depth (approximate along the oblique edge)
|
|
28
|
+
const dx = Math.floor(d * 0.7);
|
|
29
|
+
const dy = Math.floor(d * 0.5);
|
|
30
|
+
commands.push(...advShapes.drawDistance(fX + w + 2, fY + h, fX + w + 2 + dx, fY + h - dy, '#e91e63'));
|
|
31
|
+
|
|
32
|
+
return commands;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Lesson: Sphere Properties (Center, Radius, Cross contours, Axis)
|
|
37
|
+
*/
|
|
38
|
+
function lessonSphereProperties(canvasSize = 32, color = '#9c27b0') {
|
|
39
|
+
const commands = [];
|
|
40
|
+
const cx = Math.floor(canvasSize / 2);
|
|
41
|
+
const cy = Math.floor(canvasSize / 2);
|
|
42
|
+
const r = Math.floor(canvasSize * 0.35);
|
|
43
|
+
|
|
44
|
+
// Draw sphere with volume lines
|
|
45
|
+
commands.push(...shapes3d.drawSphereVolume(cx, cy, r, color));
|
|
46
|
+
|
|
47
|
+
// Highlight radius
|
|
48
|
+
commands.push(...advShapes.drawDistance(cx, cy, cx + r, cy, '#ff9800'));
|
|
49
|
+
|
|
50
|
+
return commands;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Lesson: Cylinder Properties (Central axis, Bases, Perspective change)
|
|
55
|
+
*/
|
|
56
|
+
function lessonCylinderProperties(canvasSize = 32, color = '#00bcd4') {
|
|
57
|
+
const commands = [];
|
|
58
|
+
const cx = Math.floor(canvasSize / 2);
|
|
59
|
+
const cy = Math.floor(canvasSize / 2);
|
|
60
|
+
const r = Math.floor(canvasSize * 0.3);
|
|
61
|
+
const h = Math.floor(canvasSize * 0.6);
|
|
62
|
+
|
|
63
|
+
// Draw cylinder volume
|
|
64
|
+
commands.push(...shapes3d.drawCylinderVolume(cx, cy, r, h, color));
|
|
65
|
+
|
|
66
|
+
// Highlight Height
|
|
67
|
+
commands.push(...advShapes.drawDistance(cx + r + 4, cy - Math.floor(h/2), cx + r + 4, cy + Math.floor(h/2), '#4caf50'));
|
|
68
|
+
|
|
69
|
+
// Highlight Radius (top base)
|
|
70
|
+
commands.push(...advShapes.drawDistance(cx, cy - Math.floor(h/2), cx + r, cy - Math.floor(h/2), '#ff9800'));
|
|
71
|
+
|
|
72
|
+
return commands;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Lesson: Cone Properties (Apex, Base, Axis)
|
|
77
|
+
*/
|
|
78
|
+
function lessonConeProperties(canvasSize = 32, color = '#ff5722') {
|
|
79
|
+
const commands = [];
|
|
80
|
+
const cx = Math.floor(canvasSize / 2);
|
|
81
|
+
const cy = Math.floor(canvasSize / 2);
|
|
82
|
+
const r = Math.floor(canvasSize * 0.35);
|
|
83
|
+
const h = Math.floor(canvasSize * 0.6);
|
|
84
|
+
|
|
85
|
+
// Draw cone volume
|
|
86
|
+
commands.push(...shapes3d.drawConeVolume(cx, cy, r, h, color));
|
|
87
|
+
|
|
88
|
+
// Highlight Height
|
|
89
|
+
const apexY = cy - Math.floor(h/2);
|
|
90
|
+
const baseY = cy + Math.floor(h/2);
|
|
91
|
+
commands.push(...advShapes.drawDistance(cx + r + 4, apexY, cx + r + 4, baseY, '#4caf50'));
|
|
92
|
+
|
|
93
|
+
// Highlight Radius (base)
|
|
94
|
+
commands.push(...advShapes.drawDistance(cx, baseY, cx + r, baseY, '#ff9800'));
|
|
95
|
+
|
|
96
|
+
return commands;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function get3DLessonCatalog() {
|
|
100
|
+
return [
|
|
101
|
+
{ id: '3d_box', name: '3D Box Properties', description: 'Width, height, depth, planes, inner structure', fn: lessonBoxProperties },
|
|
102
|
+
{ id: '3d_sphere', name: '3D Sphere Properties', description: 'Center, radius, axis, cross contours', fn: lessonSphereProperties },
|
|
103
|
+
{ id: '3d_cylinder', name: '3D Cylinder Properties', description: 'Axis, bases, height, perspective changes', fn: lessonCylinderProperties },
|
|
104
|
+
{ id: '3d_cone', name: '3D Cone Properties', description: 'Apex, base, axis, radius', fn: lessonConeProperties },
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = {
|
|
109
|
+
lessonBoxProperties,
|
|
110
|
+
lessonSphereProperties,
|
|
111
|
+
lessonCylinderProperties,
|
|
112
|
+
lessonConeProperties,
|
|
113
|
+
get3DLessonCatalog
|
|
114
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
const shapes = require('./shapes');
|
|
2
|
+
const ellipseShapes = require('./ellipse_shapes');
|
|
3
|
+
const advShapes = require('./advanced_shapes');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Draw a wireframe box (oblique projection)
|
|
7
|
+
*/
|
|
8
|
+
function drawWireframeBox(cx, cy, width, height, depth, color) {
|
|
9
|
+
const commands = [];
|
|
10
|
+
const dx = Math.floor(depth * 0.7);
|
|
11
|
+
const dy = Math.floor(depth * 0.5);
|
|
12
|
+
|
|
13
|
+
// Front face
|
|
14
|
+
const fX = cx - Math.floor(width/2);
|
|
15
|
+
const fY = cy - Math.floor(height/2);
|
|
16
|
+
commands.push(shapes.drawRectangle(fX, fY, width, height, color, false));
|
|
17
|
+
|
|
18
|
+
// Back face (shifted by dx, -dy)
|
|
19
|
+
const bX = fX + dx;
|
|
20
|
+
const bY = fY - dy;
|
|
21
|
+
const backColor = '#9e9e9e'; // Lighter color for inner/back structure
|
|
22
|
+
commands.push(shapes.drawRectangle(bX, bY, width, height, backColor, false));
|
|
23
|
+
|
|
24
|
+
// Connect corners (edges)
|
|
25
|
+
commands.push(shapes.drawLine(fX, fY, bX, bY, color)); // Top-left
|
|
26
|
+
commands.push(shapes.drawLine(fX + width, fY, bX + width, bY, color)); // Top-right
|
|
27
|
+
commands.push(shapes.drawLine(fX, fY + height, bX, bY + height, backColor)); // Bottom-left (hidden)
|
|
28
|
+
commands.push(shapes.drawLine(fX + width, fY + height, bX + width, bY + height, color)); // Bottom-right
|
|
29
|
+
|
|
30
|
+
return commands;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Draw a sphere showing volume with cross contours
|
|
35
|
+
*/
|
|
36
|
+
function drawSphereVolume(cx, cy, r, color) {
|
|
37
|
+
const commands = [];
|
|
38
|
+
|
|
39
|
+
// Silhouette
|
|
40
|
+
commands.push(shapes.drawCircle(cx, cy, r, color, false));
|
|
41
|
+
|
|
42
|
+
// Horizontal cross contour (latitude)
|
|
43
|
+
commands.push(shapes.drawEllipse(cx, cy, r, Math.floor(r * 0.3), '#03a9f4', false));
|
|
44
|
+
|
|
45
|
+
// Vertical cross contour (longitude)
|
|
46
|
+
commands.push(shapes.drawEllipse(cx, cy, Math.floor(r * 0.3), r, '#03a9f4', false));
|
|
47
|
+
|
|
48
|
+
// Center
|
|
49
|
+
commands.push(shapes.drawCircle(cx, cy, 1, '#ff0000', true));
|
|
50
|
+
|
|
51
|
+
// Axis
|
|
52
|
+
commands.push(advShapes.drawAxis(cx, cy - r - 5, cx, cy + r + 5));
|
|
53
|
+
|
|
54
|
+
return commands;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Draw a cylinder showing volume and perspective
|
|
59
|
+
*/
|
|
60
|
+
function drawCylinderVolume(cx, cy, r, h, color) {
|
|
61
|
+
const commands = [];
|
|
62
|
+
|
|
63
|
+
const topRy = Math.floor(r * 0.2); // Top ellipse is narrower
|
|
64
|
+
const botRy = Math.floor(r * 0.3); // Bottom ellipse is wider (perspective)
|
|
65
|
+
|
|
66
|
+
const topY = cy - Math.floor(h/2);
|
|
67
|
+
const botY = cy + Math.floor(h/2);
|
|
68
|
+
|
|
69
|
+
// Central axis
|
|
70
|
+
commands.push(advShapes.drawAxis(cx, topY - 10, cx, botY + 10));
|
|
71
|
+
|
|
72
|
+
// Back curve of bottom ellipse (lighter)
|
|
73
|
+
// Our drawEllipse draws the whole thing, we'll just draw the whole ellipse
|
|
74
|
+
commands.push(shapes.drawEllipse(cx, botY, r, botRy, color, false));
|
|
75
|
+
|
|
76
|
+
// Vertical edges
|
|
77
|
+
commands.push(shapes.drawLine(cx - r, topY, cx - r, botY, color));
|
|
78
|
+
commands.push(shapes.drawLine(cx + r, topY, cx + r, botY, color));
|
|
79
|
+
|
|
80
|
+
// Top ellipse
|
|
81
|
+
commands.push(shapes.drawEllipse(cx, topY, r, topRy, color, false));
|
|
82
|
+
|
|
83
|
+
return commands;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Draw a cone showing volume
|
|
88
|
+
*/
|
|
89
|
+
function drawConeVolume(cx, cy, r, h, color) {
|
|
90
|
+
const commands = [];
|
|
91
|
+
const baseRy = Math.floor(r * 0.3);
|
|
92
|
+
const apexY = cy - Math.floor(h/2);
|
|
93
|
+
const baseY = cy + Math.floor(h/2);
|
|
94
|
+
|
|
95
|
+
// Central axis
|
|
96
|
+
commands.push(advShapes.drawAxis(cx, apexY - 10, cx, baseY + 10));
|
|
97
|
+
|
|
98
|
+
// Base ellipse
|
|
99
|
+
commands.push(shapes.drawEllipse(cx, baseY, r, baseRy, color, false));
|
|
100
|
+
|
|
101
|
+
// Tangent edges (approximate to the edges of the ellipse rx)
|
|
102
|
+
commands.push(shapes.drawLine(cx, apexY, cx - r, baseY, color));
|
|
103
|
+
commands.push(shapes.drawLine(cx, apexY, cx + r, baseY, color));
|
|
104
|
+
|
|
105
|
+
// Apex
|
|
106
|
+
commands.push(shapes.drawCircle(cx, apexY, 2, '#ff0000', true));
|
|
107
|
+
|
|
108
|
+
return commands;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = {
|
|
112
|
+
drawWireframeBox,
|
|
113
|
+
drawSphereVolume,
|
|
114
|
+
drawCylinderVolume,
|
|
115
|
+
drawConeVolume
|
|
116
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
const { registerTool, sendCommand } = require('../../core/server');
|
|
3
|
+
const lessons3d = require('./3d_lessons');
|
|
4
|
+
|
|
5
|
+
const hexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/).describe('Hex color e.g. #ff0000');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Register all 3D tools on the MCP server.
|
|
9
|
+
* @param {McpServer} server
|
|
10
|
+
*/
|
|
11
|
+
function register(server) {
|
|
12
|
+
registerTool(server, 'art_tree_lesson_3d_box',
|
|
13
|
+
'Lesson: 3D Forms - Box Properties (Width, Height, Depth, Planes)',
|
|
14
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#2196f3') },
|
|
15
|
+
async (p) => {
|
|
16
|
+
const commands = lessons3d.lessonBoxProperties(p.canvasSize, p.color);
|
|
17
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
18
|
+
return { content: [{ type: 'text', text: `✓ 3D Box Properties lesson completed` }] };
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
registerTool(server, 'art_tree_lesson_3d_sphere',
|
|
22
|
+
'Lesson: 3D Forms - Sphere Properties (Center, Radius, Cross contours)',
|
|
23
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#9c27b0') },
|
|
24
|
+
async (p) => {
|
|
25
|
+
const commands = lessons3d.lessonSphereProperties(p.canvasSize, p.color);
|
|
26
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
27
|
+
return { content: [{ type: 'text', text: `✓ 3D Sphere Properties lesson completed` }] };
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
registerTool(server, 'art_tree_lesson_3d_cylinder',
|
|
31
|
+
'Lesson: 3D Forms - Cylinder Properties (Axis, Bases, Height)',
|
|
32
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#00bcd4') },
|
|
33
|
+
async (p) => {
|
|
34
|
+
const commands = lessons3d.lessonCylinderProperties(p.canvasSize, p.color);
|
|
35
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
36
|
+
return { content: [{ type: 'text', text: `✓ 3D Cylinder Properties lesson completed` }] };
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
registerTool(server, 'art_tree_lesson_3d_cone',
|
|
40
|
+
'Lesson: 3D Forms - Cone Properties (Apex, Base, Axis)',
|
|
41
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#ff5722') },
|
|
42
|
+
async (p) => {
|
|
43
|
+
const commands = lessons3d.lessonConeProperties(p.canvasSize, p.color);
|
|
44
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
45
|
+
return { content: [{ type: 'text', text: `✓ 3D Cone Properties lesson completed` }] };
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
registerTool(server, 'art_tree_lesson_3d_catalog',
|
|
49
|
+
'Get the catalog of all available 3D Form lessons',
|
|
50
|
+
{},
|
|
51
|
+
() => {
|
|
52
|
+
const catalog = lessons3d.get3DLessonCatalog();
|
|
53
|
+
const text = catalog.map(l =>
|
|
54
|
+
` • ${l.id}: ${l.name} — ${l.description}`
|
|
55
|
+
).join('\n');
|
|
56
|
+
return { content: [{ type: 'text', text: `📚 3D Form Lessons:\n\n${text}` }] };
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = { register };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
const shapes = require('./shapes');
|
|
2
|
+
const advShapes = require('./advanced_shapes');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Lesson: Shape Ratios
|
|
6
|
+
*/
|
|
7
|
+
function lessonShapeRatios(canvasSize = 32, color = '#2196f3') {
|
|
8
|
+
const margin = Math.floor(canvasSize * 0.1);
|
|
9
|
+
const w1 = Math.floor(canvasSize * 0.2);
|
|
10
|
+
const h1 = w1; // 1:1
|
|
11
|
+
const w2 = Math.floor(canvasSize * 0.2);
|
|
12
|
+
const h2 = Math.floor(canvasSize * 0.4); // 1:2
|
|
13
|
+
|
|
14
|
+
return [
|
|
15
|
+
shapes.drawRectangle(margin, margin, w1, h1, color, false),
|
|
16
|
+
shapes.drawRectangle(canvasSize - margin - w2, margin, w2, h2, '#4caf50', false),
|
|
17
|
+
...advShapes.drawDistance(margin, margin + h1 + 2, margin + w1, margin + h1 + 2, '#888'),
|
|
18
|
+
...advShapes.drawDistance(canvasSize - margin - w2, margin + h2 + 2, canvasSize - margin, margin + h2 + 2, '#888')
|
|
19
|
+
];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Lesson: Symmetry & Axes
|
|
24
|
+
*/
|
|
25
|
+
function lessonSymmetryAndAxis(canvasSize = 32, color = '#9c27b0') {
|
|
26
|
+
const mid = Math.floor(canvasSize / 2);
|
|
27
|
+
const w = Math.floor(canvasSize * 0.5);
|
|
28
|
+
const h = Math.floor(canvasSize * 0.5);
|
|
29
|
+
const commands = [];
|
|
30
|
+
|
|
31
|
+
// Draw Rhombus
|
|
32
|
+
commands.push(advShapes.drawRhombus(mid, mid, w, h, color, false));
|
|
33
|
+
// Vertical axis
|
|
34
|
+
commands.push(advShapes.drawAxis(mid, Math.floor(canvasSize * 0.1), mid, canvasSize - Math.floor(canvasSize * 0.1)));
|
|
35
|
+
// Horizontal axis
|
|
36
|
+
commands.push(advShapes.drawAxis(Math.floor(canvasSize * 0.1), mid, canvasSize - Math.floor(canvasSize * 0.1), mid));
|
|
37
|
+
|
|
38
|
+
return commands;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Lesson: Angles
|
|
43
|
+
*/
|
|
44
|
+
function lessonAngles(canvasSize = 32, color = '#ff5722') {
|
|
45
|
+
const mid = Math.floor(canvasSize / 2);
|
|
46
|
+
const w = Math.floor(canvasSize * 0.6);
|
|
47
|
+
const h = Math.floor(canvasSize * 0.3);
|
|
48
|
+
const margin = Math.floor(canvasSize * 0.2);
|
|
49
|
+
|
|
50
|
+
const commands = [];
|
|
51
|
+
// Trapezoid (shows acute and obtuse angles)
|
|
52
|
+
commands.push(advShapes.drawTrapezoid(margin, margin, Math.floor(w/2), w, h, color, false));
|
|
53
|
+
|
|
54
|
+
// Right Triangle (shows 90 degree angle)
|
|
55
|
+
commands.push(shapes.drawTriangle([
|
|
56
|
+
{ x: margin, y: canvasSize - margin },
|
|
57
|
+
{ x: margin, y: mid + margin },
|
|
58
|
+
{ x: margin + w, y: canvasSize - margin }
|
|
59
|
+
], '#3f51b5', false));
|
|
60
|
+
|
|
61
|
+
return commands;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Lesson: Distances
|
|
66
|
+
*/
|
|
67
|
+
function lessonDistances(canvasSize = 32, color = '#00bcd4') {
|
|
68
|
+
const margin = Math.floor(canvasSize * 0.2);
|
|
69
|
+
const commands = [];
|
|
70
|
+
|
|
71
|
+
// Points
|
|
72
|
+
commands.push(shapes.drawCircle(margin, margin, 1, color, true));
|
|
73
|
+
commands.push(shapes.drawCircle(canvasSize - margin, margin, 1, color, true));
|
|
74
|
+
commands.push(shapes.drawCircle(margin, canvasSize - margin, 1, color, true));
|
|
75
|
+
|
|
76
|
+
// Distances between them
|
|
77
|
+
commands.push(...advShapes.drawDistance(margin, margin, canvasSize - margin, margin));
|
|
78
|
+
commands.push(...advShapes.drawDistance(margin, margin, margin, canvasSize - margin));
|
|
79
|
+
|
|
80
|
+
return commands;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Lesson: Shape Relationships
|
|
85
|
+
*/
|
|
86
|
+
function lessonShapeRelationships(canvasSize = 32, color = '#607d8b') {
|
|
87
|
+
const mid = Math.floor(canvasSize / 2);
|
|
88
|
+
const r = Math.floor(canvasSize * 0.2);
|
|
89
|
+
const commands = [];
|
|
90
|
+
|
|
91
|
+
// Overlapping circles
|
|
92
|
+
commands.push(shapes.drawCircle(mid - Math.floor(r/2), mid, r, color, false));
|
|
93
|
+
commands.push(shapes.drawCircle(mid + Math.floor(r/2), mid, r, '#e91e63', false));
|
|
94
|
+
|
|
95
|
+
// Nested squares (top left)
|
|
96
|
+
const margin = Math.floor(canvasSize * 0.1);
|
|
97
|
+
const size = Math.floor(canvasSize * 0.25);
|
|
98
|
+
commands.push(shapes.drawSquare(margin, margin, size, '#4caf50', false));
|
|
99
|
+
commands.push(shapes.drawSquare(margin + 2, margin + 2, size - 4, '#8bc34a', false));
|
|
100
|
+
|
|
101
|
+
return commands;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get catalog of advanced lessons
|
|
106
|
+
*/
|
|
107
|
+
function getAdvancedLessonCatalog() {
|
|
108
|
+
return [
|
|
109
|
+
{ id: 'adv_ratios', name: 'Shape Ratios', description: 'Width-to-height ratios of shapes', fn: lessonShapeRatios },
|
|
110
|
+
{ id: 'adv_symmetry', name: 'Symmetry & Axes', description: 'Symmetry and central axes', fn: lessonSymmetryAndAxis },
|
|
111
|
+
{ id: 'adv_angles', name: 'Angles', description: 'Acute, right, and obtuse angles', fn: lessonAngles },
|
|
112
|
+
{ id: 'adv_distances', name: 'Distances', description: 'Distances between points', fn: lessonDistances },
|
|
113
|
+
{ id: 'adv_relationships', name: 'Shape Relationships', description: 'Overlap and nesting of shapes', fn: lessonShapeRelationships },
|
|
114
|
+
];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = {
|
|
118
|
+
lessonShapeRatios,
|
|
119
|
+
lessonSymmetryAndAxis,
|
|
120
|
+
lessonAngles,
|
|
121
|
+
lessonDistances,
|
|
122
|
+
lessonShapeRelationships,
|
|
123
|
+
getAdvancedLessonCatalog
|
|
124
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const shapes = require('./shapes');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Draw a trapezoid (isosceles usually) centered horizontally.
|
|
5
|
+
* @param {number} x - Top-left X of the bounding box
|
|
6
|
+
* @param {number} y - Top Y
|
|
7
|
+
* @param {number} topWidth - Width of the top edge
|
|
8
|
+
* @param {number} bottomWidth - Width of the bottom edge
|
|
9
|
+
* @param {number} height - Height of the trapezoid
|
|
10
|
+
* @param {string} color - Hex color
|
|
11
|
+
* @param {boolean} filled
|
|
12
|
+
*/
|
|
13
|
+
function drawTrapezoid(x, y, topWidth, bottomWidth, height, color, filled = false) {
|
|
14
|
+
const midX = x + Math.max(topWidth, bottomWidth) / 2;
|
|
15
|
+
const topX1 = Math.round(midX - topWidth / 2);
|
|
16
|
+
const topX2 = Math.round(midX + topWidth / 2);
|
|
17
|
+
const botX1 = Math.round(midX - bottomWidth / 2);
|
|
18
|
+
const botX2 = Math.round(midX + bottomWidth / 2);
|
|
19
|
+
|
|
20
|
+
const points = [
|
|
21
|
+
{ x: topX1, y },
|
|
22
|
+
{ x: topX2, y },
|
|
23
|
+
{ x: botX2, y: y + height },
|
|
24
|
+
{ x: botX1, y: y + height }
|
|
25
|
+
];
|
|
26
|
+
return shapes.drawPolygon(points, color, filled);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Draw a rhombus given center and its two diagonals (width, height).
|
|
31
|
+
*/
|
|
32
|
+
function drawRhombus(cx, cy, width, height, color, filled = false) {
|
|
33
|
+
const points = [
|
|
34
|
+
{ x: cx, y: cy - Math.floor(height / 2) },
|
|
35
|
+
{ x: cx + Math.floor(width / 2), y: cy },
|
|
36
|
+
{ x: cx, y: cy + Math.floor(height / 2) },
|
|
37
|
+
{ x: cx - Math.floor(width / 2), y: cy }
|
|
38
|
+
];
|
|
39
|
+
return shapes.drawPolygon(points, color, filled);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Draw a symmetry axis line (using a distinct color, e.g., light grey/red).
|
|
44
|
+
*/
|
|
45
|
+
function drawAxis(x0, y0, x1, y1) {
|
|
46
|
+
return shapes.drawLine(x0, y0, x1, y1, '#ffaaaa'); // Light red for axis
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Draw a dimension/distance line with end ticks.
|
|
51
|
+
*/
|
|
52
|
+
function drawDistance(x0, y0, x1, y1, color = '#888888') {
|
|
53
|
+
const commands = [];
|
|
54
|
+
commands.push(shapes.drawLine(x0, y0, x1, y1, color));
|
|
55
|
+
|
|
56
|
+
// Draw small ticks at ends (perpendicular)
|
|
57
|
+
const isHorizontal = Math.abs(x1 - x0) > Math.abs(y1 - y0);
|
|
58
|
+
if (isHorizontal) {
|
|
59
|
+
commands.push(shapes.drawLine(x0, y0 - 2, x0, y0 + 2, color));
|
|
60
|
+
commands.push(shapes.drawLine(x1, y1 - 2, x1, y1 + 2, color));
|
|
61
|
+
} else {
|
|
62
|
+
commands.push(shapes.drawLine(x0 - 2, y0, x0 + 2, y0, color));
|
|
63
|
+
commands.push(shapes.drawLine(x1 - 2, y1, x1 + 2, y1, color));
|
|
64
|
+
}
|
|
65
|
+
return commands;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = {
|
|
69
|
+
drawTrapezoid,
|
|
70
|
+
drawRhombus,
|
|
71
|
+
drawAxis,
|
|
72
|
+
drawDistance
|
|
73
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
const { registerTool, sendCommand } = require('../../core/server');
|
|
3
|
+
const advLessons = require('./advanced_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 art-tree tools on the MCP server.
|
|
9
|
+
* @param {McpServer} server
|
|
10
|
+
*/
|
|
11
|
+
function register(server) {
|
|
12
|
+
registerTool(server, 'art_tree_lesson_ratios',
|
|
13
|
+
'Lesson: Properties - Shape Ratios',
|
|
14
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#2196f3') },
|
|
15
|
+
async (p) => {
|
|
16
|
+
const commands = advLessons.lessonShapeRatios(p.canvasSize, p.color);
|
|
17
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
18
|
+
return { content: [{ type: 'text', text: `✓ Shape Ratios lesson completed` }] };
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
registerTool(server, 'art_tree_lesson_symmetry',
|
|
22
|
+
'Lesson: Properties - Symmetry & Axes',
|
|
23
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#9c27b0') },
|
|
24
|
+
async (p) => {
|
|
25
|
+
const commands = advLessons.lessonSymmetryAndAxis(p.canvasSize, p.color);
|
|
26
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
27
|
+
return { content: [{ type: 'text', text: `✓ Symmetry & Axes lesson completed` }] };
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
registerTool(server, 'art_tree_lesson_angles',
|
|
31
|
+
'Lesson: Properties - Angles',
|
|
32
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#ff5722') },
|
|
33
|
+
async (p) => {
|
|
34
|
+
const commands = advLessons.lessonAngles(p.canvasSize, p.color);
|
|
35
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
36
|
+
return { content: [{ type: 'text', text: `✓ Angles lesson completed` }] };
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
registerTool(server, 'art_tree_lesson_distances',
|
|
40
|
+
'Lesson: Properties - Distances',
|
|
41
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#00bcd4') },
|
|
42
|
+
async (p) => {
|
|
43
|
+
const commands = advLessons.lessonDistances(p.canvasSize, p.color);
|
|
44
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
45
|
+
return { content: [{ type: 'text', text: `✓ Distances lesson completed` }] };
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
registerTool(server, 'art_tree_lesson_relationships',
|
|
49
|
+
'Lesson: Properties - Shape Relationships',
|
|
50
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#607d8b') },
|
|
51
|
+
async (p) => {
|
|
52
|
+
const commands = advLessons.lessonShapeRelationships(p.canvasSize, p.color);
|
|
53
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
54
|
+
return { content: [{ type: 'text', text: `✓ Shape Relationships lesson completed` }] };
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
registerTool(server, 'art_tree_lesson_adv_catalog',
|
|
58
|
+
'Get the catalog of all available Advanced Art Tree lessons',
|
|
59
|
+
{},
|
|
60
|
+
() => {
|
|
61
|
+
const catalog = advLessons.getAdvancedLessonCatalog();
|
|
62
|
+
const text = catalog.map(l =>
|
|
63
|
+
` • ${l.id}: ${l.name} — ${l.description}`
|
|
64
|
+
).join('\n');
|
|
65
|
+
return { content: [{ type: 'text', text: `📚 Advanced Art Tree Lesson Catalog:\n\n${text}` }] };
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = { register };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const analysisShapes = require('./analysis_shapes');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lesson: Object Analysis (Capstone)
|
|
5
|
+
* A 10-step breakdown of a Teapot.
|
|
6
|
+
* Users can supply the step number (1 to 10) to see a specific layer of analysis.
|
|
7
|
+
*/
|
|
8
|
+
function lessonObjectAnalysis(canvasSize = 32, step = 1, color = '#2196f3') {
|
|
9
|
+
const cx = Math.floor(canvasSize / 2);
|
|
10
|
+
const cy = Math.floor(canvasSize / 2);
|
|
11
|
+
|
|
12
|
+
// Ensure step is between 1 and 10
|
|
13
|
+
const validStep = Math.max(1, Math.min(10, step));
|
|
14
|
+
|
|
15
|
+
return analysisShapes.drawTeapotAnalysis(cx, cy, canvasSize, validStep, color);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getAnalysisLessonCatalog() {
|
|
19
|
+
return [
|
|
20
|
+
{
|
|
21
|
+
id: 'analysis_step',
|
|
22
|
+
name: '10-Step Object Analysis',
|
|
23
|
+
description: 'Break down a Teapot from Silhouette (1) to Lighting (10). Supply step=1..10',
|
|
24
|
+
fn: lessonObjectAnalysis
|
|
25
|
+
}
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
lessonObjectAnalysis,
|
|
31
|
+
getAnalysisLessonCatalog
|
|
32
|
+
};
|