@pixel-normal-edit/mcp 2.0.7 → 2.0.10
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/advanced_lessons.js +11 -11
- package/domains/art-tree/advanced_tools.js +12 -12
- 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/hidden_lessons.js +3 -3
- package/domains/art-tree/index.js +10 -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 +19 -19
- package/domains/art-tree/light_lessons.js +3 -3
- package/domains/art-tree/light_shapes.js +5 -5
- 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 +1 -1
- package/domains/art-tree/perspective_tools.js +1 -1
- 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/surface_lessons.js +7 -7
- package/domains/art-tree/tools.js +27 -27
- package/domains/art-tree/transform_lessons_1.js +2 -2
- package/domains/art-tree/transform_lessons_2.js +4 -4
- package/domains/art-tree/transform_tools.js +8 -8
- 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/index.js +4 -0
- package/package.json +1 -1
- package/rules/README.md +403 -0
- package/rules/index.js +34 -0
- package/rules/prerequisites.js +145 -0
- package/rules/rules.js +111 -0
- package/rules/workflow.js +200 -0
- package/tools/{anchors.js → anchor-tools.js} +1 -1
- package/tools/{animation.js → animation-tools.js} +1 -1
- package/tools/{canvas.js → canvas-tools.js} +1 -1
- package/tools/{colors.js → color-tools.js} +1 -1
- package/tools/{drawing.js → drawing-tools.js} +1 -1
- package/tools/{filters.js → filter-tools.js} +1 -1
- package/tools/{health.js → health-tools.js} +1 -1
- package/tools/{history.js → history-tools.js} +1 -1
- package/tools/index.js +31 -13
- package/tools/layer-tools.js +80 -0
- package/tools/{modes.js → mode-tools.js} +1 -1
- package/tools/{query.js → query-tools.js} +1 -1
- package/tools/{region.js → region-tools.js} +1 -1
- package/tools/{sprite.js → sprite-tools.js} +1 -1
- package/tools/{workspace.js → workspace-tools.js} +2 -3
|
@@ -2,7 +2,7 @@ const shapes = require('./shapes');
|
|
|
2
2
|
const advShapes = require('./advanced_shapes');
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Lesson:
|
|
5
|
+
* Lesson: Shape Ratios
|
|
6
6
|
*/
|
|
7
7
|
function lessonShapeRatios(canvasSize = 32, color = '#2196f3') {
|
|
8
8
|
const margin = Math.floor(canvasSize * 0.1);
|
|
@@ -20,7 +20,7 @@ function lessonShapeRatios(canvasSize = 32, color = '#2196f3') {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* Lesson:
|
|
23
|
+
* Lesson: Symmetry & Axes
|
|
24
24
|
*/
|
|
25
25
|
function lessonSymmetryAndAxis(canvasSize = 32, color = '#9c27b0') {
|
|
26
26
|
const mid = Math.floor(canvasSize / 2);
|
|
@@ -39,7 +39,7 @@ function lessonSymmetryAndAxis(canvasSize = 32, color = '#9c27b0') {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
* Lesson:
|
|
42
|
+
* Lesson: Angles
|
|
43
43
|
*/
|
|
44
44
|
function lessonAngles(canvasSize = 32, color = '#ff5722') {
|
|
45
45
|
const mid = Math.floor(canvasSize / 2);
|
|
@@ -62,7 +62,7 @@ function lessonAngles(canvasSize = 32, color = '#ff5722') {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
/**
|
|
65
|
-
* Lesson:
|
|
65
|
+
* Lesson: Distances
|
|
66
66
|
*/
|
|
67
67
|
function lessonDistances(canvasSize = 32, color = '#00bcd4') {
|
|
68
68
|
const margin = Math.floor(canvasSize * 0.2);
|
|
@@ -81,7 +81,7 @@ function lessonDistances(canvasSize = 32, color = '#00bcd4') {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
/**
|
|
84
|
-
* Lesson:
|
|
84
|
+
* Lesson: Shape Relationships
|
|
85
85
|
*/
|
|
86
86
|
function lessonShapeRelationships(canvasSize = 32, color = '#607d8b') {
|
|
87
87
|
const mid = Math.floor(canvasSize / 2);
|
|
@@ -106,11 +106,11 @@ function lessonShapeRelationships(canvasSize = 32, color = '#607d8b') {
|
|
|
106
106
|
*/
|
|
107
107
|
function getAdvancedLessonCatalog() {
|
|
108
108
|
return [
|
|
109
|
-
{ id: 'adv_ratios', name: '
|
|
110
|
-
{ id: 'adv_symmetry', name: '
|
|
111
|
-
{ id: 'adv_angles', name: '
|
|
112
|
-
{ id: 'adv_distances', name: '
|
|
113
|
-
{ id: 'adv_relationships', name: '
|
|
109
|
+
{ id: 'adv_ratios', name: 'Shape Ratios', description: 'Width-to-height ratios of shapes', fn: lessonShapeRatios },
|
|
110
|
+
{ id: 'adv_symmetry', name: 'Symmetry & Axes', description: 'Symmetry and central axes', fn: lessonSymmetryAndAxis },
|
|
111
|
+
{ id: 'adv_angles', name: 'Angles', description: 'Acute, right, and obtuse angles', fn: lessonAngles },
|
|
112
|
+
{ id: 'adv_distances', name: 'Distances', description: 'Distances between points', fn: lessonDistances },
|
|
113
|
+
{ id: 'adv_relationships', name: 'Shape Relationships', description: 'Overlap and nesting of shapes', fn: lessonShapeRelationships },
|
|
114
114
|
];
|
|
115
115
|
}
|
|
116
116
|
|
|
@@ -121,4 +121,4 @@ module.exports = {
|
|
|
121
121
|
lessonDistances,
|
|
122
122
|
lessonShapeRelationships,
|
|
123
123
|
getAdvancedLessonCatalog
|
|
124
|
-
};
|
|
124
|
+
};
|
|
@@ -10,48 +10,48 @@ const hexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/).describe('Hex color e.g.
|
|
|
10
10
|
*/
|
|
11
11
|
function register(server) {
|
|
12
12
|
registerTool(server, 'art_tree_lesson_ratios',
|
|
13
|
-
'Lesson:
|
|
13
|
+
'Lesson: Properties - Shape Ratios',
|
|
14
14
|
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#2196f3') },
|
|
15
15
|
async (p) => {
|
|
16
16
|
const commands = advLessons.lessonShapeRatios(p.canvasSize, p.color);
|
|
17
17
|
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
18
|
-
return { content: [{ type: 'text', text: `✓
|
|
18
|
+
return { content: [{ type: 'text', text: `✓ Shape Ratios lesson completed` }] };
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
registerTool(server, 'art_tree_lesson_symmetry',
|
|
22
|
-
'Lesson:
|
|
22
|
+
'Lesson: Properties - Symmetry & Axes',
|
|
23
23
|
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#9c27b0') },
|
|
24
24
|
async (p) => {
|
|
25
25
|
const commands = advLessons.lessonSymmetryAndAxis(p.canvasSize, p.color);
|
|
26
26
|
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
27
|
-
return { content: [{ type: 'text', text: `✓
|
|
27
|
+
return { content: [{ type: 'text', text: `✓ Symmetry & Axes lesson completed` }] };
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
registerTool(server, 'art_tree_lesson_angles',
|
|
31
|
-
'Lesson:
|
|
31
|
+
'Lesson: Properties - Angles',
|
|
32
32
|
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#ff5722') },
|
|
33
33
|
async (p) => {
|
|
34
34
|
const commands = advLessons.lessonAngles(p.canvasSize, p.color);
|
|
35
35
|
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
36
|
-
return { content: [{ type: 'text', text: `✓
|
|
36
|
+
return { content: [{ type: 'text', text: `✓ Angles lesson completed` }] };
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
registerTool(server, 'art_tree_lesson_distances',
|
|
40
|
-
'Lesson:
|
|
40
|
+
'Lesson: Properties - Distances',
|
|
41
41
|
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#00bcd4') },
|
|
42
42
|
async (p) => {
|
|
43
43
|
const commands = advLessons.lessonDistances(p.canvasSize, p.color);
|
|
44
44
|
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
45
|
-
return { content: [{ type: 'text', text: `✓
|
|
45
|
+
return { content: [{ type: 'text', text: `✓ Distances lesson completed` }] };
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
registerTool(server, 'art_tree_lesson_relationships',
|
|
49
|
-
'Lesson:
|
|
49
|
+
'Lesson: Properties - Shape Relationships',
|
|
50
50
|
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#607d8b') },
|
|
51
51
|
async (p) => {
|
|
52
52
|
const commands = advLessons.lessonShapeRelationships(p.canvasSize, p.color);
|
|
53
53
|
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
54
|
-
return { content: [{ type: 'text', text: `✓
|
|
54
|
+
return { content: [{ type: 'text', text: `✓ Shape Relationships lesson completed` }] };
|
|
55
55
|
});
|
|
56
56
|
|
|
57
57
|
registerTool(server, 'art_tree_lesson_adv_catalog',
|
|
@@ -62,8 +62,8 @@ function register(server) {
|
|
|
62
62
|
const text = catalog.map(l =>
|
|
63
63
|
` • ${l.id}: ${l.name} — ${l.description}`
|
|
64
64
|
).join('\n');
|
|
65
|
-
return { content: [{ type: 'text', text: `📚
|
|
65
|
+
return { content: [{ type: 'text', text: `📚 Advanced Art Tree Lesson Catalog:\n\n${text}` }] };
|
|
66
66
|
});
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
module.exports = { register };
|
|
69
|
+
module.exports = { register };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const analysisShapes = require('./analysis_shapes');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lesson: Object Analysis (Capstone)
|
|
5
|
+
* A 10-step breakdown of a Teapot.
|
|
6
|
+
* Users can supply the step number (1 to 10) to see a specific layer of analysis.
|
|
7
|
+
*/
|
|
8
|
+
function lessonObjectAnalysis(canvasSize = 32, step = 1, color = '#2196f3') {
|
|
9
|
+
const cx = Math.floor(canvasSize / 2);
|
|
10
|
+
const cy = Math.floor(canvasSize / 2);
|
|
11
|
+
|
|
12
|
+
// Ensure step is between 1 and 10
|
|
13
|
+
const validStep = Math.max(1, Math.min(10, step));
|
|
14
|
+
|
|
15
|
+
return analysisShapes.drawTeapotAnalysis(cx, cy, canvasSize, validStep, color);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getAnalysisLessonCatalog() {
|
|
19
|
+
return [
|
|
20
|
+
{
|
|
21
|
+
id: 'analysis_step',
|
|
22
|
+
name: '10-Step Object Analysis',
|
|
23
|
+
description: 'Break down a Teapot from Silhouette (1) to Lighting (10). Supply step=1..10',
|
|
24
|
+
fn: lessonObjectAnalysis
|
|
25
|
+
}
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
lessonObjectAnalysis,
|
|
31
|
+
getAnalysisLessonCatalog
|
|
32
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
const shapes = require('./shapes');
|
|
2
|
+
const lightShapes = require('./light_shapes');
|
|
3
|
+
|
|
4
|
+
// Master function to draw the Teapot at a specific analytical step
|
|
5
|
+
function drawTeapotAnalysis(cx, cy, scale, step, color = '#2196f3') {
|
|
6
|
+
const commands = [];
|
|
7
|
+
const r = Math.floor(scale * 0.3); // Main body radius
|
|
8
|
+
|
|
9
|
+
// Base outlines for the Teapot used across multiple steps
|
|
10
|
+
const drawBaseOutline = (c) => {
|
|
11
|
+
// Body (Sphere)
|
|
12
|
+
commands.push(shapes.drawCircle(cx, cy, r, c, false));
|
|
13
|
+
// Base/Foot
|
|
14
|
+
commands.push(shapes.drawLine(cx - Math.floor(r*0.6), cy + r, cx + Math.floor(r*0.6), cy + r, c));
|
|
15
|
+
commands.push(shapes.drawLine(cx - Math.floor(r*0.6), cy + r, cx - Math.floor(r*0.7), cy + r + 5, c));
|
|
16
|
+
commands.push(shapes.drawLine(cx + Math.floor(r*0.6), cy + r, cx + Math.floor(r*0.7), cy + r + 5, c));
|
|
17
|
+
commands.push(shapes.drawLine(cx - Math.floor(r*0.7), cy + r + 5, cx + Math.floor(r*0.7), cy + r + 5, c));
|
|
18
|
+
// Lid
|
|
19
|
+
commands.push(shapes.drawLine(cx - Math.floor(r*0.6), cy - r, cx + Math.floor(r*0.6), cy - r, c));
|
|
20
|
+
commands.push(shapes.drawEllipse(cx, cy - r - 5, Math.floor(r*0.6), Math.floor(r*0.2), c, false));
|
|
21
|
+
commands.push(shapes.drawCircle(cx, cy - r - 15, Math.floor(r*0.15), c, false)); // Knob
|
|
22
|
+
// Spout (Left)
|
|
23
|
+
commands.push(shapes.drawLine(cx - r, cy, cx - Math.floor(r*1.8), cy - Math.floor(r*0.8), c));
|
|
24
|
+
commands.push(shapes.drawLine(cx - Math.floor(r*0.8), cy + Math.floor(r*0.4), cx - Math.floor(r*1.5), cy - Math.floor(r*0.6), c));
|
|
25
|
+
commands.push(shapes.drawLine(cx - Math.floor(r*1.8), cy - Math.floor(r*0.8), cx - Math.floor(r*1.5), cy - Math.floor(r*0.6), c));
|
|
26
|
+
// Handle (Right)
|
|
27
|
+
const handlePts1 = [], handlePts2 = [];
|
|
28
|
+
for(let i=0; i<=10; i++) {
|
|
29
|
+
const a = -Math.PI/2 + (i/10)*Math.PI; // -90 to 90
|
|
30
|
+
handlePts1.push({x: Math.round(cx + r + Math.floor(r*0.8)*Math.cos(a)), y: Math.round(cy - Math.floor(r*0.2) + Math.floor(r*0.6)*Math.sin(a))});
|
|
31
|
+
handlePts2.push({x: Math.round(cx + r + Math.floor(r*0.5)*Math.cos(a)), y: Math.round(cy - Math.floor(r*0.2) + Math.floor(r*0.6)*Math.sin(a))});
|
|
32
|
+
}
|
|
33
|
+
commands.push(shapes.drawPolyline(handlePts1, c));
|
|
34
|
+
commands.push(shapes.drawPolyline(handlePts2, c));
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
switch (step) {
|
|
38
|
+
case 1: // Silhouette (Flat black)
|
|
39
|
+
// Since we can't easily union-fill all paths, we simulate it by drawing the base thick
|
|
40
|
+
// For this, we just draw the outline in solid black with no inner details
|
|
41
|
+
drawBaseOutline('#000000');
|
|
42
|
+
break;
|
|
43
|
+
|
|
44
|
+
case 2: // Proportions (Bounding box)
|
|
45
|
+
drawBaseOutline('#e0e0e0');
|
|
46
|
+
// Bounding box
|
|
47
|
+
const minX = cx - Math.floor(r*1.8) - 10;
|
|
48
|
+
const maxX = cx + Math.floor(r*1.8) + 10;
|
|
49
|
+
const minY = cy - r - 30;
|
|
50
|
+
const maxY = cy + r + 15;
|
|
51
|
+
commands.push(shapes.drawRectangle(minX, minY, maxX - minX, maxY - minY, color, false));
|
|
52
|
+
// Center lines of bounding box
|
|
53
|
+
commands.push(shapes.drawLine(cx, minY, cx, maxY, '#ff9800'));
|
|
54
|
+
commands.push(shapes.drawLine(minX, cy, maxX, cy, '#ff9800'));
|
|
55
|
+
break;
|
|
56
|
+
|
|
57
|
+
case 3: // Axis (X, Y, Z structural lines)
|
|
58
|
+
drawBaseOutline('#e0e0e0');
|
|
59
|
+
// Y axis
|
|
60
|
+
commands.push(shapes.drawLine(cx, cy - r - 40, cx, cy + r + 40, '#f44336'));
|
|
61
|
+
// X axis
|
|
62
|
+
commands.push(shapes.drawLine(cx - r - 40, cy, cx + r + 40, cy, '#4caf50'));
|
|
63
|
+
// Z axis (depth, diagonal)
|
|
64
|
+
commands.push(shapes.drawLine(cx - Math.floor(r*0.5), cy + Math.floor(r*0.5), cx + Math.floor(r*0.5), cy - Math.floor(r*0.5), '#2196f3'));
|
|
65
|
+
break;
|
|
66
|
+
|
|
67
|
+
case 4: // Main Masses (Primitives)
|
|
68
|
+
drawBaseOutline('#e0e0e0');
|
|
69
|
+
// Highlight the sphere
|
|
70
|
+
commands.push(shapes.drawCircle(cx, cy, r, '#e91e63', false));
|
|
71
|
+
// Highlight the spout cylinder/cone
|
|
72
|
+
commands.push(shapes.drawLine(cx - r, cy, cx - Math.floor(r*1.8), cy - Math.floor(r*0.8), '#00bcd4'));
|
|
73
|
+
commands.push(shapes.drawLine(cx - Math.floor(r*0.8), cy + Math.floor(r*0.4), cx - Math.floor(r*1.5), cy - Math.floor(r*0.6), '#00bcd4'));
|
|
74
|
+
break;
|
|
75
|
+
|
|
76
|
+
case 5: // Cross-sections
|
|
77
|
+
drawBaseOutline('#e0e0e0');
|
|
78
|
+
// Slicing ellipses on the body
|
|
79
|
+
commands.push(shapes.drawEllipse(cx, cy, r, Math.floor(r*0.3), '#9c27b0', false));
|
|
80
|
+
commands.push(shapes.drawEllipse(cx, cy - Math.floor(r*0.5), Math.floor(r*0.86), Math.floor(r*0.25), '#9c27b0', false));
|
|
81
|
+
commands.push(shapes.drawEllipse(cx, cy + Math.floor(r*0.5), Math.floor(r*0.86), Math.floor(r*0.25), '#9c27b0', false));
|
|
82
|
+
break;
|
|
83
|
+
|
|
84
|
+
case 6: // Transformations (Bend, Taper)
|
|
85
|
+
drawBaseOutline('#e0e0e0');
|
|
86
|
+
// Highlight bending spout
|
|
87
|
+
commands.push(shapes.drawEllipse(cx - Math.floor(r*1.4), cy - Math.floor(r*0.4), Math.floor(r*0.4), Math.floor(r*0.2), '#ff9800', false));
|
|
88
|
+
// Tapering lid
|
|
89
|
+
commands.push(shapes.drawLine(cx - Math.floor(r*0.6), cy - r, cx, cy - r - 15, '#ff9800'));
|
|
90
|
+
commands.push(shapes.drawLine(cx + Math.floor(r*0.6), cy - r, cx, cy - r - 15, '#ff9800'));
|
|
91
|
+
break;
|
|
92
|
+
|
|
93
|
+
case 7: // Secondary parts
|
|
94
|
+
drawBaseOutline('#e0e0e0');
|
|
95
|
+
// Boldly highlight spout and handle
|
|
96
|
+
commands.push(shapes.drawLine(cx - r, cy, cx - Math.floor(r*1.8), cy - Math.floor(r*0.8), '#ff5722'));
|
|
97
|
+
commands.push(shapes.drawLine(cx - Math.floor(r*0.8), cy + Math.floor(r*0.4), cx - Math.floor(r*1.5), cy - Math.floor(r*0.6), '#ff5722'));
|
|
98
|
+
const hPts = [];
|
|
99
|
+
for(let i=0; i<=10; i++) {
|
|
100
|
+
const a = -Math.PI/2 + (i/10)*Math.PI;
|
|
101
|
+
hPts.push({x: Math.round(cx + r + Math.floor(r*0.8)*Math.cos(a)), y: Math.round(cy - Math.floor(r*0.2) + Math.floor(r*0.6)*Math.sin(a))});
|
|
102
|
+
}
|
|
103
|
+
commands.push(shapes.drawPolyline(hPts, '#3f51b5'));
|
|
104
|
+
break;
|
|
105
|
+
|
|
106
|
+
case 8: // Surfaces (Convex, Concave)
|
|
107
|
+
drawBaseOutline('#e0e0e0');
|
|
108
|
+
// Convex body
|
|
109
|
+
commands.push(shapes.drawCircle(cx, cy, r, '#8bc34a', false));
|
|
110
|
+
// Concave inner handle space
|
|
111
|
+
commands.push(shapes.drawEllipse(cx + r + Math.floor(r*0.2), cy - Math.floor(r*0.2), Math.floor(r*0.3), Math.floor(r*0.5), '#00bcd4', false));
|
|
112
|
+
break;
|
|
113
|
+
|
|
114
|
+
case 9: // Perspective
|
|
115
|
+
drawBaseOutline(color);
|
|
116
|
+
// Horizon line above
|
|
117
|
+
const horizonY = cy - r - 50;
|
|
118
|
+
const vpX = cx + Math.floor(r*2);
|
|
119
|
+
commands.push(shapes.drawLine(0, horizonY, scale*2, horizonY, '#9e9e9e'));
|
|
120
|
+
commands.push(shapes.drawCircle(vpX, horizonY, 3, '#ff0000', true));
|
|
121
|
+
// Orthogonals from top and bottom to VP
|
|
122
|
+
commands.push(shapes.drawLine(cx, cy - r, vpX, horizonY, '#bdbdbd'));
|
|
123
|
+
commands.push(shapes.drawLine(cx, cy + r, vpX, horizonY, '#bdbdbd'));
|
|
124
|
+
break;
|
|
125
|
+
|
|
126
|
+
case 10: // Lighting
|
|
127
|
+
// Re-use shaded sphere for the main body
|
|
128
|
+
commands.push(...lightShapes.drawShadedSphere(cx, cy, r, -Math.PI*0.75));
|
|
129
|
+
// We skip the detailed spout/handle shading to stay within line limits,
|
|
130
|
+
// but draw them solidly over the sphere to complete the form
|
|
131
|
+
const c = '#546e7a';
|
|
132
|
+
commands.push(shapes.drawLine(cx - r, cy, cx - Math.floor(r*1.8), cy - Math.floor(r*0.8), c));
|
|
133
|
+
commands.push(shapes.drawLine(cx - Math.floor(r*0.8), cy + Math.floor(r*0.4), cx - Math.floor(r*1.5), cy - Math.floor(r*0.6), c));
|
|
134
|
+
break;
|
|
135
|
+
|
|
136
|
+
default:
|
|
137
|
+
drawBaseOutline(color);
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return commands;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = {
|
|
145
|
+
drawTeapotAnalysis
|
|
146
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
const { registerTool, sendCommand } = require('../../core/server');
|
|
3
|
+
const analysisLessons = require('./analysis_lessons');
|
|
4
|
+
|
|
5
|
+
const hexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/).describe('Hex color e.g. #ff0000');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Register all analysis tools on the MCP server.
|
|
9
|
+
* @param {McpServer} server
|
|
10
|
+
*/
|
|
11
|
+
function register(server) {
|
|
12
|
+
registerTool(server, 'art_tree_lesson_analysis_step',
|
|
13
|
+
'Lesson: Capstone - 10-Step Object Analysis of a Teapot. Pass step (1 to 10).',
|
|
14
|
+
{
|
|
15
|
+
canvasSize: z.number().int().min(16).max(64).default(32),
|
|
16
|
+
step: z.number().int().min(1).max(10).describe('The analysis step to view (1=Silhouette, 2=Proportions, ..., 10=Lighting)'),
|
|
17
|
+
color: hexColor.default('#2196f3')
|
|
18
|
+
},
|
|
19
|
+
async (p) => {
|
|
20
|
+
const commands = analysisLessons.lessonObjectAnalysis(p.canvasSize, p.step, p.color);
|
|
21
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
22
|
+
const steps = [
|
|
23
|
+
"1. Silhouette (Hình bao ngoài)",
|
|
24
|
+
"2. Proportions (Tỉ lệ)",
|
|
25
|
+
"3. Axis (Trục)",
|
|
26
|
+
"4. Main masses (Khối lớn)",
|
|
27
|
+
"5. Cross-sections (Mặt cắt)",
|
|
28
|
+
"6. Transformations (Biến dạng)",
|
|
29
|
+
"7. Secondary parts (Các bộ phận phụ)",
|
|
30
|
+
"8. Surfaces (Bề mặt)",
|
|
31
|
+
"9. Perspective (Phối cảnh)",
|
|
32
|
+
"10. Lighting (Ánh sáng)"
|
|
33
|
+
];
|
|
34
|
+
return { content: [{ type: 'text', text: `✓ Step ${p.step} Analysis completed: ${steps[p.step - 1]}` }] };
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
registerTool(server, 'art_tree_lesson_analysis_catalog',
|
|
38
|
+
'Get the catalog of all available Analysis lessons',
|
|
39
|
+
{},
|
|
40
|
+
() => {
|
|
41
|
+
const catalog = analysisLessons.getAnalysisLessonCatalog();
|
|
42
|
+
const text = catalog.map(l =>
|
|
43
|
+
` • ${l.id}: ${l.name} — ${l.description}`
|
|
44
|
+
).join('\n');
|
|
45
|
+
return { content: [{ type: 'text', text: `📚 Analysis Lessons:\n\n${text}` }] };
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = { register };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const hiddenShapes = require('./hidden_shapes');
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Lesson: Hidden Box (
|
|
4
|
+
* Lesson: Hidden Box (6-sided box)
|
|
5
5
|
* You see 3 faces, but you must understand all 6.
|
|
6
6
|
*/
|
|
7
7
|
function lessonHiddenBox(canvasSize = 32, visibleColor = '#2196f3', hiddenColor = '#e0e0e0') {
|
|
@@ -15,7 +15,7 @@ function lessonHiddenBox(canvasSize = 32, visibleColor = '#2196f3', hiddenColor
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* Lesson: Hidden Head (
|
|
18
|
+
* Lesson: Hidden Head (Human head and skull)
|
|
19
19
|
* You don't see the whole skull, but must understand the structure behind it.
|
|
20
20
|
*/
|
|
21
21
|
function lessonHiddenHead(canvasSize = 32, visibleColor = '#ff5722', hiddenColor = '#e0e0e0') {
|
|
@@ -27,7 +27,7 @@ function lessonHiddenHead(canvasSize = 32, visibleColor = '#ff5722', hiddenColor
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
* Lesson: Hidden Cup (
|
|
30
|
+
* Lesson: Hidden Cup (Cup and inner bottom)
|
|
31
31
|
* You don't see the full inside bottom, but must understand how the wall goes down.
|
|
32
32
|
*/
|
|
33
33
|
function lessonHiddenCup(canvasSize = 32, visibleColor = '#4caf50', hiddenColor = '#e0e0e0') {
|
|
@@ -21,6 +21,11 @@ const surfaceTools = require('./surface_tools');
|
|
|
21
21
|
const perspTools = require('./perspective_tools');
|
|
22
22
|
const hiddenTools = require('./hidden_tools');
|
|
23
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');
|
|
24
29
|
|
|
25
30
|
/**
|
|
26
31
|
* Register all Art Tree MCP tools on the server
|
|
@@ -39,6 +44,11 @@ function registerAll(server) {
|
|
|
39
44
|
perspTools.register(server);
|
|
40
45
|
hiddenTools.register(server);
|
|
41
46
|
lightTools.register(server);
|
|
47
|
+
materialTools.register(server);
|
|
48
|
+
analysisTools.register(server);
|
|
49
|
+
layerTools.register(server);
|
|
50
|
+
skyTools.register(server);
|
|
51
|
+
vocabTools.register(server);
|
|
42
52
|
}
|
|
43
53
|
|
|
44
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 };
|