@pixel-normal-edit/mcp 2.0.6 → 2.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/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 +22 -0
- package/domains/art-tree/lessons.js +112 -0
- 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/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/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 +54 -0
- 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/package.json +1 -1
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
const shapes3d = require('./3d_shapes');
|
|
2
|
+
const advShapes = require('./advanced_shapes');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Lesson: Box Properties (Width, Height, Depth, Planes, Edges)
|
|
6
|
+
*/
|
|
7
|
+
function lessonBoxProperties(canvasSize = 32, color = '#2196f3') {
|
|
8
|
+
const commands = [];
|
|
9
|
+
const cx = Math.floor(canvasSize / 2);
|
|
10
|
+
const cy = Math.floor(canvasSize / 2);
|
|
11
|
+
const w = Math.floor(canvasSize * 0.4);
|
|
12
|
+
const h = Math.floor(canvasSize * 0.4);
|
|
13
|
+
const d = Math.floor(canvasSize * 0.3);
|
|
14
|
+
|
|
15
|
+
// Draw wireframe box
|
|
16
|
+
commands.push(...shapes3d.drawWireframeBox(cx, cy, w, h, d, color));
|
|
17
|
+
|
|
18
|
+
// Highlight dimensions
|
|
19
|
+
const fX = cx - Math.floor(w/2);
|
|
20
|
+
const fY = cy - Math.floor(h/2);
|
|
21
|
+
|
|
22
|
+
// Width
|
|
23
|
+
commands.push(...advShapes.drawDistance(fX, fY + h + 2, fX + w, fY + h + 2, '#4caf50'));
|
|
24
|
+
// Height
|
|
25
|
+
commands.push(...advShapes.drawDistance(fX - 2, fY, fX - 2, fY + h, '#ff9800'));
|
|
26
|
+
|
|
27
|
+
// Depth (approximate along the oblique edge)
|
|
28
|
+
const dx = Math.floor(d * 0.7);
|
|
29
|
+
const dy = Math.floor(d * 0.5);
|
|
30
|
+
commands.push(...advShapes.drawDistance(fX + w + 2, fY + h, fX + w + 2 + dx, fY + h - dy, '#e91e63'));
|
|
31
|
+
|
|
32
|
+
return commands;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Lesson: Sphere Properties (Center, Radius, Cross contours, Axis)
|
|
37
|
+
*/
|
|
38
|
+
function lessonSphereProperties(canvasSize = 32, color = '#9c27b0') {
|
|
39
|
+
const commands = [];
|
|
40
|
+
const cx = Math.floor(canvasSize / 2);
|
|
41
|
+
const cy = Math.floor(canvasSize / 2);
|
|
42
|
+
const r = Math.floor(canvasSize * 0.35);
|
|
43
|
+
|
|
44
|
+
// Draw sphere with volume lines
|
|
45
|
+
commands.push(...shapes3d.drawSphereVolume(cx, cy, r, color));
|
|
46
|
+
|
|
47
|
+
// Highlight radius
|
|
48
|
+
commands.push(...advShapes.drawDistance(cx, cy, cx + r, cy, '#ff9800'));
|
|
49
|
+
|
|
50
|
+
return commands;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Lesson: Cylinder Properties (Central axis, Bases, Perspective change)
|
|
55
|
+
*/
|
|
56
|
+
function lessonCylinderProperties(canvasSize = 32, color = '#00bcd4') {
|
|
57
|
+
const commands = [];
|
|
58
|
+
const cx = Math.floor(canvasSize / 2);
|
|
59
|
+
const cy = Math.floor(canvasSize / 2);
|
|
60
|
+
const r = Math.floor(canvasSize * 0.3);
|
|
61
|
+
const h = Math.floor(canvasSize * 0.6);
|
|
62
|
+
|
|
63
|
+
// Draw cylinder volume
|
|
64
|
+
commands.push(...shapes3d.drawCylinderVolume(cx, cy, r, h, color));
|
|
65
|
+
|
|
66
|
+
// Highlight Height
|
|
67
|
+
commands.push(...advShapes.drawDistance(cx + r + 4, cy - Math.floor(h/2), cx + r + 4, cy + Math.floor(h/2), '#4caf50'));
|
|
68
|
+
|
|
69
|
+
// Highlight Radius (top base)
|
|
70
|
+
commands.push(...advShapes.drawDistance(cx, cy - Math.floor(h/2), cx + r, cy - Math.floor(h/2), '#ff9800'));
|
|
71
|
+
|
|
72
|
+
return commands;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Lesson: Cone Properties (Apex, Base, Axis)
|
|
77
|
+
*/
|
|
78
|
+
function lessonConeProperties(canvasSize = 32, color = '#ff5722') {
|
|
79
|
+
const commands = [];
|
|
80
|
+
const cx = Math.floor(canvasSize / 2);
|
|
81
|
+
const cy = Math.floor(canvasSize / 2);
|
|
82
|
+
const r = Math.floor(canvasSize * 0.35);
|
|
83
|
+
const h = Math.floor(canvasSize * 0.6);
|
|
84
|
+
|
|
85
|
+
// Draw cone volume
|
|
86
|
+
commands.push(...shapes3d.drawConeVolume(cx, cy, r, h, color));
|
|
87
|
+
|
|
88
|
+
// Highlight Height
|
|
89
|
+
const apexY = cy - Math.floor(h/2);
|
|
90
|
+
const baseY = cy + Math.floor(h/2);
|
|
91
|
+
commands.push(...advShapes.drawDistance(cx + r + 4, apexY, cx + r + 4, baseY, '#4caf50'));
|
|
92
|
+
|
|
93
|
+
// Highlight Radius (base)
|
|
94
|
+
commands.push(...advShapes.drawDistance(cx, baseY, cx + r, baseY, '#ff9800'));
|
|
95
|
+
|
|
96
|
+
return commands;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function get3DLessonCatalog() {
|
|
100
|
+
return [
|
|
101
|
+
{ id: '3d_box', name: '3D Box Properties', description: 'Width, height, depth, planes, inner structure', fn: lessonBoxProperties },
|
|
102
|
+
{ id: '3d_sphere', name: '3D Sphere Properties', description: 'Center, radius, axis, cross contours', fn: lessonSphereProperties },
|
|
103
|
+
{ id: '3d_cylinder', name: '3D Cylinder Properties', description: 'Axis, bases, height, perspective changes', fn: lessonCylinderProperties },
|
|
104
|
+
{ id: '3d_cone', name: '3D Cone Properties', description: 'Apex, base, axis, radius', fn: lessonConeProperties },
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = {
|
|
109
|
+
lessonBoxProperties,
|
|
110
|
+
lessonSphereProperties,
|
|
111
|
+
lessonCylinderProperties,
|
|
112
|
+
lessonConeProperties,
|
|
113
|
+
get3DLessonCatalog
|
|
114
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
const shapes = require('./shapes');
|
|
2
|
+
const ellipseShapes = require('./ellipse_shapes');
|
|
3
|
+
const advShapes = require('./advanced_shapes');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Draw a wireframe box (oblique projection)
|
|
7
|
+
*/
|
|
8
|
+
function drawWireframeBox(cx, cy, width, height, depth, color) {
|
|
9
|
+
const commands = [];
|
|
10
|
+
const dx = Math.floor(depth * 0.7);
|
|
11
|
+
const dy = Math.floor(depth * 0.5);
|
|
12
|
+
|
|
13
|
+
// Front face
|
|
14
|
+
const fX = cx - Math.floor(width/2);
|
|
15
|
+
const fY = cy - Math.floor(height/2);
|
|
16
|
+
commands.push(shapes.drawRectangle(fX, fY, width, height, color, false));
|
|
17
|
+
|
|
18
|
+
// Back face (shifted by dx, -dy)
|
|
19
|
+
const bX = fX + dx;
|
|
20
|
+
const bY = fY - dy;
|
|
21
|
+
const backColor = '#9e9e9e'; // Lighter color for inner/back structure
|
|
22
|
+
commands.push(shapes.drawRectangle(bX, bY, width, height, backColor, false));
|
|
23
|
+
|
|
24
|
+
// Connect corners (edges)
|
|
25
|
+
commands.push(shapes.drawLine(fX, fY, bX, bY, color)); // Top-left
|
|
26
|
+
commands.push(shapes.drawLine(fX + width, fY, bX + width, bY, color)); // Top-right
|
|
27
|
+
commands.push(shapes.drawLine(fX, fY + height, bX, bY + height, backColor)); // Bottom-left (hidden)
|
|
28
|
+
commands.push(shapes.drawLine(fX + width, fY + height, bX + width, bY + height, color)); // Bottom-right
|
|
29
|
+
|
|
30
|
+
return commands;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Draw a sphere showing volume with cross contours
|
|
35
|
+
*/
|
|
36
|
+
function drawSphereVolume(cx, cy, r, color) {
|
|
37
|
+
const commands = [];
|
|
38
|
+
|
|
39
|
+
// Silhouette
|
|
40
|
+
commands.push(shapes.drawCircle(cx, cy, r, color, false));
|
|
41
|
+
|
|
42
|
+
// Horizontal cross contour (latitude)
|
|
43
|
+
commands.push(shapes.drawEllipse(cx, cy, r, Math.floor(r * 0.3), '#03a9f4', false));
|
|
44
|
+
|
|
45
|
+
// Vertical cross contour (longitude)
|
|
46
|
+
commands.push(shapes.drawEllipse(cx, cy, Math.floor(r * 0.3), r, '#03a9f4', false));
|
|
47
|
+
|
|
48
|
+
// Center
|
|
49
|
+
commands.push(shapes.drawCircle(cx, cy, 1, '#ff0000', true));
|
|
50
|
+
|
|
51
|
+
// Axis
|
|
52
|
+
commands.push(advShapes.drawAxis(cx, cy - r - 5, cx, cy + r + 5));
|
|
53
|
+
|
|
54
|
+
return commands;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Draw a cylinder showing volume and perspective
|
|
59
|
+
*/
|
|
60
|
+
function drawCylinderVolume(cx, cy, r, h, color) {
|
|
61
|
+
const commands = [];
|
|
62
|
+
|
|
63
|
+
const topRy = Math.floor(r * 0.2); // Top ellipse is narrower
|
|
64
|
+
const botRy = Math.floor(r * 0.3); // Bottom ellipse is wider (perspective)
|
|
65
|
+
|
|
66
|
+
const topY = cy - Math.floor(h/2);
|
|
67
|
+
const botY = cy + Math.floor(h/2);
|
|
68
|
+
|
|
69
|
+
// Central axis
|
|
70
|
+
commands.push(advShapes.drawAxis(cx, topY - 10, cx, botY + 10));
|
|
71
|
+
|
|
72
|
+
// Back curve of bottom ellipse (lighter)
|
|
73
|
+
// Our drawEllipse draws the whole thing, we'll just draw the whole ellipse
|
|
74
|
+
commands.push(shapes.drawEllipse(cx, botY, r, botRy, color, false));
|
|
75
|
+
|
|
76
|
+
// Vertical edges
|
|
77
|
+
commands.push(shapes.drawLine(cx - r, topY, cx - r, botY, color));
|
|
78
|
+
commands.push(shapes.drawLine(cx + r, topY, cx + r, botY, color));
|
|
79
|
+
|
|
80
|
+
// Top ellipse
|
|
81
|
+
commands.push(shapes.drawEllipse(cx, topY, r, topRy, color, false));
|
|
82
|
+
|
|
83
|
+
return commands;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Draw a cone showing volume
|
|
88
|
+
*/
|
|
89
|
+
function drawConeVolume(cx, cy, r, h, color) {
|
|
90
|
+
const commands = [];
|
|
91
|
+
const baseRy = Math.floor(r * 0.3);
|
|
92
|
+
const apexY = cy - Math.floor(h/2);
|
|
93
|
+
const baseY = cy + Math.floor(h/2);
|
|
94
|
+
|
|
95
|
+
// Central axis
|
|
96
|
+
commands.push(advShapes.drawAxis(cx, apexY - 10, cx, baseY + 10));
|
|
97
|
+
|
|
98
|
+
// Base ellipse
|
|
99
|
+
commands.push(shapes.drawEllipse(cx, baseY, r, baseRy, color, false));
|
|
100
|
+
|
|
101
|
+
// Tangent edges (approximate to the edges of the ellipse rx)
|
|
102
|
+
commands.push(shapes.drawLine(cx, apexY, cx - r, baseY, color));
|
|
103
|
+
commands.push(shapes.drawLine(cx, apexY, cx + r, baseY, color));
|
|
104
|
+
|
|
105
|
+
// Apex
|
|
106
|
+
commands.push(shapes.drawCircle(cx, apexY, 2, '#ff0000', true));
|
|
107
|
+
|
|
108
|
+
return commands;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = {
|
|
112
|
+
drawWireframeBox,
|
|
113
|
+
drawSphereVolume,
|
|
114
|
+
drawCylinderVolume,
|
|
115
|
+
drawConeVolume
|
|
116
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
const { registerTool, sendCommand } = require('../../core/server');
|
|
3
|
+
const lessons3d = require('./3d_lessons');
|
|
4
|
+
|
|
5
|
+
const hexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/).describe('Hex color e.g. #ff0000');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Register all 3D tools on the MCP server.
|
|
9
|
+
* @param {McpServer} server
|
|
10
|
+
*/
|
|
11
|
+
function register(server) {
|
|
12
|
+
registerTool(server, 'art_tree_lesson_3d_box',
|
|
13
|
+
'Lesson: 3D Forms - Box Properties (Width, Height, Depth, Planes)',
|
|
14
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#2196f3') },
|
|
15
|
+
async (p) => {
|
|
16
|
+
const commands = lessons3d.lessonBoxProperties(p.canvasSize, p.color);
|
|
17
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
18
|
+
return { content: [{ type: 'text', text: `✓ 3D Box Properties lesson completed` }] };
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
registerTool(server, 'art_tree_lesson_3d_sphere',
|
|
22
|
+
'Lesson: 3D Forms - Sphere Properties (Center, Radius, Cross contours)',
|
|
23
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#9c27b0') },
|
|
24
|
+
async (p) => {
|
|
25
|
+
const commands = lessons3d.lessonSphereProperties(p.canvasSize, p.color);
|
|
26
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
27
|
+
return { content: [{ type: 'text', text: `✓ 3D Sphere Properties lesson completed` }] };
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
registerTool(server, 'art_tree_lesson_3d_cylinder',
|
|
31
|
+
'Lesson: 3D Forms - Cylinder Properties (Axis, Bases, Height)',
|
|
32
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#00bcd4') },
|
|
33
|
+
async (p) => {
|
|
34
|
+
const commands = lessons3d.lessonCylinderProperties(p.canvasSize, p.color);
|
|
35
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
36
|
+
return { content: [{ type: 'text', text: `✓ 3D Cylinder Properties lesson completed` }] };
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
registerTool(server, 'art_tree_lesson_3d_cone',
|
|
40
|
+
'Lesson: 3D Forms - Cone Properties (Apex, Base, Axis)',
|
|
41
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#ff5722') },
|
|
42
|
+
async (p) => {
|
|
43
|
+
const commands = lessons3d.lessonConeProperties(p.canvasSize, p.color);
|
|
44
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
45
|
+
return { content: [{ type: 'text', text: `✓ 3D Cone Properties lesson completed` }] };
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
registerTool(server, 'art_tree_lesson_3d_catalog',
|
|
49
|
+
'Get the catalog of all available 3D Form lessons',
|
|
50
|
+
{},
|
|
51
|
+
() => {
|
|
52
|
+
const catalog = lessons3d.get3DLessonCatalog();
|
|
53
|
+
const text = catalog.map(l =>
|
|
54
|
+
` • ${l.id}: ${l.name} — ${l.description}`
|
|
55
|
+
).join('\n');
|
|
56
|
+
return { content: [{ type: 'text', text: `📚 3D Form Lessons:\n\n${text}` }] };
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = { register };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
const shapes = require('./shapes');
|
|
2
|
+
const advShapes = require('./advanced_shapes');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Lesson: Tỉ lệ (Ratios)
|
|
6
|
+
*/
|
|
7
|
+
function lessonShapeRatios(canvasSize = 32, color = '#2196f3') {
|
|
8
|
+
const margin = Math.floor(canvasSize * 0.1);
|
|
9
|
+
const w1 = Math.floor(canvasSize * 0.2);
|
|
10
|
+
const h1 = w1; // 1:1
|
|
11
|
+
const w2 = Math.floor(canvasSize * 0.2);
|
|
12
|
+
const h2 = Math.floor(canvasSize * 0.4); // 1:2
|
|
13
|
+
|
|
14
|
+
return [
|
|
15
|
+
shapes.drawRectangle(margin, margin, w1, h1, color, false),
|
|
16
|
+
shapes.drawRectangle(canvasSize - margin - w2, margin, w2, h2, '#4caf50', false),
|
|
17
|
+
...advShapes.drawDistance(margin, margin + h1 + 2, margin + w1, margin + h1 + 2, '#888'),
|
|
18
|
+
...advShapes.drawDistance(canvasSize - margin - w2, margin + h2 + 2, canvasSize - margin, margin + h2 + 2, '#888')
|
|
19
|
+
];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Lesson: Đối xứng & Trục giữa (Symmetry & Axes)
|
|
24
|
+
*/
|
|
25
|
+
function lessonSymmetryAndAxis(canvasSize = 32, color = '#9c27b0') {
|
|
26
|
+
const mid = Math.floor(canvasSize / 2);
|
|
27
|
+
const w = Math.floor(canvasSize * 0.5);
|
|
28
|
+
const h = Math.floor(canvasSize * 0.5);
|
|
29
|
+
const commands = [];
|
|
30
|
+
|
|
31
|
+
// Draw Rhombus
|
|
32
|
+
commands.push(advShapes.drawRhombus(mid, mid, w, h, color, false));
|
|
33
|
+
// Vertical axis
|
|
34
|
+
commands.push(advShapes.drawAxis(mid, Math.floor(canvasSize * 0.1), mid, canvasSize - Math.floor(canvasSize * 0.1)));
|
|
35
|
+
// Horizontal axis
|
|
36
|
+
commands.push(advShapes.drawAxis(Math.floor(canvasSize * 0.1), mid, canvasSize - Math.floor(canvasSize * 0.1), mid));
|
|
37
|
+
|
|
38
|
+
return commands;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Lesson: Góc (Angles)
|
|
43
|
+
*/
|
|
44
|
+
function lessonAngles(canvasSize = 32, color = '#ff5722') {
|
|
45
|
+
const mid = Math.floor(canvasSize / 2);
|
|
46
|
+
const w = Math.floor(canvasSize * 0.6);
|
|
47
|
+
const h = Math.floor(canvasSize * 0.3);
|
|
48
|
+
const margin = Math.floor(canvasSize * 0.2);
|
|
49
|
+
|
|
50
|
+
const commands = [];
|
|
51
|
+
// Trapezoid (shows acute and obtuse angles)
|
|
52
|
+
commands.push(advShapes.drawTrapezoid(margin, margin, Math.floor(w/2), w, h, color, false));
|
|
53
|
+
|
|
54
|
+
// Right Triangle (shows 90 degree angle)
|
|
55
|
+
commands.push(shapes.drawTriangle([
|
|
56
|
+
{ x: margin, y: canvasSize - margin },
|
|
57
|
+
{ x: margin, y: mid + margin },
|
|
58
|
+
{ x: margin + w, y: canvasSize - margin }
|
|
59
|
+
], '#3f51b5', false));
|
|
60
|
+
|
|
61
|
+
return commands;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Lesson: Khoảng cách (Distances)
|
|
66
|
+
*/
|
|
67
|
+
function lessonDistances(canvasSize = 32, color = '#00bcd4') {
|
|
68
|
+
const margin = Math.floor(canvasSize * 0.2);
|
|
69
|
+
const commands = [];
|
|
70
|
+
|
|
71
|
+
// Points
|
|
72
|
+
commands.push(shapes.drawCircle(margin, margin, 1, color, true));
|
|
73
|
+
commands.push(shapes.drawCircle(canvasSize - margin, margin, 1, color, true));
|
|
74
|
+
commands.push(shapes.drawCircle(margin, canvasSize - margin, 1, color, true));
|
|
75
|
+
|
|
76
|
+
// Distances between them
|
|
77
|
+
commands.push(...advShapes.drawDistance(margin, margin, canvasSize - margin, margin));
|
|
78
|
+
commands.push(...advShapes.drawDistance(margin, margin, margin, canvasSize - margin));
|
|
79
|
+
|
|
80
|
+
return commands;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Lesson: Quan hệ hình khối (Relationships)
|
|
85
|
+
*/
|
|
86
|
+
function lessonShapeRelationships(canvasSize = 32, color = '#607d8b') {
|
|
87
|
+
const mid = Math.floor(canvasSize / 2);
|
|
88
|
+
const r = Math.floor(canvasSize * 0.2);
|
|
89
|
+
const commands = [];
|
|
90
|
+
|
|
91
|
+
// Overlapping circles
|
|
92
|
+
commands.push(shapes.drawCircle(mid - Math.floor(r/2), mid, r, color, false));
|
|
93
|
+
commands.push(shapes.drawCircle(mid + Math.floor(r/2), mid, r, '#e91e63', false));
|
|
94
|
+
|
|
95
|
+
// Nested squares (top left)
|
|
96
|
+
const margin = Math.floor(canvasSize * 0.1);
|
|
97
|
+
const size = Math.floor(canvasSize * 0.25);
|
|
98
|
+
commands.push(shapes.drawSquare(margin, margin, size, '#4caf50', false));
|
|
99
|
+
commands.push(shapes.drawSquare(margin + 2, margin + 2, size - 4, '#8bc34a', false));
|
|
100
|
+
|
|
101
|
+
return commands;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get catalog of advanced lessons
|
|
106
|
+
*/
|
|
107
|
+
function getAdvancedLessonCatalog() {
|
|
108
|
+
return [
|
|
109
|
+
{ id: 'adv_ratios', name: 'Tỉ lệ hình', description: 'Tỉ lệ chiều rộng và chiều cao', fn: lessonShapeRatios },
|
|
110
|
+
{ id: 'adv_symmetry', name: 'Đối xứng & Trục', description: 'Sự đối xứng và trục giữa', fn: lessonSymmetryAndAxis },
|
|
111
|
+
{ id: 'adv_angles', name: 'Góc', description: 'Góc nhọn, góc vuông, góc tù', fn: lessonAngles },
|
|
112
|
+
{ id: 'adv_distances', name: 'Khoảng cách', description: 'Khoảng cách giữa các điểm', fn: lessonDistances },
|
|
113
|
+
{ id: 'adv_relationships', name: 'Quan hệ hình', description: 'Sự giao nhau và lồng nhau của các hình', fn: lessonShapeRelationships },
|
|
114
|
+
];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = {
|
|
118
|
+
lessonShapeRatios,
|
|
119
|
+
lessonSymmetryAndAxis,
|
|
120
|
+
lessonAngles,
|
|
121
|
+
lessonDistances,
|
|
122
|
+
lessonShapeRelationships,
|
|
123
|
+
getAdvancedLessonCatalog
|
|
124
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const shapes = require('./shapes');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Draw a trapezoid (isosceles usually) centered horizontally.
|
|
5
|
+
* @param {number} x - Top-left X of the bounding box
|
|
6
|
+
* @param {number} y - Top Y
|
|
7
|
+
* @param {number} topWidth - Width of the top edge
|
|
8
|
+
* @param {number} bottomWidth - Width of the bottom edge
|
|
9
|
+
* @param {number} height - Height of the trapezoid
|
|
10
|
+
* @param {string} color - Hex color
|
|
11
|
+
* @param {boolean} filled
|
|
12
|
+
*/
|
|
13
|
+
function drawTrapezoid(x, y, topWidth, bottomWidth, height, color, filled = false) {
|
|
14
|
+
const midX = x + Math.max(topWidth, bottomWidth) / 2;
|
|
15
|
+
const topX1 = Math.round(midX - topWidth / 2);
|
|
16
|
+
const topX2 = Math.round(midX + topWidth / 2);
|
|
17
|
+
const botX1 = Math.round(midX - bottomWidth / 2);
|
|
18
|
+
const botX2 = Math.round(midX + bottomWidth / 2);
|
|
19
|
+
|
|
20
|
+
const points = [
|
|
21
|
+
{ x: topX1, y },
|
|
22
|
+
{ x: topX2, y },
|
|
23
|
+
{ x: botX2, y: y + height },
|
|
24
|
+
{ x: botX1, y: y + height }
|
|
25
|
+
];
|
|
26
|
+
return shapes.drawPolygon(points, color, filled);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Draw a rhombus given center and its two diagonals (width, height).
|
|
31
|
+
*/
|
|
32
|
+
function drawRhombus(cx, cy, width, height, color, filled = false) {
|
|
33
|
+
const points = [
|
|
34
|
+
{ x: cx, y: cy - Math.floor(height / 2) },
|
|
35
|
+
{ x: cx + Math.floor(width / 2), y: cy },
|
|
36
|
+
{ x: cx, y: cy + Math.floor(height / 2) },
|
|
37
|
+
{ x: cx - Math.floor(width / 2), y: cy }
|
|
38
|
+
];
|
|
39
|
+
return shapes.drawPolygon(points, color, filled);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Draw a symmetry axis line (using a distinct color, e.g., light grey/red).
|
|
44
|
+
*/
|
|
45
|
+
function drawAxis(x0, y0, x1, y1) {
|
|
46
|
+
return shapes.drawLine(x0, y0, x1, y1, '#ffaaaa'); // Light red for axis
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Draw a dimension/distance line with end ticks.
|
|
51
|
+
*/
|
|
52
|
+
function drawDistance(x0, y0, x1, y1, color = '#888888') {
|
|
53
|
+
const commands = [];
|
|
54
|
+
commands.push(shapes.drawLine(x0, y0, x1, y1, color));
|
|
55
|
+
|
|
56
|
+
// Draw small ticks at ends (perpendicular)
|
|
57
|
+
const isHorizontal = Math.abs(x1 - x0) > Math.abs(y1 - y0);
|
|
58
|
+
if (isHorizontal) {
|
|
59
|
+
commands.push(shapes.drawLine(x0, y0 - 2, x0, y0 + 2, color));
|
|
60
|
+
commands.push(shapes.drawLine(x1, y1 - 2, x1, y1 + 2, color));
|
|
61
|
+
} else {
|
|
62
|
+
commands.push(shapes.drawLine(x0 - 2, y0, x0 + 2, y0, color));
|
|
63
|
+
commands.push(shapes.drawLine(x1 - 2, y1, x1 + 2, y1, color));
|
|
64
|
+
}
|
|
65
|
+
return commands;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = {
|
|
69
|
+
drawTrapezoid,
|
|
70
|
+
drawRhombus,
|
|
71
|
+
drawAxis,
|
|
72
|
+
drawDistance
|
|
73
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
const { registerTool, sendCommand } = require('../../core/server');
|
|
3
|
+
const advLessons = require('./advanced_lessons');
|
|
4
|
+
|
|
5
|
+
const hexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/).describe('Hex color e.g. #ff0000');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Register all advanced art-tree tools on the MCP server.
|
|
9
|
+
* @param {McpServer} server
|
|
10
|
+
*/
|
|
11
|
+
function register(server) {
|
|
12
|
+
registerTool(server, 'art_tree_lesson_ratios',
|
|
13
|
+
'Lesson: Đặc tính - Tỉ lệ hình (Ratios)',
|
|
14
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#2196f3') },
|
|
15
|
+
async (p) => {
|
|
16
|
+
const commands = advLessons.lessonShapeRatios(p.canvasSize, p.color);
|
|
17
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
18
|
+
return { content: [{ type: 'text', text: `✓ Bài học tỉ lệ hình hoàn thành` }] };
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
registerTool(server, 'art_tree_lesson_symmetry',
|
|
22
|
+
'Lesson: Đặc tính - Đối xứng & Trục (Symmetry and Axes)',
|
|
23
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#9c27b0') },
|
|
24
|
+
async (p) => {
|
|
25
|
+
const commands = advLessons.lessonSymmetryAndAxis(p.canvasSize, p.color);
|
|
26
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
27
|
+
return { content: [{ type: 'text', text: `✓ Bài học đối xứng & trục hoàn thành` }] };
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
registerTool(server, 'art_tree_lesson_angles',
|
|
31
|
+
'Lesson: Đặc tính - Góc (Angles)',
|
|
32
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#ff5722') },
|
|
33
|
+
async (p) => {
|
|
34
|
+
const commands = advLessons.lessonAngles(p.canvasSize, p.color);
|
|
35
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
36
|
+
return { content: [{ type: 'text', text: `✓ Bài học góc hoàn thành` }] };
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
registerTool(server, 'art_tree_lesson_distances',
|
|
40
|
+
'Lesson: Đặc tính - Khoảng cách (Distances)',
|
|
41
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#00bcd4') },
|
|
42
|
+
async (p) => {
|
|
43
|
+
const commands = advLessons.lessonDistances(p.canvasSize, p.color);
|
|
44
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
45
|
+
return { content: [{ type: 'text', text: `✓ Bài học khoảng cách hoàn thành` }] };
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
registerTool(server, 'art_tree_lesson_relationships',
|
|
49
|
+
'Lesson: Đặc tính - Quan hệ hình (Relationships)',
|
|
50
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#607d8b') },
|
|
51
|
+
async (p) => {
|
|
52
|
+
const commands = advLessons.lessonShapeRelationships(p.canvasSize, p.color);
|
|
53
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
54
|
+
return { content: [{ type: 'text', text: `✓ Bài học quan hệ hình hoàn thành` }] };
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
registerTool(server, 'art_tree_lesson_adv_catalog',
|
|
58
|
+
'Get the catalog of all available Advanced Art Tree lessons',
|
|
59
|
+
{},
|
|
60
|
+
() => {
|
|
61
|
+
const catalog = advLessons.getAdvancedLessonCatalog();
|
|
62
|
+
const text = catalog.map(l =>
|
|
63
|
+
` • ${l.id}: ${l.name} — ${l.description}`
|
|
64
|
+
).join('\n');
|
|
65
|
+
return { content: [{ type: 'text', text: `📚 Danh sách bài học Art Tree (Đặc tính):\n\n${text}` }] };
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = { register };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const csShapes = require('./cross_section_shapes');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lesson: Bottle Sections (Base, Body, Shoulder, Neck, Mouth)
|
|
5
|
+
*/
|
|
6
|
+
function lessonBottleSections(canvasSize = 32, color = '#2196f3') {
|
|
7
|
+
const cx = Math.floor(canvasSize / 2);
|
|
8
|
+
const totalH = Math.floor(canvasSize * 0.8);
|
|
9
|
+
const topY = Math.floor(canvasSize * 0.1);
|
|
10
|
+
const bottomY = topY + totalH;
|
|
11
|
+
|
|
12
|
+
const sections = [
|
|
13
|
+
{ y: topY, r: Math.floor(canvasSize * 0.08) }, // Mouth (slightly open)
|
|
14
|
+
{ y: topY + Math.floor(totalH * 0.1), r: Math.floor(canvasSize * 0.06) }, // Neck (narrow)
|
|
15
|
+
{ y: topY + Math.floor(totalH * 0.3), r: Math.floor(canvasSize * 0.2) }, // Shoulder (shrinking to body)
|
|
16
|
+
{ y: topY + Math.floor(totalH * 0.6), r: Math.floor(canvasSize * 0.25) }, // Body (wide)
|
|
17
|
+
{ y: bottomY, r: Math.floor(canvasSize * 0.25) } // Base (wide)
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
return csShapes.drawLoftedForm(cx, topY, bottomY, sections, color, true);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Lesson: Glass Sections (Base, Mid, Mouth)
|
|
25
|
+
*/
|
|
26
|
+
function lessonGlassSections(canvasSize = 32, color = '#9c27b0') {
|
|
27
|
+
const cx = Math.floor(canvasSize / 2);
|
|
28
|
+
const totalH = Math.floor(canvasSize * 0.6);
|
|
29
|
+
const topY = Math.floor(canvasSize * 0.2);
|
|
30
|
+
const bottomY = topY + totalH;
|
|
31
|
+
|
|
32
|
+
const sections = [
|
|
33
|
+
{ y: topY, r: Math.floor(canvasSize * 0.3) }, // Mouth (wide)
|
|
34
|
+
{ y: topY + Math.floor(totalH * 0.5), r: Math.floor(canvasSize * 0.2) }, // Mid body (widening)
|
|
35
|
+
{ y: bottomY, r: Math.floor(canvasSize * 0.15) } // Base (small)
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
return csShapes.drawLoftedForm(cx, topY, bottomY, sections, color, true);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Lesson: Head Sections (Cranium, Face, Jaw)
|
|
43
|
+
*/
|
|
44
|
+
function lessonHeadSections(canvasSize = 32, color = '#ff9800') {
|
|
45
|
+
const cx = Math.floor(canvasSize / 2);
|
|
46
|
+
const cy = Math.floor(canvasSize / 2);
|
|
47
|
+
const height = Math.floor(canvasSize * 0.8);
|
|
48
|
+
|
|
49
|
+
return csShapes.drawHeadStructure(cx, cy, height, color);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getCrossSectionLessonCatalog() {
|
|
53
|
+
return [
|
|
54
|
+
{ id: 'cs_bottle', name: 'Bottle Cross-sections', description: 'Base, body, shoulder, neck, mouth', fn: lessonBottleSections },
|
|
55
|
+
{ id: 'cs_glass', name: 'Glass Cross-sections', description: 'Base, body, mouth', fn: lessonGlassSections },
|
|
56
|
+
{ id: 'cs_head', name: 'Head Cross-sections', description: 'Cranium, face, jaw', fn: lessonHeadSections },
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = {
|
|
61
|
+
lessonBottleSections,
|
|
62
|
+
lessonGlassSections,
|
|
63
|
+
lessonHeadSections,
|
|
64
|
+
getCrossSectionLessonCatalog
|
|
65
|
+
};
|