@pixel-normal-edit/mcp 2.0.6 → 2.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/domains/art-tree/3d_lessons.js +114 -0
  2. package/domains/art-tree/3d_shapes.js +116 -0
  3. package/domains/art-tree/3d_tools.js +60 -0
  4. package/domains/art-tree/advanced_lessons.js +124 -0
  5. package/domains/art-tree/advanced_shapes.js +73 -0
  6. package/domains/art-tree/advanced_tools.js +69 -0
  7. package/domains/art-tree/analysis_lessons.js +32 -0
  8. package/domains/art-tree/analysis_shapes.js +146 -0
  9. package/domains/art-tree/analysis_tools.js +49 -0
  10. package/domains/art-tree/cross_section_lessons.js +65 -0
  11. package/domains/art-tree/cross_section_shapes.js +84 -0
  12. package/domains/art-tree/cross_section_tools.js +51 -0
  13. package/domains/art-tree/curve_lessons.js +138 -0
  14. package/domains/art-tree/curve_shapes.js +90 -0
  15. package/domains/art-tree/curve_tools.js +51 -0
  16. package/domains/art-tree/ellipse_lessons.js +105 -0
  17. package/domains/art-tree/ellipse_shapes.js +63 -0
  18. package/domains/art-tree/ellipse_tools.js +60 -0
  19. package/domains/art-tree/hidden_lessons.js +55 -0
  20. package/domains/art-tree/hidden_shapes.js +97 -0
  21. package/domains/art-tree/hidden_tools.js +51 -0
  22. package/domains/art-tree/index.js +32 -0
  23. package/domains/art-tree/layer_lessons.js +32 -0
  24. package/domains/art-tree/layer_shapes.js +117 -0
  25. package/domains/art-tree/layer_tools.js +43 -0
  26. package/domains/art-tree/lessons.js +119 -7
  27. package/domains/art-tree/light_lessons.js +92 -0
  28. package/domains/art-tree/light_shapes.js +136 -0
  29. package/domains/art-tree/light_tools.js +49 -0
  30. package/domains/art-tree/material_lessons.js +71 -0
  31. package/domains/art-tree/material_shapes.js +130 -0
  32. package/domains/art-tree/material_tools.js +49 -0
  33. package/domains/art-tree/perspective_lessons.js +138 -0
  34. package/domains/art-tree/perspective_shapes.js +96 -0
  35. package/domains/art-tree/perspective_tools.js +60 -0
  36. package/domains/art-tree/shapes.js +15 -0
  37. package/domains/art-tree/sky_lessons_1.js +82 -0
  38. package/domains/art-tree/sky_shapes_1.js +68 -0
  39. package/domains/art-tree/sky_tools.js +51 -0
  40. package/domains/art-tree/structure_lessons.js +102 -0
  41. package/domains/art-tree/structure_shapes.js +74 -0
  42. package/domains/art-tree/structure_tools.js +51 -0
  43. package/domains/art-tree/surface_lessons.js +112 -0
  44. package/domains/art-tree/surface_shapes.js +87 -0
  45. package/domains/art-tree/surface_tools.js +60 -0
  46. package/domains/art-tree/tools.js +69 -15
  47. package/domains/art-tree/transform_lessons_1.js +108 -0
  48. package/domains/art-tree/transform_lessons_2.js +102 -0
  49. package/domains/art-tree/transform_shapes.js +109 -0
  50. package/domains/art-tree/transform_tools.js +104 -0
  51. package/domains/art-tree/vocab_lessons.js +32 -0
  52. package/domains/art-tree/vocab_shapes.js +116 -0
  53. package/domains/art-tree/vocab_tools.js +41 -0
  54. package/package.json +1 -1
  55. package/tools/index.js +2 -0
  56. package/tools/layer.js +80 -0
@@ -0,0 +1,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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixel-normal-edit/mcp",
3
- "version": "2.0.6",
3
+ "version": "2.0.8",
4
4
  "description": "MCP server for Pixel Normal Edit canvas - enables AI agents to draw pixel art",
5
5
  "main": "index.js",
6
6
  "bin": {
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 };