@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,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 };
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const shapes = require('./shapes');
|
|
2
|
+
const ellipseShapes = require('./ellipse_shapes');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Draw a lofted 3D form by providing a central axis and cross-sections.
|
|
6
|
+
* @param {number} cx Center X
|
|
7
|
+
* @param {number} topY Top Y of the axis
|
|
8
|
+
* @param {number} bottomY Bottom Y of the axis
|
|
9
|
+
* @param {Array<{y: number, r: number}>} sections Slices sorted from top to bottom (or bottom to top)
|
|
10
|
+
* @param {string} color
|
|
11
|
+
* @param {boolean} drawAxis Whether to draw the central vertical axis
|
|
12
|
+
*/
|
|
13
|
+
function drawLoftedForm(cx, topY, bottomY, sections, color, drawAxis = true) {
|
|
14
|
+
const commands = [];
|
|
15
|
+
|
|
16
|
+
if (drawAxis) {
|
|
17
|
+
commands.push(shapes.drawLine(cx, topY - 5, cx, bottomY + 5, '#bdbdbd'));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Sort sections by y to ensure we can connect them properly
|
|
21
|
+
const sorted = [...sections].sort((a, b) => a.y - b.y);
|
|
22
|
+
|
|
23
|
+
// Draw ellipses and connect edges
|
|
24
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
25
|
+
const sec = sorted[i];
|
|
26
|
+
const ry = Math.max(2, Math.floor(sec.r * 0.3)); // perspective foreshortening
|
|
27
|
+
|
|
28
|
+
// Draw cross-section ellipse
|
|
29
|
+
commands.push(shapes.drawEllipse(cx, sec.y, sec.r, ry, color, false));
|
|
30
|
+
|
|
31
|
+
// Connect to next section's edges
|
|
32
|
+
if (i < sorted.length - 1) {
|
|
33
|
+
const nextSec = sorted[i + 1];
|
|
34
|
+
// Left contour
|
|
35
|
+
commands.push(shapes.drawLine(cx - sec.r, sec.y, cx - nextSec.r, nextSec.y, color));
|
|
36
|
+
// Right contour
|
|
37
|
+
commands.push(shapes.drawLine(cx + sec.r, sec.y, cx + nextSec.r, nextSec.y, color));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return commands;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Draw the structural volume of a head using cross sections
|
|
46
|
+
*/
|
|
47
|
+
function drawHeadStructure(cx, cy, height, color) {
|
|
48
|
+
const commands = [];
|
|
49
|
+
|
|
50
|
+
const topY = cy - Math.floor(height / 2);
|
|
51
|
+
const bottomY = cy + Math.floor(height / 2);
|
|
52
|
+
|
|
53
|
+
// 1. Cranium (large sphere at the top)
|
|
54
|
+
const craniumR = Math.floor(height * 0.35);
|
|
55
|
+
const craniumY = topY + craniumR;
|
|
56
|
+
commands.push(shapes.drawCircle(cx, craniumY, craniumR, color, false));
|
|
57
|
+
|
|
58
|
+
// Cranium cross sections (horizontal and vertical)
|
|
59
|
+
commands.push(shapes.drawEllipse(cx, craniumY, craniumR, Math.floor(craniumR * 0.4), '#03a9f4', false));
|
|
60
|
+
commands.push(shapes.drawEllipse(cx, craniumY, Math.floor(craniumR * 0.3), craniumR, '#03a9f4', false));
|
|
61
|
+
|
|
62
|
+
// 2. Face & Jaw (narrowing down)
|
|
63
|
+
const jawR = Math.floor(craniumR * 0.6);
|
|
64
|
+
const jawY = bottomY - Math.floor(jawR * 0.5);
|
|
65
|
+
|
|
66
|
+
// Jaw cross section
|
|
67
|
+
commands.push(shapes.drawEllipse(cx, jawY, jawR, Math.floor(jawR * 0.3), '#e91e63', false));
|
|
68
|
+
|
|
69
|
+
// Connect Cranium to Jaw (Cheekbones/Jawline)
|
|
70
|
+
// Left side
|
|
71
|
+
commands.push(shapes.drawLine(cx - craniumR, craniumY, cx - jawR, jawY, color));
|
|
72
|
+
// Right side
|
|
73
|
+
commands.push(shapes.drawLine(cx + craniumR, craniumY, cx + jawR, jawY, color));
|
|
74
|
+
|
|
75
|
+
// Central facial axis (Center line of the face)
|
|
76
|
+
commands.push(shapes.drawLine(cx, topY, cx, bottomY, '#4caf50'));
|
|
77
|
+
|
|
78
|
+
return commands;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = {
|
|
82
|
+
drawLoftedForm,
|
|
83
|
+
drawHeadStructure
|
|
84
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
const { registerTool, sendCommand } = require('../../core/server');
|
|
3
|
+
const csLessons = require('./cross_section_lessons');
|
|
4
|
+
|
|
5
|
+
const hexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/).describe('Hex color e.g. #ff0000');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Register all cross-section tools on the MCP server.
|
|
9
|
+
* @param {McpServer} server
|
|
10
|
+
*/
|
|
11
|
+
function register(server) {
|
|
12
|
+
registerTool(server, 'art_tree_lesson_cs_bottle',
|
|
13
|
+
'Lesson: Cross-sections - Bottle Analysis (Base, body, shoulder, neck, mouth)',
|
|
14
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#2196f3') },
|
|
15
|
+
async (p) => {
|
|
16
|
+
const commands = csLessons.lessonBottleSections(p.canvasSize, p.color);
|
|
17
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
18
|
+
return { content: [{ type: 'text', text: `✓ Bottle Cross-sections lesson completed` }] };
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
registerTool(server, 'art_tree_lesson_cs_glass',
|
|
22
|
+
'Lesson: Cross-sections - Glass Analysis (Base, mid, mouth)',
|
|
23
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#9c27b0') },
|
|
24
|
+
async (p) => {
|
|
25
|
+
const commands = csLessons.lessonGlassSections(p.canvasSize, p.color);
|
|
26
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
27
|
+
return { content: [{ type: 'text', text: `✓ Glass Cross-sections lesson completed` }] };
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
registerTool(server, 'art_tree_lesson_cs_head',
|
|
31
|
+
'Lesson: Cross-sections - Head Analysis (Cranium, face, jaw)',
|
|
32
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#ff9800') },
|
|
33
|
+
async (p) => {
|
|
34
|
+
const commands = csLessons.lessonHeadSections(p.canvasSize, p.color);
|
|
35
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
36
|
+
return { content: [{ type: 'text', text: `✓ Head Cross-sections lesson completed` }] };
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
registerTool(server, 'art_tree_lesson_cs_catalog',
|
|
40
|
+
'Get the catalog of all available Cross-section lessons',
|
|
41
|
+
{},
|
|
42
|
+
() => {
|
|
43
|
+
const catalog = csLessons.getCrossSectionLessonCatalog();
|
|
44
|
+
const text = catalog.map(l =>
|
|
45
|
+
` • ${l.id}: ${l.name} — ${l.description}`
|
|
46
|
+
).join('\n');
|
|
47
|
+
return { content: [{ type: 'text', text: `📚 Cross-section Lessons:\n\n${text}` }] };
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = { register };
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
const shapes = require('./shapes');
|
|
2
|
+
const curveShapes = require('./curve_shapes');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Lesson 1: Curve Types (C, S, Convex, Concave)
|
|
6
|
+
*/
|
|
7
|
+
function lessonCurveTypes(canvasSize = 32, color = '#2196f3') {
|
|
8
|
+
const margin = Math.floor(canvasSize * 0.1);
|
|
9
|
+
const commands = [];
|
|
10
|
+
|
|
11
|
+
const w = Math.floor(canvasSize / 2) - margin * 2;
|
|
12
|
+
const h = Math.floor(canvasSize / 2) - margin * 2;
|
|
13
|
+
|
|
14
|
+
// C Curve (Top Left)
|
|
15
|
+
const cP0 = { x: margin + w, y: margin };
|
|
16
|
+
const cP1 = { x: margin - w, y: margin + Math.floor(h/2) }; // Pulls curve left
|
|
17
|
+
const cP2 = { x: margin + w, y: margin + h };
|
|
18
|
+
commands.push(...curveShapes.drawQuadraticCurve(cP0, cP1, cP2, color));
|
|
19
|
+
|
|
20
|
+
// S Curve (Top Right)
|
|
21
|
+
const sP0 = { x: canvasSize - margin, y: margin };
|
|
22
|
+
const sP1 = { x: canvasSize - margin - w*2, y: margin };
|
|
23
|
+
const sP2 = { x: canvasSize - margin + w, y: margin + h };
|
|
24
|
+
const sP3 = { x: canvasSize - margin - w, y: margin + h };
|
|
25
|
+
commands.push(...curveShapes.drawCubicCurve(sP0, sP1, sP2, sP3, '#e91e63'));
|
|
26
|
+
|
|
27
|
+
// Convex (Bottom Left, viewed from bottom)
|
|
28
|
+
const cvxP0 = { x: margin, y: canvasSize - margin };
|
|
29
|
+
const cvxP1 = { x: margin + Math.floor(w/2), y: canvasSize - margin - h };
|
|
30
|
+
const cvxP2 = { x: margin + w, y: canvasSize - margin };
|
|
31
|
+
commands.push(...curveShapes.drawQuadraticCurve(cvxP0, cvxP1, cvxP2, '#4caf50'));
|
|
32
|
+
|
|
33
|
+
// Concave (Bottom Right, viewed from bottom)
|
|
34
|
+
const cnvP0 = { x: canvasSize - margin - w, y: canvasSize - margin - Math.floor(h/2) };
|
|
35
|
+
const cnvP1 = { x: canvasSize - margin - Math.floor(w/2), y: canvasSize - margin + h };
|
|
36
|
+
const cnvP2 = { x: canvasSize - margin, y: canvasSize - margin - Math.floor(h/2) };
|
|
37
|
+
commands.push(...curveShapes.drawQuadraticCurve(cnvP0, cnvP1, cnvP2, '#ff9800'));
|
|
38
|
+
|
|
39
|
+
return commands;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Lesson 2: Curve Topology (Closed, Open, Symmetrical, Asymmetrical)
|
|
44
|
+
*/
|
|
45
|
+
function lessonCurveTopology(canvasSize = 32, color = '#9c27b0') {
|
|
46
|
+
const mid = Math.floor(canvasSize / 2);
|
|
47
|
+
const q = Math.floor(canvasSize / 4);
|
|
48
|
+
const commands = [];
|
|
49
|
+
|
|
50
|
+
// Closed Curve (Circle-like using Bezier for demonstration)
|
|
51
|
+
// Since we already have drawCircle, we just draw a circle to represent a closed curve
|
|
52
|
+
commands.push(shapes.drawCircle(q, q, Math.floor(q*0.8), color, false));
|
|
53
|
+
|
|
54
|
+
// Open Curve (Quadratic)
|
|
55
|
+
commands.push(...curveShapes.drawQuadraticCurve(
|
|
56
|
+
{ x: canvasSize - q*1.5, y: q },
|
|
57
|
+
{ x: canvasSize - q*0.5, y: q - q/2 },
|
|
58
|
+
{ x: canvasSize - q*0.5, y: q + q/2 },
|
|
59
|
+
'#3f51b5'
|
|
60
|
+
));
|
|
61
|
+
|
|
62
|
+
// Symmetrical Curve (Parabola-like)
|
|
63
|
+
commands.push(...curveShapes.drawQuadraticCurve(
|
|
64
|
+
{ x: q*0.5, y: canvasSize - q*0.5 },
|
|
65
|
+
{ x: q, y: canvasSize - q*1.5 },
|
|
66
|
+
{ x: q*1.5, y: canvasSize - q*0.5 },
|
|
67
|
+
'#00bcd4'
|
|
68
|
+
));
|
|
69
|
+
|
|
70
|
+
// Asymmetrical Curve
|
|
71
|
+
commands.push(...curveShapes.drawQuadraticCurve(
|
|
72
|
+
{ x: canvasSize - q*1.5, y: canvasSize - q*0.5 },
|
|
73
|
+
{ x: canvasSize - q*0.2, y: canvasSize - q*1.8 }, // Pull point is skewed
|
|
74
|
+
{ x: canvasSize - q*0.5, y: canvasSize - q*0.5 },
|
|
75
|
+
'#ff5722'
|
|
76
|
+
));
|
|
77
|
+
|
|
78
|
+
return commands;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Lesson 3: Curve Properties (Start, Direction, Curvature, Inflection, Transition)
|
|
83
|
+
*/
|
|
84
|
+
function lessonCurveProperties(canvasSize = 32, color = '#607d8b') {
|
|
85
|
+
const margin = Math.floor(canvasSize * 0.1);
|
|
86
|
+
const commands = [];
|
|
87
|
+
|
|
88
|
+
// 1. Start point and Direction (Top Left Curve)
|
|
89
|
+
const p0 = { x: margin, y: margin * 2 };
|
|
90
|
+
const p1 = { x: margin + canvasSize*0.3, y: margin };
|
|
91
|
+
const p2 = { x: margin + canvasSize*0.3, y: margin + canvasSize*0.3 };
|
|
92
|
+
commands.push(...curveShapes.drawQuadraticCurve(p0, p1, p2, color));
|
|
93
|
+
commands.push(curveShapes.drawStartMarker(p0.x, p0.y, '#00ff00')); // Green dot at start
|
|
94
|
+
commands.push(...curveShapes.drawDirectionArrow(p0, p1, p2, null, '#ff0000')); // Red arrow
|
|
95
|
+
|
|
96
|
+
// 2. Curvature comparison (Bottom Left)
|
|
97
|
+
// Strong vs Weak curve with same endpoints
|
|
98
|
+
const bp0 = { x: margin, y: canvasSize - margin };
|
|
99
|
+
const bp2 = { x: margin + canvasSize*0.4, y: canvasSize - margin };
|
|
100
|
+
// Weak curve
|
|
101
|
+
commands.push(...curveShapes.drawQuadraticCurve(bp0, { x: margin + canvasSize*0.2, y: canvasSize - margin - canvasSize*0.1 }, bp2, '#8bc34a'));
|
|
102
|
+
// Strong curve
|
|
103
|
+
commands.push(...curveShapes.drawQuadraticCurve(bp0, { x: margin + canvasSize*0.2, y: canvasSize - margin - canvasSize*0.4 }, bp2, '#388e3c'));
|
|
104
|
+
|
|
105
|
+
// 3. Inflection point (Top Right S Curve)
|
|
106
|
+
const sP0 = { x: canvasSize - margin - canvasSize*0.3, y: margin };
|
|
107
|
+
const sP1 = { x: canvasSize - margin, y: margin };
|
|
108
|
+
const sP2 = { x: canvasSize - margin - canvasSize*0.4, y: margin + canvasSize*0.3 };
|
|
109
|
+
const sP3 = { x: canvasSize - margin, y: margin + canvasSize*0.3 };
|
|
110
|
+
commands.push(...curveShapes.drawCubicCurve(sP0, sP1, sP2, sP3, '#03a9f4'));
|
|
111
|
+
// Inflection point roughly at t=0.5
|
|
112
|
+
const inflection = curveShapes.cubicBezier(sP0, sP1, sP2, sP3, 0.5);
|
|
113
|
+
commands.push(curveShapes.drawInflectionMarker(inflection.x, inflection.y, '#ff9800'));
|
|
114
|
+
|
|
115
|
+
// 4. Transition (Bottom Right - line transitioning to curve)
|
|
116
|
+
const startLine = { x: canvasSize - margin - canvasSize*0.3, y: canvasSize - margin - canvasSize*0.2 };
|
|
117
|
+
const transPt = { x: canvasSize - margin - canvasSize*0.1, y: canvasSize - margin - canvasSize*0.2 };
|
|
118
|
+
commands.push(shapes.drawLine(startLine.x, startLine.y, transPt.x, transPt.y, '#9e9e9e'));
|
|
119
|
+
// Curve starting tangentially
|
|
120
|
+
commands.push(...curveShapes.drawQuadraticCurve(transPt, { x: canvasSize - margin, y: canvasSize - margin - canvasSize*0.2 }, { x: canvasSize - margin, y: canvasSize - margin }, '#795548'));
|
|
121
|
+
|
|
122
|
+
return commands;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function getCurveLessonCatalog() {
|
|
126
|
+
return [
|
|
127
|
+
{ id: 'curve_types', name: 'Curve Types', description: 'C, S, Convex, and Concave curves', fn: lessonCurveTypes },
|
|
128
|
+
{ id: 'curve_topology', name: 'Curve Topology', description: 'Closed, Open, Symmetrical, Asymmetrical curves', fn: lessonCurveTopology },
|
|
129
|
+
{ id: 'curve_properties', name: 'Curve Properties', description: 'Start point, Direction, Curvature, Inflection, and Transitions', fn: lessonCurveProperties },
|
|
130
|
+
];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = {
|
|
134
|
+
lessonCurveTypes,
|
|
135
|
+
lessonCurveTopology,
|
|
136
|
+
lessonCurveProperties,
|
|
137
|
+
getCurveLessonCatalog
|
|
138
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const shapes = require('./shapes');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Draw a marker at the start of a curve
|
|
5
|
+
*/
|
|
6
|
+
function drawStartMarker(x, y, color = '#00ff00') {
|
|
7
|
+
return shapes.drawCircle(x, y, 2, color, true);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Draw an inflection point marker
|
|
12
|
+
*/
|
|
13
|
+
function drawInflectionMarker(x, y, color = '#ff0000') {
|
|
14
|
+
return shapes.drawCircle(x, y, 2, color, true);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Calculate quadratic bezier point
|
|
19
|
+
*/
|
|
20
|
+
function quadraticBezier(p0, p1, p2, t) {
|
|
21
|
+
const x = Math.round((1 - t) * (1 - t) * p0.x + 2 * (1 - t) * t * p1.x + t * t * p2.x);
|
|
22
|
+
const y = Math.round((1 - t) * (1 - t) * p0.y + 2 * (1 - t) * t * p1.y + t * t * p2.y);
|
|
23
|
+
return { x, y };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Draw a quadratic bezier curve as a polyline
|
|
28
|
+
*/
|
|
29
|
+
function drawQuadraticCurve(p0, p1, p2, color, segments = 20) {
|
|
30
|
+
const points = [];
|
|
31
|
+
for (let i = 0; i <= segments; i++) {
|
|
32
|
+
const t = i / segments;
|
|
33
|
+
points.push(quadraticBezier(p0, p1, p2, t));
|
|
34
|
+
}
|
|
35
|
+
return shapes.drawPolyline(points, color);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Calculate cubic bezier point
|
|
40
|
+
*/
|
|
41
|
+
function cubicBezier(p0, p1, p2, p3, t) {
|
|
42
|
+
const x = Math.round(Math.pow(1 - t, 3) * p0.x + 3 * Math.pow(1 - t, 2) * t * p1.x + 3 * (1 - t) * t * t * p2.x + t * t * t * p3.x);
|
|
43
|
+
const y = Math.round(Math.pow(1 - t, 3) * p0.y + 3 * Math.pow(1 - t, 2) * t * p1.y + 3 * (1 - t) * t * t * p2.y + t * t * t * p3.y);
|
|
44
|
+
return { x, y };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Draw a cubic bezier curve as a polyline
|
|
49
|
+
*/
|
|
50
|
+
function drawCubicCurve(p0, p1, p2, p3, color, segments = 30) {
|
|
51
|
+
const points = [];
|
|
52
|
+
for (let i = 0; i <= segments; i++) {
|
|
53
|
+
const t = i / segments;
|
|
54
|
+
points.push(cubicBezier(p0, p1, p2, p3, t));
|
|
55
|
+
}
|
|
56
|
+
return shapes.drawPolyline(points, color);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Draw a direction arrow roughly along the curve at t=0.5
|
|
61
|
+
*/
|
|
62
|
+
function drawDirectionArrow(p0, p1, p2, p3 = null, color = '#ff0000') {
|
|
63
|
+
const commands = [];
|
|
64
|
+
const t = 0.5;
|
|
65
|
+
const pt = p3 ? cubicBezier(p0, p1, p2, p3, t) : quadraticBezier(p0, p1, p2, t);
|
|
66
|
+
const ptNext = p3 ? cubicBezier(p0, p1, p2, p3, t + 0.1) : quadraticBezier(p0, p1, p2, t + 0.1);
|
|
67
|
+
|
|
68
|
+
// Calculate angle
|
|
69
|
+
const angle = Math.atan2(ptNext.y - pt.y, ptNext.x - pt.x);
|
|
70
|
+
|
|
71
|
+
// Draw arrow head
|
|
72
|
+
const arrowLen = 4;
|
|
73
|
+
const a1 = angle + Math.PI * 0.8;
|
|
74
|
+
const a2 = angle - Math.PI * 0.8;
|
|
75
|
+
|
|
76
|
+
commands.push(shapes.drawLine(pt.x, pt.y, Math.round(pt.x + arrowLen * Math.cos(a1)), Math.round(pt.y + arrowLen * Math.sin(a1)), color));
|
|
77
|
+
commands.push(shapes.drawLine(pt.x, pt.y, Math.round(pt.x + arrowLen * Math.cos(a2)), Math.round(pt.y + arrowLen * Math.sin(a2)), color));
|
|
78
|
+
|
|
79
|
+
return commands;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = {
|
|
83
|
+
drawStartMarker,
|
|
84
|
+
drawInflectionMarker,
|
|
85
|
+
quadraticBezier,
|
|
86
|
+
drawQuadraticCurve,
|
|
87
|
+
cubicBezier,
|
|
88
|
+
drawCubicCurve,
|
|
89
|
+
drawDirectionArrow
|
|
90
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
const { registerTool, sendCommand } = require('../../core/server');
|
|
3
|
+
const curveLessons = require('./curve_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 curve tools on the MCP server.
|
|
9
|
+
* @param {McpServer} server
|
|
10
|
+
*/
|
|
11
|
+
function register(server) {
|
|
12
|
+
registerTool(server, 'art_tree_lesson_curve_types',
|
|
13
|
+
'Lesson: Advanced Curves - Types (C, S, Convex, Concave)',
|
|
14
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#2196f3') },
|
|
15
|
+
async (p) => {
|
|
16
|
+
const commands = curveLessons.lessonCurveTypes(p.canvasSize, p.color);
|
|
17
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
18
|
+
return { content: [{ type: 'text', text: `✓ Curve Types lesson completed` }] };
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
registerTool(server, 'art_tree_lesson_curve_topology',
|
|
22
|
+
'Lesson: Advanced Curves - Topology (Closed, Open, Symmetrical, Asymmetrical)',
|
|
23
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#9c27b0') },
|
|
24
|
+
async (p) => {
|
|
25
|
+
const commands = curveLessons.lessonCurveTopology(p.canvasSize, p.color);
|
|
26
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
27
|
+
return { content: [{ type: 'text', text: `✓ Curve Topology lesson completed` }] };
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
registerTool(server, 'art_tree_lesson_curve_properties',
|
|
31
|
+
'Lesson: Advanced Curves - Properties (Start, Direction, Curvature, Inflection, Transitions)',
|
|
32
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#607d8b') },
|
|
33
|
+
async (p) => {
|
|
34
|
+
const commands = curveLessons.lessonCurveProperties(p.canvasSize, p.color);
|
|
35
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
36
|
+
return { content: [{ type: 'text', text: `✓ Curve Properties lesson completed` }] };
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
registerTool(server, 'art_tree_lesson_curve_catalog',
|
|
40
|
+
'Get the catalog of all available Advanced Curve lessons',
|
|
41
|
+
{},
|
|
42
|
+
() => {
|
|
43
|
+
const catalog = curveLessons.getCurveLessonCatalog();
|
|
44
|
+
const text = catalog.map(l =>
|
|
45
|
+
` • ${l.id}: ${l.name} — ${l.description}`
|
|
46
|
+
).join('\n');
|
|
47
|
+
return { content: [{ type: 'text', text: `📚 Advanced Curve Lessons:\n\n${text}` }] };
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = { register };
|