@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,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 };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const hiddenShapes = require('./hidden_shapes');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lesson: Hidden Box (6-sided box)
|
|
5
|
+
* You see 3 faces, but you must understand all 6.
|
|
6
|
+
*/
|
|
7
|
+
function lessonHiddenBox(canvasSize = 32, visibleColor = '#2196f3', hiddenColor = '#e0e0e0') {
|
|
8
|
+
const cx = Math.floor(canvasSize / 2);
|
|
9
|
+
const cy = Math.floor(canvasSize / 2);
|
|
10
|
+
const w = Math.floor(canvasSize * 0.4);
|
|
11
|
+
const h = Math.floor(canvasSize * 0.4);
|
|
12
|
+
const d = Math.floor(canvasSize * 0.4);
|
|
13
|
+
|
|
14
|
+
return hiddenShapes.drawXRayBox(cx, cy, w, h, d, visibleColor, hiddenColor);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Lesson: Hidden Head (Human head and skull)
|
|
19
|
+
* You don't see the whole skull, but must understand the structure behind it.
|
|
20
|
+
*/
|
|
21
|
+
function lessonHiddenHead(canvasSize = 32, visibleColor = '#ff5722', hiddenColor = '#e0e0e0') {
|
|
22
|
+
const cx = Math.floor(canvasSize / 2);
|
|
23
|
+
const cy = Math.floor(canvasSize * 0.4);
|
|
24
|
+
const r = Math.floor(canvasSize * 0.25);
|
|
25
|
+
|
|
26
|
+
return hiddenShapes.drawXRayHead(cx, cy, r, visibleColor, hiddenColor);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Lesson: Hidden Cup (Cup and inner bottom)
|
|
31
|
+
* You don't see the full inside bottom, but must understand how the wall goes down.
|
|
32
|
+
*/
|
|
33
|
+
function lessonHiddenCup(canvasSize = 32, visibleColor = '#4caf50', hiddenColor = '#e0e0e0') {
|
|
34
|
+
const cx = Math.floor(canvasSize / 2);
|
|
35
|
+
const cy = Math.floor(canvasSize / 2);
|
|
36
|
+
const r = Math.floor(canvasSize * 0.25);
|
|
37
|
+
const h = Math.floor(canvasSize * 0.5);
|
|
38
|
+
|
|
39
|
+
return hiddenShapes.drawXRayCup(cx, cy, r, h, visibleColor, hiddenColor);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getHiddenLessonCatalog() {
|
|
43
|
+
return [
|
|
44
|
+
{ id: 'hidden_box', name: 'Hidden Box', description: 'See all 6 faces of a box, not just 3', fn: lessonHiddenBox },
|
|
45
|
+
{ id: 'hidden_head', name: 'Hidden Head', description: 'Understand the cranium sphere behind the face', fn: lessonHiddenHead },
|
|
46
|
+
{ id: 'hidden_cup', name: 'Hidden Cup', description: 'Understand the inner depth and bottom of a cup', fn: lessonHiddenCup },
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = {
|
|
51
|
+
lessonHiddenBox,
|
|
52
|
+
lessonHiddenHead,
|
|
53
|
+
lessonHiddenCup,
|
|
54
|
+
getHiddenLessonCatalog
|
|
55
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
const shapes = require('./shapes');
|
|
2
|
+
const ellipseShapes = require('./ellipse_shapes');
|
|
3
|
+
|
|
4
|
+
function drawXRayBox(cx, cy, w, h, d, visibleColor, hiddenColor) {
|
|
5
|
+
const commands = [];
|
|
6
|
+
const dx = Math.floor(d * 0.7);
|
|
7
|
+
const dy = Math.floor(d * 0.5);
|
|
8
|
+
|
|
9
|
+
const fX = cx - Math.floor(w/2);
|
|
10
|
+
const fY = cy + Math.floor(h/2);
|
|
11
|
+
|
|
12
|
+
// Hidden edges (Back, Bottom, Left side)
|
|
13
|
+
// Back face
|
|
14
|
+
const bX = fX + dx;
|
|
15
|
+
const bY = fY - dy;
|
|
16
|
+
commands.push(shapes.drawLine(fX, fY, bX, bY, hiddenColor)); // Bottom left depth
|
|
17
|
+
commands.push(shapes.drawLine(bX, bY, bX + w, bY, hiddenColor)); // Back bottom
|
|
18
|
+
commands.push(shapes.drawLine(bX, bY, bX, bY - h, hiddenColor)); // Back left vertical
|
|
19
|
+
|
|
20
|
+
// Visible edges (Front, Top, Right side)
|
|
21
|
+
// Front face
|
|
22
|
+
commands.push(shapes.drawRectangle(fX, fY - h, w, h, visibleColor, false));
|
|
23
|
+
// Top face
|
|
24
|
+
commands.push(shapes.drawLine(fX, fY - h, bX, bY - h, visibleColor));
|
|
25
|
+
commands.push(shapes.drawLine(fX + w, fY - h, bX + w, bY - h, visibleColor));
|
|
26
|
+
commands.push(shapes.drawLine(bX, bY - h, bX + w, bY - h, visibleColor));
|
|
27
|
+
// Right side depth
|
|
28
|
+
commands.push(shapes.drawLine(fX + w, fY, bX + w, bY, visibleColor));
|
|
29
|
+
// Right back vertical
|
|
30
|
+
commands.push(shapes.drawLine(bX + w, bY, bX + w, bY - h, visibleColor));
|
|
31
|
+
|
|
32
|
+
return commands;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function drawXRayHead(cx, cy, r, visibleColor, hiddenColor) {
|
|
36
|
+
const commands = [];
|
|
37
|
+
|
|
38
|
+
// Hidden/Structural elements (Cranium sphere behind the face)
|
|
39
|
+
commands.push(shapes.drawCircle(cx, cy, r, hiddenColor, false));
|
|
40
|
+
// Cranium cross-contours (hidden structure)
|
|
41
|
+
commands.push(shapes.drawEllipse(cx, cy, r, Math.floor(r*0.3), hiddenColor, false)); // Equator
|
|
42
|
+
commands.push(shapes.drawEllipse(cx, cy, Math.floor(r*0.3), r, hiddenColor, false)); // Prime meridian
|
|
43
|
+
|
|
44
|
+
// Visible features (Face and Jaw)
|
|
45
|
+
const jawY = cy + Math.floor(r * 1.4);
|
|
46
|
+
const jawW = Math.floor(r * 0.6);
|
|
47
|
+
// Cheek lines dropping down
|
|
48
|
+
commands.push(shapes.drawLine(cx - r, cy, cx - jawW, jawY, visibleColor));
|
|
49
|
+
commands.push(shapes.drawLine(cx + r, cy, cx + jawW, jawY, visibleColor));
|
|
50
|
+
// Jaw line
|
|
51
|
+
commands.push(shapes.drawEllipse(cx, jawY, jawW, Math.floor(jawW * 0.4), visibleColor, false));
|
|
52
|
+
// Center facial axis
|
|
53
|
+
commands.push(shapes.drawLine(cx, cy - r, cx, jawY, visibleColor));
|
|
54
|
+
|
|
55
|
+
return commands;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function drawXRayCup(cx, cy, r, h, visibleColor, hiddenColor) {
|
|
59
|
+
const commands = [];
|
|
60
|
+
const topY = cy - Math.floor(h/2);
|
|
61
|
+
const botY = cy + Math.floor(h/2);
|
|
62
|
+
const ry = Math.floor(r * 0.3);
|
|
63
|
+
|
|
64
|
+
// Hidden elements (Inner bottom surface, back curve of outer base)
|
|
65
|
+
// Back curve of the outer base (hidden from viewer)
|
|
66
|
+
commands.push(shapes.drawEllipse(cx, botY, r, ry, hiddenColor, false));
|
|
67
|
+
|
|
68
|
+
// Inner bottom surface (showing the depth/thickness of the cup bottom)
|
|
69
|
+
const innerBotY = botY - Math.floor(h * 0.1);
|
|
70
|
+
const innerR = Math.floor(r * 0.85);
|
|
71
|
+
const innerRy = Math.floor(ry * 0.85);
|
|
72
|
+
commands.push(shapes.drawEllipse(cx, innerBotY, innerR, innerRy, hiddenColor, false));
|
|
73
|
+
|
|
74
|
+
// Visible elements (Outer walls, front curve of base, rim, inner wall)
|
|
75
|
+
// Walls
|
|
76
|
+
commands.push(shapes.drawLine(cx - r, topY, cx - r, botY, visibleColor));
|
|
77
|
+
commands.push(shapes.drawLine(cx + r, topY, cx + r, botY, visibleColor));
|
|
78
|
+
// Rim / Lip
|
|
79
|
+
commands.push(shapes.drawEllipse(cx, topY, r, ry, visibleColor, false));
|
|
80
|
+
commands.push(shapes.drawEllipse(cx, topY, innerR, innerRy, visibleColor, false));
|
|
81
|
+
|
|
82
|
+
// To draw just the front curve of the base in visible color, we approximate with a polyline
|
|
83
|
+
const frontCurve = [];
|
|
84
|
+
for (let i = 0; i <= 18; i++) {
|
|
85
|
+
const angle = (i / 18) * Math.PI; // 0 to 180 degrees (bottom half)
|
|
86
|
+
frontCurve.push({ x: Math.round(cx + r * Math.cos(angle)), y: Math.round(botY + ry * Math.sin(angle)) });
|
|
87
|
+
}
|
|
88
|
+
commands.push(shapes.drawPolyline(frontCurve, visibleColor));
|
|
89
|
+
|
|
90
|
+
return commands;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = {
|
|
94
|
+
drawXRayBox,
|
|
95
|
+
drawXRayHead,
|
|
96
|
+
drawXRayCup
|
|
97
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
const { registerTool, sendCommand } = require('../../core/server');
|
|
3
|
+
const hiddenLessons = require('./hidden_lessons');
|
|
4
|
+
|
|
5
|
+
const hexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/).describe('Hex color e.g. #ff0000');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Register all hidden shapes tools on the MCP server.
|
|
9
|
+
* @param {McpServer} server
|
|
10
|
+
*/
|
|
11
|
+
function register(server) {
|
|
12
|
+
registerTool(server, 'art_tree_lesson_hidden_box',
|
|
13
|
+
'Lesson: Hidden - Understand all 6 faces of a box (X-Ray vision)',
|
|
14
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), visibleColor: hexColor.default('#2196f3'), hiddenColor: hexColor.default('#e0e0e0') },
|
|
15
|
+
async (p) => {
|
|
16
|
+
const commands = hiddenLessons.lessonHiddenBox(p.canvasSize, p.visibleColor, p.hiddenColor);
|
|
17
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
18
|
+
return { content: [{ type: 'text', text: `✓ Hidden Box lesson completed` }] };
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
registerTool(server, 'art_tree_lesson_hidden_head',
|
|
22
|
+
'Lesson: Hidden - Understand the cranium sphere behind the face (X-Ray vision)',
|
|
23
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), visibleColor: hexColor.default('#ff5722'), hiddenColor: hexColor.default('#e0e0e0') },
|
|
24
|
+
async (p) => {
|
|
25
|
+
const commands = hiddenLessons.lessonHiddenHead(p.canvasSize, p.visibleColor, p.hiddenColor);
|
|
26
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
27
|
+
return { content: [{ type: 'text', text: `✓ Hidden Head lesson completed` }] };
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
registerTool(server, 'art_tree_lesson_hidden_cup',
|
|
31
|
+
'Lesson: Hidden - Understand the inner depth of a cup (X-Ray vision)',
|
|
32
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), visibleColor: hexColor.default('#4caf50'), hiddenColor: hexColor.default('#e0e0e0') },
|
|
33
|
+
async (p) => {
|
|
34
|
+
const commands = hiddenLessons.lessonHiddenCup(p.canvasSize, p.visibleColor, p.hiddenColor);
|
|
35
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
36
|
+
return { content: [{ type: 'text', text: `✓ Hidden Cup lesson completed` }] };
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
registerTool(server, 'art_tree_lesson_hidden_catalog',
|
|
40
|
+
'Get the catalog of all available Hidden Shapes lessons',
|
|
41
|
+
{},
|
|
42
|
+
() => {
|
|
43
|
+
const catalog = hiddenLessons.getHiddenLessonCatalog();
|
|
44
|
+
const text = catalog.map(l =>
|
|
45
|
+
` • ${l.id}: ${l.name} — ${l.description}`
|
|
46
|
+
).join('\n');
|
|
47
|
+
return { content: [{ type: 'text', text: `📚 Hidden Shapes Lessons:\n\n${text}` }] };
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = { register };
|
|
@@ -10,6 +10,22 @@
|
|
|
10
10
|
const shapes = require('./shapes');
|
|
11
11
|
const lessons = require('./lessons');
|
|
12
12
|
const tools = require('./tools');
|
|
13
|
+
const advTools = require('./advanced_tools');
|
|
14
|
+
const curveTools = require('./curve_tools');
|
|
15
|
+
const ellipseTools = require('./ellipse_tools');
|
|
16
|
+
const tools3d = require('./3d_tools');
|
|
17
|
+
const structureTools = require('./structure_tools');
|
|
18
|
+
const csTools = require('./cross_section_tools');
|
|
19
|
+
const transformTools = require('./transform_tools');
|
|
20
|
+
const surfaceTools = require('./surface_tools');
|
|
21
|
+
const perspTools = require('./perspective_tools');
|
|
22
|
+
const hiddenTools = require('./hidden_tools');
|
|
23
|
+
const lightTools = require('./light_tools');
|
|
24
|
+
const materialTools = require('./material_tools');
|
|
25
|
+
const analysisTools = require('./analysis_tools');
|
|
26
|
+
const layerTools = require('./layer_tools');
|
|
27
|
+
const skyTools = require('./sky_tools');
|
|
28
|
+
const vocabTools = require('./vocab_tools');
|
|
13
29
|
|
|
14
30
|
/**
|
|
15
31
|
* Register all Art Tree MCP tools on the server
|
|
@@ -17,6 +33,22 @@ const tools = require('./tools');
|
|
|
17
33
|
*/
|
|
18
34
|
function registerAll(server) {
|
|
19
35
|
tools.register(server);
|
|
36
|
+
advTools.register(server);
|
|
37
|
+
curveTools.register(server);
|
|
38
|
+
ellipseTools.register(server);
|
|
39
|
+
tools3d.register(server);
|
|
40
|
+
structureTools.register(server);
|
|
41
|
+
csTools.register(server);
|
|
42
|
+
transformTools.register(server);
|
|
43
|
+
surfaceTools.register(server);
|
|
44
|
+
perspTools.register(server);
|
|
45
|
+
hiddenTools.register(server);
|
|
46
|
+
lightTools.register(server);
|
|
47
|
+
materialTools.register(server);
|
|
48
|
+
analysisTools.register(server);
|
|
49
|
+
layerTools.register(server);
|
|
50
|
+
skyTools.register(server);
|
|
51
|
+
vocabTools.register(server);
|
|
20
52
|
}
|
|
21
53
|
|
|
22
54
|
module.exports = {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const layerShapes = require('./layer_shapes');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lesson: Layer Structure (Tư duy Layer)
|
|
5
|
+
* A 7-step progression showing what belongs on each layer.
|
|
6
|
+
* Users can supply the layer index (1 to 7) to see a specific layer's content.
|
|
7
|
+
*/
|
|
8
|
+
function lessonLayerStep(canvasSize = 32, layerIndex = 1) {
|
|
9
|
+
const cx = Math.floor(canvasSize / 2);
|
|
10
|
+
const cy = Math.floor(canvasSize / 2);
|
|
11
|
+
|
|
12
|
+
// Ensure layerIndex is between 1 and 7
|
|
13
|
+
const validLayer = Math.max(1, Math.min(7, layerIndex));
|
|
14
|
+
|
|
15
|
+
return layerShapes.drawLayeredCup(cx, cy, canvasSize, validLayer);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getLayerLessonCatalog() {
|
|
19
|
+
return [
|
|
20
|
+
{
|
|
21
|
+
id: 'layer_step',
|
|
22
|
+
name: '7-Layer Painting Structure',
|
|
23
|
+
description: 'View the contents of a specific layer of a Cup (1=Ref, 2=Sketch, 3=Construction, 4=Lineart, 5=Color, 6=Shadow, 7=Light)',
|
|
24
|
+
fn: lessonLayerStep
|
|
25
|
+
}
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
lessonLayerStep,
|
|
31
|
+
getLayerLessonCatalog
|
|
32
|
+
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
const shapes = require('./shapes');
|
|
2
|
+
|
|
3
|
+
// Helper to draw a specific layer of a Cup
|
|
4
|
+
function drawLayeredCup(cx, cy, scale, layerIndex) {
|
|
5
|
+
const commands = [];
|
|
6
|
+
const w = Math.floor(scale * 0.4);
|
|
7
|
+
const h = Math.floor(scale * 0.6);
|
|
8
|
+
const r = Math.floor(w / 2);
|
|
9
|
+
const topRy = Math.floor(r * 0.3);
|
|
10
|
+
const botRy = Math.floor(r * 0.25);
|
|
11
|
+
const topY = cy - Math.floor(h/2);
|
|
12
|
+
const botY = cy + Math.floor(h/2);
|
|
13
|
+
|
|
14
|
+
switch (layerIndex) {
|
|
15
|
+
case 1: // Reference
|
|
16
|
+
// Draw a "photo frame" indicating where a reference image goes
|
|
17
|
+
const refX = cx - Math.floor(scale*0.4);
|
|
18
|
+
const refY = cy - Math.floor(scale*0.4);
|
|
19
|
+
const refW = Math.floor(scale*0.3);
|
|
20
|
+
const refH = Math.floor(scale*0.3);
|
|
21
|
+
commands.push(shapes.drawRectangle(refX, refY, refW, refH, '#e0e0e0', true));
|
|
22
|
+
commands.push(shapes.drawRectangle(refX, refY, refW, refH, '#9e9e9e', false));
|
|
23
|
+
commands.push(shapes.drawLine(refX, refY, refX+refW, refY+refH, '#9e9e9e'));
|
|
24
|
+
commands.push(shapes.drawLine(refX+refW, refY, refX, refY+refH, '#9e9e9e'));
|
|
25
|
+
break;
|
|
26
|
+
|
|
27
|
+
case 2: // Sketch
|
|
28
|
+
// Loose, messy lines (Gesture, proportion bounds)
|
|
29
|
+
const sketchC = '#90caf9'; // Light blue sketch pencil
|
|
30
|
+
commands.push(shapes.drawRectangle(cx - r - 5, topY - topRy - 5, w + 10, h + botRy + 10, sketchC, false));
|
|
31
|
+
// Messy circles/ovals for top and bottom
|
|
32
|
+
commands.push(shapes.drawEllipse(cx, topY, Math.floor(r*1.1), Math.floor(topRy*1.2), sketchC, false));
|
|
33
|
+
commands.push(shapes.drawEllipse(cx+2, topY-2, Math.floor(r*0.9), Math.floor(topRy*0.8), sketchC, false));
|
|
34
|
+
// Messy walls
|
|
35
|
+
commands.push(shapes.drawLine(cx - r - 2, topY, cx - r + 3, botY, sketchC));
|
|
36
|
+
commands.push(shapes.drawLine(cx + r + 2, topY, cx + r - 3, botY, sketchC));
|
|
37
|
+
break;
|
|
38
|
+
|
|
39
|
+
case 3: // Construction
|
|
40
|
+
// Precise geometric primitives and axes
|
|
41
|
+
const axisC = '#f44336';
|
|
42
|
+
const constC = '#4caf50';
|
|
43
|
+
// Axis
|
|
44
|
+
commands.push(shapes.drawLine(cx, cy - h, cx, cy + h, axisC));
|
|
45
|
+
// Top ellipse
|
|
46
|
+
commands.push(shapes.drawEllipse(cx, topY, r, topRy, constC, false));
|
|
47
|
+
// Bottom ellipse (full, showing through)
|
|
48
|
+
commands.push(shapes.drawEllipse(cx, botY, r, botRy, constC, false));
|
|
49
|
+
// Straight walls connecting tangents
|
|
50
|
+
commands.push(shapes.drawLine(cx - r, topY, cx - r, botY, constC));
|
|
51
|
+
commands.push(shapes.drawLine(cx + r, topY, cx + r, botY, constC));
|
|
52
|
+
break;
|
|
53
|
+
|
|
54
|
+
case 4: // Lineart
|
|
55
|
+
// Clean, solid outer and inner contours
|
|
56
|
+
const lineC = '#000000';
|
|
57
|
+
// Walls
|
|
58
|
+
commands.push(shapes.drawLine(cx - r, topY, cx - r, botY, lineC));
|
|
59
|
+
commands.push(shapes.drawLine(cx + r, topY, cx + r, botY, lineC));
|
|
60
|
+
// Top rim
|
|
61
|
+
commands.push(shapes.drawEllipse(cx, topY, r, topRy, lineC, false));
|
|
62
|
+
// Inner lip
|
|
63
|
+
commands.push(shapes.drawEllipse(cx, topY, Math.floor(r*0.85), Math.floor(topRy*0.85), lineC, false));
|
|
64
|
+
// Front bottom curve (simulate by drawing half an ellipse manually, but we don't have arc tool)
|
|
65
|
+
// We use polyline for the front bottom curve to not show the hidden back
|
|
66
|
+
const frontCurve = [];
|
|
67
|
+
for (let i = 0; i <= 10; i++) {
|
|
68
|
+
const a = (i / 10) * Math.PI;
|
|
69
|
+
frontCurve.push({ x: Math.round(cx + r * Math.cos(a)), y: Math.round(botY + botRy * Math.sin(a)) });
|
|
70
|
+
}
|
|
71
|
+
commands.push(shapes.drawPolyline(frontCurve, lineC));
|
|
72
|
+
break;
|
|
73
|
+
|
|
74
|
+
case 5: // Base Color
|
|
75
|
+
// Flat solid colors for the cup body and interior
|
|
76
|
+
const bodyC = '#ff9800'; // Orange cup
|
|
77
|
+
const innerC = '#795548'; // Coffee inside
|
|
78
|
+
// Body fill (Using wireframe/thick lines to fake fill if needed, or assume SVG renders it)
|
|
79
|
+
// Actually, we can use drawEllipse and rectangle
|
|
80
|
+
commands.push(shapes.drawRectangle(cx - r, topY, w, h, bodyC, true));
|
|
81
|
+
commands.push(shapes.drawEllipse(cx, botY, r, botRy, bodyC, true));
|
|
82
|
+
// Fill the top area
|
|
83
|
+
commands.push(shapes.drawEllipse(cx, topY, r, topRy, bodyC, true));
|
|
84
|
+
// Coffee inside
|
|
85
|
+
commands.push(shapes.drawEllipse(cx, topY, Math.floor(r*0.85), Math.floor(topRy*0.85), innerC, true));
|
|
86
|
+
break;
|
|
87
|
+
|
|
88
|
+
case 6: // Shadow
|
|
89
|
+
// Core shadow and Cast shadow (Usually drawn with Multiply blend mode, but here we just draw dark shapes)
|
|
90
|
+
const shadowC = '#424242';
|
|
91
|
+
// Cast shadow on floor
|
|
92
|
+
commands.push(shapes.drawEllipse(cx - Math.floor(w*0.8), botY + Math.floor(botRy*0.5), Math.floor(w*1.2), Math.floor(botRy*1.2), shadowC, true));
|
|
93
|
+
// Core shadow on the left side of the cup body
|
|
94
|
+
commands.push(shapes.drawRectangle(cx - r, topY, Math.floor(r*0.6), h, '#3e2723', true));
|
|
95
|
+
break;
|
|
96
|
+
|
|
97
|
+
case 7: // Light
|
|
98
|
+
// Highlights and Rim light (Usually drawn with Screen/Add blend mode, here just bright white/yellow)
|
|
99
|
+
const lightC = '#ffffff';
|
|
100
|
+
// Rim light on the right edge
|
|
101
|
+
commands.push(shapes.drawLine(cx + r - 2, topY, cx + r - 2, botY, lightC));
|
|
102
|
+
// Strong sharp highlight on the body
|
|
103
|
+
commands.push(shapes.drawEllipse(cx + Math.floor(r*0.5), cy, Math.floor(r*0.1), Math.floor(h*0.3), lightC, true));
|
|
104
|
+
// Highlight on the rim
|
|
105
|
+
commands.push(shapes.drawEllipse(cx + Math.floor(r*0.7), topY - Math.floor(topRy*0.5), Math.floor(r*0.2), 2, lightC, true));
|
|
106
|
+
break;
|
|
107
|
+
|
|
108
|
+
default:
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return commands;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = {
|
|
116
|
+
drawLayeredCup
|
|
117
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
const { registerTool, sendCommand } = require('../../core/server');
|
|
3
|
+
const layerLessons = require('./layer_lessons');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Register all layer tools on the MCP server.
|
|
7
|
+
* @param {McpServer} server
|
|
8
|
+
*/
|
|
9
|
+
function register(server) {
|
|
10
|
+
registerTool(server, 'art_tree_lesson_layer_step',
|
|
11
|
+
'Lesson: Layer Structure - See exactly what belongs on each layer of a Cup (1 to 7).',
|
|
12
|
+
{
|
|
13
|
+
canvasSize: z.number().int().min(16).max(64).default(32),
|
|
14
|
+
layer: z.number().int().min(1).max(7).describe('The layer index (1=Ref, 2=Sketch, 3=Construction, 4=Lineart, 5=Color, 6=Shadow, 7=Light)')
|
|
15
|
+
},
|
|
16
|
+
async (p) => {
|
|
17
|
+
const commands = layerLessons.lessonLayerStep(p.canvasSize, p.layer);
|
|
18
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
19
|
+
const layers = [
|
|
20
|
+
"1. Reference (Ảnh tham khảo)",
|
|
21
|
+
"2. Sketch (Phác thảo)",
|
|
22
|
+
"3. Construction (Dựng hình)",
|
|
23
|
+
"4. Lineart (Nét chính)",
|
|
24
|
+
"5. Base Color (Màu cơ bản)",
|
|
25
|
+
"6. Shadow (Bóng tối)",
|
|
26
|
+
"7. Light (Ánh sáng)"
|
|
27
|
+
];
|
|
28
|
+
return { content: [{ type: 'text', text: `✓ Layer ${p.layer} visualization completed: ${layers[p.layer - 1]}` }] };
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
registerTool(server, 'art_tree_lesson_layer_catalog',
|
|
32
|
+
'Get the catalog of all available Layer Structure lessons',
|
|
33
|
+
{},
|
|
34
|
+
() => {
|
|
35
|
+
const catalog = layerLessons.getLayerLessonCatalog();
|
|
36
|
+
const text = catalog.map(l =>
|
|
37
|
+
` • ${l.id}: ${l.name} — ${l.description}`
|
|
38
|
+
).join('\n');
|
|
39
|
+
return { content: [{ type: 'text', text: `📚 Layer Lessons:\n\n${text}` }] };
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = { register };
|