@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,102 @@
|
|
|
1
|
+
const shapes = require('./shapes');
|
|
2
|
+
const transformShapes = require('./transform_shapes');
|
|
3
|
+
const structureLessons = require('./structure_lessons');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Lesson: Rotate (Rotation)
|
|
7
|
+
* Analyzing how objects change when rotated around X, Y, Z axes.
|
|
8
|
+
* We can reuse the Axis Orientation lesson as it perfectly demonstrates rotation.
|
|
9
|
+
*/
|
|
10
|
+
function lessonRotate(canvasSize = 32, color = '#ff5722') {
|
|
11
|
+
// Reusing the axis orientation lesson which draws cylinders rotated along X, Y, Z
|
|
12
|
+
return structureLessons.lessonAxisOrientation(canvasSize, color);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Lesson: Cut (Slice a form)
|
|
17
|
+
* Sphere -> Truncated Sphere (exposing internal cross-section)
|
|
18
|
+
*/
|
|
19
|
+
function lessonCut(canvasSize = 32, color = '#8bc34a') {
|
|
20
|
+
const commands = [];
|
|
21
|
+
const cx = Math.floor(canvasSize / 2);
|
|
22
|
+
const cy = Math.floor(canvasSize / 2);
|
|
23
|
+
const r = Math.floor(canvasSize * 0.35);
|
|
24
|
+
|
|
25
|
+
// Original sphere (ghost)
|
|
26
|
+
commands.push(shapes.drawCircle(cx, cy, r, '#bdbdbd', false));
|
|
27
|
+
|
|
28
|
+
// Cut sphere
|
|
29
|
+
commands.push(...transformShapes.drawTruncatedSphere(cx, cy, r, color));
|
|
30
|
+
|
|
31
|
+
return commands;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Lesson: Hollow (Carve interior)
|
|
36
|
+
* Cylinder -> Pipe/Hole
|
|
37
|
+
*/
|
|
38
|
+
function lessonHollow(canvasSize = 32, color = '#3f51b5') {
|
|
39
|
+
const commands = [];
|
|
40
|
+
const cx = Math.floor(canvasSize / 2);
|
|
41
|
+
const cy = Math.floor(canvasSize / 2);
|
|
42
|
+
const outerR = Math.floor(canvasSize * 0.35);
|
|
43
|
+
const innerR = Math.floor(canvasSize * 0.25);
|
|
44
|
+
const h = Math.floor(canvasSize * 0.6);
|
|
45
|
+
|
|
46
|
+
// Hollow cylinder
|
|
47
|
+
commands.push(...transformShapes.drawHollowCylinder(cx, cy, outerR, innerR, h, color));
|
|
48
|
+
|
|
49
|
+
return commands;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Lesson: Combine (Merge forms)
|
|
54
|
+
* Box + Cylinder (e.g., a wheel on an axle, or a tower on a base)
|
|
55
|
+
*/
|
|
56
|
+
function lessonCombine(canvasSize = 32, color = '#607d8b') {
|
|
57
|
+
const commands = [];
|
|
58
|
+
const cx = Math.floor(canvasSize / 2);
|
|
59
|
+
const cy = Math.floor(canvasSize / 2);
|
|
60
|
+
|
|
61
|
+
// 1. Base Box
|
|
62
|
+
const w = Math.floor(canvasSize * 0.5);
|
|
63
|
+
const h = Math.floor(canvasSize * 0.2);
|
|
64
|
+
const d = Math.floor(canvasSize * 0.3);
|
|
65
|
+
const dx = Math.floor(d * 0.7);
|
|
66
|
+
const dy = Math.floor(d * 0.5);
|
|
67
|
+
|
|
68
|
+
const fX = cx - Math.floor(w/2);
|
|
69
|
+
const fY = cy + Math.floor(canvasSize * 0.2);
|
|
70
|
+
|
|
71
|
+
// Front face
|
|
72
|
+
commands.push(shapes.drawRectangle(fX, fY, w, h, color, false));
|
|
73
|
+
// Top face (oblique)
|
|
74
|
+
commands.push(shapes.drawLine(fX, fY, fX + dx, fY - dy, color));
|
|
75
|
+
commands.push(shapes.drawLine(fX + w, fY, fX + w + dx, fY - dy, color));
|
|
76
|
+
commands.push(shapes.drawLine(fX + dx, fY - dy, fX + w + dx, fY - dy, color));
|
|
77
|
+
|
|
78
|
+
// 2. Intersecting Cylinder (Tower on top of box)
|
|
79
|
+
const cylR = Math.floor(canvasSize * 0.15);
|
|
80
|
+
const cylH = Math.floor(canvasSize * 0.4);
|
|
81
|
+
const cylX = cx + Math.floor(dx/2);
|
|
82
|
+
const cylY = fY - Math.floor(dy/2); // Center of the top face
|
|
83
|
+
|
|
84
|
+
// Draw cylinder standing on the box
|
|
85
|
+
const topY = cylY - cylH;
|
|
86
|
+
// Top ellipse
|
|
87
|
+
commands.push(shapes.drawEllipse(cylX, topY, cylR, Math.floor(cylR * 0.3), '#ff9800', false));
|
|
88
|
+
// Base ellipse (intersecting the box top face)
|
|
89
|
+
commands.push(shapes.drawEllipse(cylX, cylY, cylR, Math.floor(cylR * 0.3), '#ff9800', false));
|
|
90
|
+
// Vertical edges
|
|
91
|
+
commands.push(shapes.drawLine(cylX - cylR, topY, cylX - cylR, cylY, '#ff9800'));
|
|
92
|
+
commands.push(shapes.drawLine(cylX + cylR, topY, cylX + cylR, cylY, '#ff9800'));
|
|
93
|
+
|
|
94
|
+
return commands;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = {
|
|
98
|
+
lessonRotate,
|
|
99
|
+
lessonCut,
|
|
100
|
+
lessonHollow,
|
|
101
|
+
lessonCombine
|
|
102
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const shapes = require('./shapes');
|
|
2
|
+
const ellipseShapes = require('./ellipse_shapes');
|
|
3
|
+
|
|
4
|
+
function drawEgg(cx, cy, r, color) {
|
|
5
|
+
const commands = [];
|
|
6
|
+
// Approximating an egg by drawing two halves (top stretched, bottom semi-circle)
|
|
7
|
+
// We'll use multiple ellipses overlapping, or a polygon approximation
|
|
8
|
+
// Let's use a simpler polygon approximation for the egg contour
|
|
9
|
+
const points = [];
|
|
10
|
+
for (let i = 0; i <= 36; i++) {
|
|
11
|
+
const t = (i / 36) * 2 * Math.PI;
|
|
12
|
+
const px = r * Math.cos(t);
|
|
13
|
+
// Stretch the top half
|
|
14
|
+
const stretch = Math.sin(t) < 0 ? 1.5 : 1.0;
|
|
15
|
+
const py = r * Math.sin(t) * stretch;
|
|
16
|
+
points.push({ x: Math.round(cx + px), y: Math.round(cy + py) });
|
|
17
|
+
}
|
|
18
|
+
commands.push(shapes.drawPolyline(points, color));
|
|
19
|
+
|
|
20
|
+
// Add cross contour to show 3D volume
|
|
21
|
+
commands.push(shapes.drawEllipse(cx, cy + Math.floor(r * 0.2), r, Math.floor(r * 0.3), '#03a9f4', false));
|
|
22
|
+
return commands;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function drawBentTube(cx, cy, r, length, color) {
|
|
26
|
+
const commands = [];
|
|
27
|
+
// Draw a curved tube using a semicircular path
|
|
28
|
+
const pathR = length; // radius of the bend path
|
|
29
|
+
// We place ellipses along the arc
|
|
30
|
+
for (let i = 0; i <= 6; i++) {
|
|
31
|
+
const angle = (i / 6) * Math.PI; // 0 to 180 degrees
|
|
32
|
+
const ex = Math.round(cx + pathR * Math.cos(angle));
|
|
33
|
+
const ey = Math.round(cy - pathR * Math.sin(angle));
|
|
34
|
+
// Rotate the ellipse to be perpendicular to the path
|
|
35
|
+
commands.push(...ellipseShapes.drawRotatedEllipse(ex, ey, Math.floor(r * 0.3), r, angle, color));
|
|
36
|
+
}
|
|
37
|
+
// Connect inner and outer edges (approximate arcs)
|
|
38
|
+
const innerPts = [], outerPts = [];
|
|
39
|
+
for (let i = 0; i <= 18; i++) {
|
|
40
|
+
const a = (i / 18) * Math.PI;
|
|
41
|
+
innerPts.push({ x: Math.round(cx + (pathR - r) * Math.cos(a)), y: Math.round(cy - (pathR - r) * Math.sin(a)) });
|
|
42
|
+
outerPts.push({ x: Math.round(cx + (pathR + r) * Math.cos(a)), y: Math.round(cy - (pathR + r) * Math.sin(a)) });
|
|
43
|
+
}
|
|
44
|
+
commands.push(shapes.drawPolyline(innerPts, color));
|
|
45
|
+
commands.push(shapes.drawPolyline(outerPts, color));
|
|
46
|
+
|
|
47
|
+
return commands;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function drawTruncatedSphere(cx, cy, r, color) {
|
|
51
|
+
const commands = [];
|
|
52
|
+
// Sphere outline (partial)
|
|
53
|
+
const points = [];
|
|
54
|
+
const cutAngle = Math.PI / 4; // Top section cut off
|
|
55
|
+
for (let i = 0; i <= 36; i++) {
|
|
56
|
+
const t = (i / 36) * 2 * Math.PI;
|
|
57
|
+
// Skip the top part
|
|
58
|
+
if (t > Math.PI + cutAngle && t < 2 * Math.PI - cutAngle) continue;
|
|
59
|
+
points.push({ x: Math.round(cx + r * Math.cos(t)), y: Math.round(cy + r * Math.sin(t)) });
|
|
60
|
+
}
|
|
61
|
+
commands.push(shapes.drawPolyline(points, color));
|
|
62
|
+
|
|
63
|
+
// Cut surface (ellipse)
|
|
64
|
+
const cutY = Math.round(cy + r * Math.sin(Math.PI + cutAngle));
|
|
65
|
+
const cutRx = Math.round(r * Math.cos(cutAngle));
|
|
66
|
+
const cutRy = Math.floor(cutRx * 0.3);
|
|
67
|
+
commands.push(shapes.drawEllipse(cx, cutY, cutRx, cutRy, '#ff5722', false)); // Highlight cut surface
|
|
68
|
+
|
|
69
|
+
// Cross contour to reinforce sphere shape
|
|
70
|
+
commands.push(shapes.drawEllipse(cx, cy + Math.floor(r*0.2), r, Math.floor(r*0.3), '#03a9f4', false));
|
|
71
|
+
|
|
72
|
+
return commands;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function drawHollowCylinder(cx, cy, outerR, innerR, h, color) {
|
|
76
|
+
const commands = [];
|
|
77
|
+
const topY = cy - Math.floor(h/2);
|
|
78
|
+
const botY = cy + Math.floor(h/2);
|
|
79
|
+
|
|
80
|
+
// Outer bottom
|
|
81
|
+
commands.push(shapes.drawEllipse(cx, botY, outerR, Math.floor(outerR * 0.3), color, false));
|
|
82
|
+
|
|
83
|
+
// Outer top
|
|
84
|
+
commands.push(shapes.drawEllipse(cx, topY, outerR, Math.floor(outerR * 0.3), color, false));
|
|
85
|
+
// Inner top (the hole)
|
|
86
|
+
commands.push(shapes.drawEllipse(cx, topY, innerR, Math.floor(innerR * 0.3), '#e91e63', false));
|
|
87
|
+
|
|
88
|
+
// Inner bottom (visible through hole - rough approximation)
|
|
89
|
+
// We just draw the back curve of the inner bottom ellipse
|
|
90
|
+
// For simplicity, we draw the full ellipse but lighter
|
|
91
|
+
commands.push(shapes.drawEllipse(cx, botY, innerR, Math.floor(innerR * 0.3), '#bdbdbd', false));
|
|
92
|
+
|
|
93
|
+
// Vertical edges
|
|
94
|
+
commands.push(shapes.drawLine(cx - outerR, topY, cx - outerR, botY, color));
|
|
95
|
+
commands.push(shapes.drawLine(cx + outerR, topY, cx + outerR, botY, color));
|
|
96
|
+
|
|
97
|
+
// Inner edges (depth)
|
|
98
|
+
commands.push(shapes.drawLine(cx - innerR, topY, cx - innerR, botY, '#9e9e9e'));
|
|
99
|
+
commands.push(shapes.drawLine(cx + innerR, topY, cx + innerR, botY, '#9e9e9e'));
|
|
100
|
+
|
|
101
|
+
return commands;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = {
|
|
105
|
+
drawEgg,
|
|
106
|
+
drawBentTube,
|
|
107
|
+
drawTruncatedSphere,
|
|
108
|
+
drawHollowCylinder
|
|
109
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
const { registerTool, sendCommand } = require('../../core/server');
|
|
3
|
+
const lessons1 = require('./transform_lessons_1');
|
|
4
|
+
const lessons2 = require('./transform_lessons_2');
|
|
5
|
+
|
|
6
|
+
const hexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/).describe('Hex color e.g. #ff0000');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Register all shape transformation tools on the MCP server.
|
|
10
|
+
* @param {McpServer} server
|
|
11
|
+
*/
|
|
12
|
+
function register(server) {
|
|
13
|
+
// --- Scale & Deform ---
|
|
14
|
+
registerTool(server, 'art_tree_lesson_transform_stretch',
|
|
15
|
+
'Lesson: Transform - Stretch (Elongate a sphere or box)',
|
|
16
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#2196f3') },
|
|
17
|
+
async (p) => {
|
|
18
|
+
const commands = lessons1.lessonStretch(p.canvasSize, p.color);
|
|
19
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
20
|
+
return { content: [{ type: 'text', text: `✓ Stretch transformation lesson completed` }] };
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
registerTool(server, 'art_tree_lesson_transform_squash',
|
|
24
|
+
'Lesson: Transform - Squash (Compress a sphere or box)',
|
|
25
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#ff9800') },
|
|
26
|
+
async (p) => {
|
|
27
|
+
const commands = lessons1.lessonSquash(p.canvasSize, p.color);
|
|
28
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
29
|
+
return { content: [{ type: 'text', text: `✓ Squash transformation lesson completed` }] };
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
registerTool(server, 'art_tree_lesson_transform_taper',
|
|
33
|
+
'Lesson: Transform - Taper/Swell (Cylinder to Bottle)',
|
|
34
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#00bcd4') },
|
|
35
|
+
async (p) => {
|
|
36
|
+
const commands = lessons1.lessonTaperSwell(p.canvasSize, p.color);
|
|
37
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
38
|
+
return { content: [{ type: 'text', text: `✓ Taper/Swell transformation lesson completed` }] };
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
registerTool(server, 'art_tree_lesson_transform_bend',
|
|
42
|
+
'Lesson: Transform - Bend (Cylinder to curved tube)',
|
|
43
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#e91e63') },
|
|
44
|
+
async (p) => {
|
|
45
|
+
const commands = lessons1.lessonBend(p.canvasSize, p.color);
|
|
46
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
47
|
+
return { content: [{ type: 'text', text: `✓ Bend transformation lesson completed` }] };
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// --- Boolean & Orientation ---
|
|
51
|
+
registerTool(server, 'art_tree_lesson_transform_rotate',
|
|
52
|
+
'Lesson: Transform - Rotate (Analyze rotation around X, Y, Z)',
|
|
53
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#ff5722') },
|
|
54
|
+
async (p) => {
|
|
55
|
+
const commands = lessons2.lessonRotate(p.canvasSize, p.color);
|
|
56
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
57
|
+
return { content: [{ type: 'text', text: `✓ Rotate transformation lesson completed` }] };
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
registerTool(server, 'art_tree_lesson_transform_cut',
|
|
61
|
+
'Lesson: Transform - Cut (Truncate a sphere)',
|
|
62
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#8bc34a') },
|
|
63
|
+
async (p) => {
|
|
64
|
+
const commands = lessons2.lessonCut(p.canvasSize, p.color);
|
|
65
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
66
|
+
return { content: [{ type: 'text', text: `✓ Cut transformation lesson completed` }] };
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
registerTool(server, 'art_tree_lesson_transform_hollow',
|
|
70
|
+
'Lesson: Transform - Hollow (Hollow out a cylinder)',
|
|
71
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#3f51b5') },
|
|
72
|
+
async (p) => {
|
|
73
|
+
const commands = lessons2.lessonHollow(p.canvasSize, p.color);
|
|
74
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
75
|
+
return { content: [{ type: 'text', text: `✓ Hollow transformation lesson completed` }] };
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
registerTool(server, 'art_tree_lesson_transform_combine',
|
|
79
|
+
'Lesson: Transform - Combine (Intersect a box and a cylinder)',
|
|
80
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#607d8b') },
|
|
81
|
+
async (p) => {
|
|
82
|
+
const commands = lessons2.lessonCombine(p.canvasSize, p.color);
|
|
83
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
84
|
+
return { content: [{ type: 'text', text: `✓ Combine transformation lesson completed` }] };
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
registerTool(server, 'art_tree_lesson_transform_catalog',
|
|
88
|
+
'Get the catalog of all available Transformation lessons',
|
|
89
|
+
{},
|
|
90
|
+
() => {
|
|
91
|
+
const text = `📚 Transformation Lessons:
|
|
92
|
+
• transform_stretch: Stretch/Elongate (Sphere -> Egg)
|
|
93
|
+
• transform_squash: Squash/Compress (Sphere -> Flat disc)
|
|
94
|
+
• transform_taper: Taper/Swell (Cylinder -> Bottle)
|
|
95
|
+
• transform_bend: Bend (Cylinder -> Bent tube)
|
|
96
|
+
• transform_rotate: Rotate (3D rotation analysis)
|
|
97
|
+
• transform_cut: Cut/Truncate (Slice a form)
|
|
98
|
+
• transform_hollow: Hollow out (Carve interior)
|
|
99
|
+
• transform_combine: Combine (Merge forms)`;
|
|
100
|
+
return { content: [{ type: 'text', text }] };
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = { register };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const vocabShapes = require('./vocab_shapes');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lesson: Visual Vocabulary - House Breakdown
|
|
5
|
+
* A 5-step process of recalling and drawing parts of an object.
|
|
6
|
+
* Users can supply the step number (1 to 5) to see the progressive breakdown.
|
|
7
|
+
*/
|
|
8
|
+
function lessonVocabHouse(canvasSize = 64, step = 1) {
|
|
9
|
+
const cx = Math.floor(canvasSize / 2);
|
|
10
|
+
const cy = Math.floor(canvasSize * 0.45);
|
|
11
|
+
|
|
12
|
+
// Ensure step is between 1 and 5
|
|
13
|
+
const validStep = Math.max(1, Math.min(5, step));
|
|
14
|
+
|
|
15
|
+
return vocabShapes.drawHouseBreakdown(cx, cy, canvasSize, validStep);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getVocabLessonCatalog() {
|
|
19
|
+
return [
|
|
20
|
+
{
|
|
21
|
+
id: 'vocab_house',
|
|
22
|
+
name: 'House Breakdown (5 Steps)',
|
|
23
|
+
description: 'Break down a house: Masses -> Structural -> Functional -> Identity -> Materials. Supply step=1..5',
|
|
24
|
+
fn: lessonVocabHouse
|
|
25
|
+
}
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
lessonVocabHouse,
|
|
31
|
+
getVocabLessonCatalog
|
|
32
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
const shapes = require('./shapes');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Draws a 5-step breakdown of a House.
|
|
5
|
+
* Step 1: Main Masses (Thân + Mái)
|
|
6
|
+
* Step 2: Structural Parts (Cột, Móng, Hiên/Bậc thềm)
|
|
7
|
+
* Step 3: Functional Parts (Cửa, Cửa sổ)
|
|
8
|
+
* Step 4: Identifying Details (Ống khói, Viền mái, Khung cửa)
|
|
9
|
+
* Step 5: Materials (Ván gỗ, Gạch)
|
|
10
|
+
*/
|
|
11
|
+
function drawHouseBreakdown(cx, cy, scale, step) {
|
|
12
|
+
const commands = [];
|
|
13
|
+
const color = '#37474f';
|
|
14
|
+
|
|
15
|
+
const w = Math.floor(scale * 0.5);
|
|
16
|
+
const h = Math.floor(scale * 0.4);
|
|
17
|
+
const roofH = Math.floor(scale * 0.25);
|
|
18
|
+
|
|
19
|
+
const leftX = cx - Math.floor(w/2);
|
|
20
|
+
const rightX = cx + Math.floor(w/2);
|
|
21
|
+
const topY = cy;
|
|
22
|
+
const botY = cy + h;
|
|
23
|
+
const roofTipY = topY - roofH;
|
|
24
|
+
|
|
25
|
+
// STEP 1: MAIN MASSES
|
|
26
|
+
if (step >= 1) {
|
|
27
|
+
// Main Body Box
|
|
28
|
+
commands.push(shapes.drawRectangle(leftX, topY, w, h, color, false));
|
|
29
|
+
// Roof Triangle
|
|
30
|
+
commands.push(shapes.drawLine(leftX, topY, cx, roofTipY, color));
|
|
31
|
+
commands.push(shapes.drawLine(cx, roofTipY, rightX, topY, color));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// STEP 2: STRUCTURAL PARTS
|
|
35
|
+
if (step >= 2) {
|
|
36
|
+
// Corner Columns (Cột)
|
|
37
|
+
commands.push(shapes.drawLine(leftX - 4, topY, leftX - 4, botY, color));
|
|
38
|
+
commands.push(shapes.drawLine(rightX + 4, topY, rightX + 4, botY, color));
|
|
39
|
+
// Foundation (Móng)
|
|
40
|
+
commands.push(shapes.drawRectangle(leftX - 6, botY, w + 12, 5, color, false));
|
|
41
|
+
// Front Porch Steps (Bậc thềm)
|
|
42
|
+
const pW = Math.floor(w * 0.4);
|
|
43
|
+
const pX = cx - Math.floor(pW/2);
|
|
44
|
+
commands.push(shapes.drawRectangle(pX, botY + 5, pW, 4, color, false));
|
|
45
|
+
commands.push(shapes.drawRectangle(pX - 2, botY + 9, pW + 4, 4, color, false));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// STEP 3: FUNCTIONAL PARTS
|
|
49
|
+
if (step >= 3) {
|
|
50
|
+
// Door (Cửa ra vào)
|
|
51
|
+
const dW = Math.floor(w * 0.25);
|
|
52
|
+
const dH = Math.floor(h * 0.6);
|
|
53
|
+
const dX = cx - Math.floor(dW/2);
|
|
54
|
+
commands.push(shapes.drawRectangle(dX, botY - dH, dW, dH, color, false));
|
|
55
|
+
|
|
56
|
+
// Windows (Cửa sổ)
|
|
57
|
+
const winW = Math.floor(w * 0.2);
|
|
58
|
+
const winH = Math.floor(h * 0.35);
|
|
59
|
+
const winY = topY + Math.floor(h * 0.2);
|
|
60
|
+
// Left window
|
|
61
|
+
commands.push(shapes.drawRectangle(leftX + 8, winY, winW, winH, color, false));
|
|
62
|
+
// Right window
|
|
63
|
+
commands.push(shapes.drawRectangle(rightX - 8 - winW, winY, winW, winH, color, false));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// STEP 4: IDENTIFYING DETAILS
|
|
67
|
+
if (step >= 4) {
|
|
68
|
+
// Chimney (Ống khói)
|
|
69
|
+
const chimW = Math.floor(w * 0.15);
|
|
70
|
+
const chimX = leftX + Math.floor(w * 0.1);
|
|
71
|
+
const chimY = topY - Math.floor(roofH * 0.6);
|
|
72
|
+
commands.push(shapes.drawRectangle(chimX, chimY, chimW, Math.floor(roofH * 0.5), color, false));
|
|
73
|
+
commands.push(shapes.drawRectangle(chimX - 2, chimY - 4, chimW + 4, 4, color, false)); // Chimney cap
|
|
74
|
+
|
|
75
|
+
// Roof Trim (Viền mái)
|
|
76
|
+
commands.push(shapes.drawLine(leftX - 6, topY + 4, cx, roofTipY - 6, color));
|
|
77
|
+
commands.push(shapes.drawLine(cx, roofTipY - 6, rightX + 6, topY + 4, color));
|
|
78
|
+
|
|
79
|
+
// Window/Door Frames & Knobs (Khung cửa)
|
|
80
|
+
const winW = Math.floor(w * 0.2);
|
|
81
|
+
const winH = Math.floor(h * 0.35);
|
|
82
|
+
const winY = topY + Math.floor(h * 0.2);
|
|
83
|
+
// Left window cross
|
|
84
|
+
commands.push(shapes.drawLine(leftX + 8 + Math.floor(winW/2), winY, leftX + 8 + Math.floor(winW/2), winY + winH, color));
|
|
85
|
+
commands.push(shapes.drawLine(leftX + 8, winY + Math.floor(winH/2), leftX + 8 + winW, winY + Math.floor(winH/2), color));
|
|
86
|
+
// Right window cross
|
|
87
|
+
commands.push(shapes.drawLine(rightX - 8 - Math.floor(winW/2), winY, rightX - 8 - Math.floor(winW/2), winY + winH, color));
|
|
88
|
+
commands.push(shapes.drawLine(rightX - 8 - winW, winY + Math.floor(winH/2), rightX - 8, winY + Math.floor(winH/2), color));
|
|
89
|
+
// Door knob
|
|
90
|
+
commands.push(shapes.drawCircle(cx + Math.floor(w * 0.08), botY - Math.floor(h * 0.3), 1, color, true));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// STEP 5: SURFACE MATERIALS
|
|
94
|
+
if (step >= 5) {
|
|
95
|
+
const matColor = '#9e9e9e';
|
|
96
|
+
// Wood Planks on the main body
|
|
97
|
+
for (let i = 1; i < 5; i++) {
|
|
98
|
+
const plankY = topY + i * Math.floor(h / 5);
|
|
99
|
+
commands.push(shapes.drawLine(leftX + 2, plankY, rightX - 2, plankY, matColor));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Brick lines on the chimney
|
|
103
|
+
const chimW = Math.floor(w * 0.15);
|
|
104
|
+
const chimX = leftX + Math.floor(w * 0.1);
|
|
105
|
+
const chimY = topY - Math.floor(roofH * 0.6);
|
|
106
|
+
commands.push(shapes.drawLine(chimX, chimY + 4, chimX + chimW, chimY + 4, matColor));
|
|
107
|
+
commands.push(shapes.drawLine(chimX, chimY + 8, chimX + chimW, chimY + 8, matColor));
|
|
108
|
+
commands.push(shapes.drawLine(chimX + 4, chimY + 4, chimX + 4, chimY + 8, matColor)); // vertical brick line
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return commands;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
module.exports = {
|
|
115
|
+
drawHouseBreakdown
|
|
116
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
const { registerTool, sendCommand } = require('../../core/server');
|
|
3
|
+
const vocabLessons = require('./vocab_lessons');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Register all visual vocabulary tools on the MCP server.
|
|
7
|
+
* @param {McpServer} server
|
|
8
|
+
*/
|
|
9
|
+
function register(server) {
|
|
10
|
+
registerTool(server, 'art_tree_lesson_vocab_house',
|
|
11
|
+
'Lesson: Visual Vocabulary - 5-Step Breakdown of a House. Pass step (1 to 5).',
|
|
12
|
+
{
|
|
13
|
+
canvasSize: z.number().int().min(16).max(128).default(64),
|
|
14
|
+
step: z.number().int().min(1).max(5).describe('The breakdown step (1=Main Masses, 2=Structural, 3=Functional, 4=Identity, 5=Materials)')
|
|
15
|
+
},
|
|
16
|
+
async (p) => {
|
|
17
|
+
const commands = vocabLessons.lessonVocabHouse(p.canvasSize, p.step);
|
|
18
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
19
|
+
const steps = [
|
|
20
|
+
"1. Main Masses (Khối chính: Thân nhà, Mái)",
|
|
21
|
+
"2. Structural Parts (Bộ phận lớn: Cột, Móng, Bậc thềm)",
|
|
22
|
+
"3. Functional Parts (Chức năng: Cửa, Cửa sổ)",
|
|
23
|
+
"4. Identifying Details (Nhận diện: Ống khói, Viền mái, Khung cửa)",
|
|
24
|
+
"5. Surface Materials (Vật liệu: Ván gỗ, Gạch)"
|
|
25
|
+
];
|
|
26
|
+
return { content: [{ type: 'text', text: `✓ Step ${p.step} Vocabulary breakdown completed:\n${steps[p.step - 1]}` }] };
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
registerTool(server, 'art_tree_lesson_vocab_catalog',
|
|
30
|
+
'Get the catalog of all available Visual Vocabulary lessons',
|
|
31
|
+
{},
|
|
32
|
+
() => {
|
|
33
|
+
const catalog = vocabLessons.getVocabLessonCatalog();
|
|
34
|
+
const text = catalog.map(l =>
|
|
35
|
+
` • ${l.id}: ${l.name} — ${l.description}`
|
|
36
|
+
).join('\n');
|
|
37
|
+
return { content: [{ type: 'text', text: `📚 Visual Vocabulary Lessons:\n\n${text}` }] };
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = { register };
|
package/package.json
CHANGED
package/tools/index.js
CHANGED
|
@@ -18,6 +18,7 @@ const history = require('./history');
|
|
|
18
18
|
const modes = require('./modes');
|
|
19
19
|
const colors = require('./colors');
|
|
20
20
|
const health = require('./health');
|
|
21
|
+
const layer = require('./layer');
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Register all core tools on the given MCP server instance.
|
|
@@ -37,6 +38,7 @@ function registerAll(server) {
|
|
|
37
38
|
modes.register(server);
|
|
38
39
|
colors.register(server);
|
|
39
40
|
health.register(server);
|
|
41
|
+
layer.register(server);
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
module.exports = { registerAll };
|
package/tools/layer.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* layer.js — MCP Tool Registration for Layers
|
|
4
|
+
*
|
|
5
|
+
* Exposes the layer management functionality to MCP clients.
|
|
6
|
+
*/
|
|
7
|
+
const { z } = require('zod');
|
|
8
|
+
const { registerTool, sendCommand } = require('../core/server');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Register all layer tools on the MCP server.
|
|
12
|
+
* @param {McpServer} server
|
|
13
|
+
*/
|
|
14
|
+
function register(server) {
|
|
15
|
+
registerTool(server, 'layer_get_info',
|
|
16
|
+
'Get information about all current layers and the active layer index.',
|
|
17
|
+
{},
|
|
18
|
+
async () => {
|
|
19
|
+
const activeRes = await sendCommand({ action: 'layer.getActiveLayerIndex' });
|
|
20
|
+
const layersRes = await sendCommand({ action: 'layer.getLayers' });
|
|
21
|
+
return {
|
|
22
|
+
content: [{
|
|
23
|
+
type: 'text',
|
|
24
|
+
text: JSON.stringify({
|
|
25
|
+
activeLayerIndex: activeRes.result,
|
|
26
|
+
layers: layersRes.result
|
|
27
|
+
}, null, 2)
|
|
28
|
+
}]
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
registerTool(server, 'layer_add',
|
|
33
|
+
'Add a new layer on top of existing layers.',
|
|
34
|
+
{},
|
|
35
|
+
async () => {
|
|
36
|
+
await sendCommand({ action: 'layer.add' });
|
|
37
|
+
return { content: [{ type: 'text', text: '✓ New layer added.' }] };
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
registerTool(server, 'layer_remove',
|
|
41
|
+
'Remove a layer by its index.',
|
|
42
|
+
{ index: z.number().int().describe('The index of the layer to remove (0 is the bottom-most)') },
|
|
43
|
+
async (p) => {
|
|
44
|
+
await sendCommand({ action: 'layer.remove', index: p.index });
|
|
45
|
+
return { content: [{ type: 'text', text: `✓ Layer ${p.index} removed.` }] };
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
registerTool(server, 'layer_move',
|
|
49
|
+
'Move a layer up or down in the stack.',
|
|
50
|
+
{
|
|
51
|
+
index: z.number().int().describe('The index of the layer to move'),
|
|
52
|
+
direction: z.enum(['up', 'down']).describe('Direction to move the layer')
|
|
53
|
+
},
|
|
54
|
+
async (p) => {
|
|
55
|
+
if (p.direction === 'up') {
|
|
56
|
+
await sendCommand({ action: 'layer.moveUp', index: p.index });
|
|
57
|
+
} else {
|
|
58
|
+
await sendCommand({ action: 'layer.moveDown', index: p.index });
|
|
59
|
+
}
|
|
60
|
+
return { content: [{ type: 'text', text: `✓ Layer ${p.index} moved ${p.direction}.` }] };
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
registerTool(server, 'layer_toggle_visibility',
|
|
64
|
+
'Toggle the visibility of a layer by its index.',
|
|
65
|
+
{ index: z.number().int().describe('The index of the layer to toggle') },
|
|
66
|
+
async (p) => {
|
|
67
|
+
await sendCommand({ action: 'layer.toggle', index: p.index });
|
|
68
|
+
return { content: [{ type: 'text', text: `✓ Toggled visibility of layer ${p.index}.` }] };
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
registerTool(server, 'layer_select',
|
|
72
|
+
'Select an active layer for drawing operations.',
|
|
73
|
+
{ index: z.number().int().describe('The index of the layer to select') },
|
|
74
|
+
async (p) => {
|
|
75
|
+
await sendCommand({ action: 'layer.select', index: p.index });
|
|
76
|
+
return { content: [{ type: 'text', text: `✓ Selected layer ${p.index}.` }] };
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = { register };
|