@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,74 @@
|
|
|
1
|
+
const shapes = require('./shapes');
|
|
2
|
+
const ellipseShapes = require('./ellipse_shapes');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Draw a 3D coordinate system (X, Y, Z axes) originating from a center point
|
|
6
|
+
*/
|
|
7
|
+
function drawXYZAxes(cx, cy, length) {
|
|
8
|
+
const commands = [];
|
|
9
|
+
|
|
10
|
+
// Y-axis (Vertical) - Green
|
|
11
|
+
commands.push(shapes.drawLine(cx, cy, cx, cy - length, '#4caf50'));
|
|
12
|
+
|
|
13
|
+
// X-axis (Horizontal) - Red
|
|
14
|
+
commands.push(shapes.drawLine(cx, cy, cx + length, cy, '#f44336'));
|
|
15
|
+
|
|
16
|
+
// Z-axis (Depth - Oblique projection) - Blue
|
|
17
|
+
const dx = Math.floor(length * 0.7 * Math.cos(Math.PI / 6));
|
|
18
|
+
const dy = Math.floor(length * 0.7 * Math.sin(Math.PI / 6));
|
|
19
|
+
commands.push(shapes.drawLine(cx, cy, cx - dx, cy + dy, '#2196f3'));
|
|
20
|
+
|
|
21
|
+
// Center point
|
|
22
|
+
commands.push(shapes.drawCircle(cx, cy, 2, '#000000', true));
|
|
23
|
+
|
|
24
|
+
return commands;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Draw a complex bottle structure using cross-sections and central axis
|
|
29
|
+
*/
|
|
30
|
+
function drawBottleAnatomy(cx, cy, totalHeight, color) {
|
|
31
|
+
const commands = [];
|
|
32
|
+
|
|
33
|
+
const bottomY = cy + Math.floor(totalHeight / 2);
|
|
34
|
+
const topY = cy - Math.floor(totalHeight / 2);
|
|
35
|
+
|
|
36
|
+
// Central vertical axis
|
|
37
|
+
commands.push(shapes.drawLine(cx, topY - 10, cx, bottomY + 10, '#bdbdbd'));
|
|
38
|
+
|
|
39
|
+
// Define cross sections (y position from bottom, radius)
|
|
40
|
+
const sections = [
|
|
41
|
+
{ y: bottomY, r: Math.floor(totalHeight * 0.2) }, // Base
|
|
42
|
+
{ y: bottomY - Math.floor(totalHeight * 0.4), r: Math.floor(totalHeight * 0.2) }, // Body
|
|
43
|
+
{ y: bottomY - Math.floor(totalHeight * 0.7), r: Math.floor(totalHeight * 0.08) }, // Neck start
|
|
44
|
+
{ y: topY, r: Math.floor(totalHeight * 0.08) } // Mouth
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
// Draw cross section ellipses
|
|
48
|
+
for (const sec of sections) {
|
|
49
|
+
// ry is based on perspective (lower = wider perspective, but keep it simple here)
|
|
50
|
+
const ry = Math.floor(sec.r * 0.3);
|
|
51
|
+
commands.push(shapes.drawEllipse(cx, sec.y, sec.r, ry, color, false));
|
|
52
|
+
|
|
53
|
+
// Cross point on the axis
|
|
54
|
+
commands.push(shapes.drawCircle(cx, sec.y, 1, '#ff0000', true));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Draw outer contour lines
|
|
58
|
+
for (let i = 0; i < sections.length - 1; i++) {
|
|
59
|
+
const s1 = sections[i];
|
|
60
|
+
const s2 = sections[i+1];
|
|
61
|
+
|
|
62
|
+
// Left contour
|
|
63
|
+
commands.push(shapes.drawLine(cx - s1.r, s1.y, cx - s2.r, s2.y, color));
|
|
64
|
+
// Right contour
|
|
65
|
+
commands.push(shapes.drawLine(cx + s1.r, s1.y, cx + s2.r, s2.y, color));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return commands;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = {
|
|
72
|
+
drawXYZAxes,
|
|
73
|
+
drawBottleAnatomy
|
|
74
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
const { registerTool, sendCommand } = require('../../core/server');
|
|
3
|
+
const structureLessons = require('./structure_lessons');
|
|
4
|
+
|
|
5
|
+
const hexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/).describe('Hex color e.g. #ff0000');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Register all structure and axis tools on the MCP server.
|
|
9
|
+
* @param {McpServer} server
|
|
10
|
+
*/
|
|
11
|
+
function register(server) {
|
|
12
|
+
registerTool(server, 'art_tree_lesson_structure_xyz',
|
|
13
|
+
'Lesson: Structure - XYZ Axes (Vertical, Horizontal, Depth directions)',
|
|
14
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#2196f3') },
|
|
15
|
+
async (p) => {
|
|
16
|
+
const commands = structureLessons.lessonXYZAxes(p.canvasSize, p.color);
|
|
17
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
18
|
+
return { content: [{ type: 'text', text: `✓ XYZ Axes lesson completed` }] };
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
registerTool(server, 'art_tree_lesson_structure_bottle',
|
|
22
|
+
'Lesson: Structure - Complex Analysis (Bottle with cross-sections)',
|
|
23
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#9c27b0') },
|
|
24
|
+
async (p) => {
|
|
25
|
+
const commands = structureLessons.lessonComplexStructure(p.canvasSize, p.color);
|
|
26
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
27
|
+
return { content: [{ type: 'text', text: `✓ Complex Structure (Bottle) lesson completed` }] };
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
registerTool(server, 'art_tree_lesson_structure_orientation',
|
|
31
|
+
'Lesson: Structure - Axis Orientation (Which axis is it oriented along?)',
|
|
32
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#00bcd4') },
|
|
33
|
+
async (p) => {
|
|
34
|
+
const commands = structureLessons.lessonAxisOrientation(p.canvasSize, p.color);
|
|
35
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
36
|
+
return { content: [{ type: 'text', text: `✓ Axis Orientation lesson completed` }] };
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
registerTool(server, 'art_tree_lesson_structure_catalog',
|
|
40
|
+
'Get the catalog of all available Structure lessons',
|
|
41
|
+
{},
|
|
42
|
+
() => {
|
|
43
|
+
const catalog = structureLessons.getStructureLessonCatalog();
|
|
44
|
+
const text = catalog.map(l =>
|
|
45
|
+
` • ${l.id}: ${l.name} — ${l.description}`
|
|
46
|
+
).join('\n');
|
|
47
|
+
return { content: [{ type: 'text', text: `📚 Structure Lessons:\n\n${text}` }] };
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = { register };
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const shapes = require('./shapes');
|
|
2
|
+
const surfaceShapes = require('./surface_shapes');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Lesson: Surface Types (Flat, convex, concave)
|
|
6
|
+
*/
|
|
7
|
+
function lessonSurfaceTypes(canvasSize = 32, color = '#2196f3') {
|
|
8
|
+
const commands = [];
|
|
9
|
+
const midY = Math.floor(canvasSize / 2);
|
|
10
|
+
const qX = Math.floor(canvasSize / 4);
|
|
11
|
+
const r = Math.floor(canvasSize * 0.15);
|
|
12
|
+
|
|
13
|
+
// 1. Flat Plane
|
|
14
|
+
const w = Math.floor(canvasSize * 0.2);
|
|
15
|
+
commands.push(shapes.drawRectangle(qX - w/2, midY - w/2, w, w, '#4caf50', false));
|
|
16
|
+
// Add some grid lines to show it's flat
|
|
17
|
+
commands.push(shapes.drawLine(qX, midY - w/2, qX, midY + w/2, '#4caf50'));
|
|
18
|
+
commands.push(shapes.drawLine(qX - w/2, midY, qX + w/2, midY, '#4caf50'));
|
|
19
|
+
|
|
20
|
+
// 2. Convex Surface (e.g. Dome/Sphere)
|
|
21
|
+
// We use an ellipse that curves outwards (downwards)
|
|
22
|
+
commands.push(shapes.drawEllipse(qX * 2, midY, r, r, '#ff9800', false));
|
|
23
|
+
// Cross contour curving DOWN (convex towards viewer)
|
|
24
|
+
commands.push(shapes.drawEllipse(qX * 2, midY + Math.floor(r*0.2), r, Math.floor(r*0.3), '#ff9800', false));
|
|
25
|
+
|
|
26
|
+
// 3. Concave Surface (e.g. Bowl)
|
|
27
|
+
// Draw the top rim of a bowl
|
|
28
|
+
commands.push(shapes.drawEllipse(qX * 3, midY, r, Math.floor(r*0.3), '#9c27b0', false));
|
|
29
|
+
// Draw the bottom curve (the inside of the bowl)
|
|
30
|
+
const bowlPoints = [];
|
|
31
|
+
for(let i=0; i<=18; i++) {
|
|
32
|
+
const a = (i/18) * Math.PI; // 0 to 180 degrees (bottom half)
|
|
33
|
+
bowlPoints.push({ x: Math.round(qX * 3 + r * Math.cos(a)), y: Math.round(midY + r * Math.sin(a)) });
|
|
34
|
+
}
|
|
35
|
+
commands.push(shapes.drawPolyline(bowlPoints, '#9c27b0'));
|
|
36
|
+
|
|
37
|
+
return commands;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Lesson: Edge Types (Hard sharp corners vs Soft rounded edges)
|
|
42
|
+
*/
|
|
43
|
+
function lessonEdgeTypes(canvasSize = 32, color = '#ff5722') {
|
|
44
|
+
const commands = [];
|
|
45
|
+
const qX1 = Math.floor(canvasSize * 0.3);
|
|
46
|
+
const qX2 = Math.floor(canvasSize * 0.7);
|
|
47
|
+
const midY = Math.floor(canvasSize / 2);
|
|
48
|
+
const w = Math.floor(canvasSize * 0.25);
|
|
49
|
+
|
|
50
|
+
// 1. Hard Edge (Sharp corners)
|
|
51
|
+
commands.push(shapes.drawRectangle(qX1 - w/2, midY - w/2, w, w, '#e91e63', false));
|
|
52
|
+
|
|
53
|
+
// 2. Soft Edge / Transitional
|
|
54
|
+
// Draw a box with rounded corners (fillet)
|
|
55
|
+
const r = Math.floor(w * 0.2); // Corner radius
|
|
56
|
+
const rx = qX2 - w/2;
|
|
57
|
+
const ry = midY - w/2;
|
|
58
|
+
|
|
59
|
+
// Straight segments
|
|
60
|
+
commands.push(shapes.drawLine(rx + r, ry, rx + w - r, ry, '#00bcd4')); // Top
|
|
61
|
+
commands.push(shapes.drawLine(rx + w, ry + r, rx + w, ry + w - r, '#00bcd4')); // Right
|
|
62
|
+
commands.push(shapes.drawLine(rx + r, ry + w, rx + w - r, ry + w, '#00bcd4')); // Bottom
|
|
63
|
+
commands.push(shapes.drawLine(rx, ry + r, rx, ry + w - r, '#00bcd4')); // Left
|
|
64
|
+
|
|
65
|
+
// Rounded corners (approximated with small circles for visual simplicity)
|
|
66
|
+
commands.push(shapes.drawCircle(rx + r, ry + r, r, '#00bcd4', false)); // TL
|
|
67
|
+
commands.push(shapes.drawCircle(rx + w - r, ry + r, r, '#00bcd4', false)); // TR
|
|
68
|
+
commands.push(shapes.drawCircle(rx + w - r, ry + w - r, r, '#00bcd4', false)); // BR
|
|
69
|
+
commands.push(shapes.drawCircle(rx + r, ry + w - r, r, '#00bcd4', false)); // BL
|
|
70
|
+
|
|
71
|
+
return commands;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Lesson: Cup Analysis (Analyzing surfaces of a cup)
|
|
76
|
+
*/
|
|
77
|
+
function lessonCupAnalysis(canvasSize = 32, color = '#607d8b') {
|
|
78
|
+
const cx = Math.floor(canvasSize / 2);
|
|
79
|
+
const cy = Math.floor(canvasSize / 2);
|
|
80
|
+
const r = Math.floor(canvasSize * 0.2);
|
|
81
|
+
const h = Math.floor(canvasSize * 0.4);
|
|
82
|
+
|
|
83
|
+
return surfaceShapes.drawCupAnalysis(cx, cy, r, h);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Lesson: Chair Analysis (Analyzing surfaces and edges of a chair)
|
|
88
|
+
*/
|
|
89
|
+
function lessonChairAnalysis(canvasSize = 32, color = '#795548') {
|
|
90
|
+
const cx = Math.floor(canvasSize / 2);
|
|
91
|
+
const cy = Math.floor(canvasSize / 2);
|
|
92
|
+
const w = Math.floor(canvasSize * 0.3);
|
|
93
|
+
|
|
94
|
+
return surfaceShapes.drawChairAnalysis(cx, cy, w);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function getSurfaceLessonCatalog() {
|
|
98
|
+
return [
|
|
99
|
+
{ id: 'surface_types', name: 'Surface Types', description: 'Flat, Convex, Concave planes', fn: lessonSurfaceTypes },
|
|
100
|
+
{ id: 'edge_types', name: 'Edge Types', description: 'Hard vs Soft (Transitional) edges', fn: lessonEdgeTypes },
|
|
101
|
+
{ id: 'surface_cup', name: 'Cup Analysis', description: 'Real object: Convex/Concave walls, Rim, Tube handle', fn: lessonCupAnalysis },
|
|
102
|
+
{ id: 'surface_chair', name: 'Chair Analysis', description: 'Real object: Flat planes, Hard edges, Cylindrical legs', fn: lessonChairAnalysis },
|
|
103
|
+
];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = {
|
|
107
|
+
lessonSurfaceTypes,
|
|
108
|
+
lessonEdgeTypes,
|
|
109
|
+
lessonCupAnalysis,
|
|
110
|
+
lessonChairAnalysis,
|
|
111
|
+
getSurfaceLessonCatalog
|
|
112
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const shapes = require('./shapes');
|
|
2
|
+
const shapes3d = require('./3d_shapes');
|
|
3
|
+
|
|
4
|
+
function drawCupAnalysis(cx, cy, r, h) {
|
|
5
|
+
const commands = [];
|
|
6
|
+
const topY = cy - Math.floor(h/2);
|
|
7
|
+
const botY = cy + Math.floor(h/2);
|
|
8
|
+
const topRy = Math.floor(r * 0.3);
|
|
9
|
+
const botRy = Math.floor(r * 0.4);
|
|
10
|
+
|
|
11
|
+
// Outer Wall (Convex) - Blue
|
|
12
|
+
commands.push(shapes.drawLine(cx - r, topY, cx - Math.floor(r*0.8), botY, '#2196f3'));
|
|
13
|
+
commands.push(shapes.drawLine(cx + r, topY, cx + Math.floor(r*0.8), botY, '#2196f3'));
|
|
14
|
+
// Base (Flat/Curved)
|
|
15
|
+
commands.push(shapes.drawEllipse(cx, botY, Math.floor(r*0.8), botRy, '#4caf50', false));
|
|
16
|
+
|
|
17
|
+
// Inner Wall (Concave) - Purple
|
|
18
|
+
// Draw the back half of the inner ellipse (the visible inside of the cup)
|
|
19
|
+
const innerR = Math.floor(r * 0.9);
|
|
20
|
+
const innerRy = Math.floor(topRy * 0.9);
|
|
21
|
+
// Just draw the full inner ellipse but highlighted differently
|
|
22
|
+
commands.push(shapes.drawEllipse(cx, topY, innerR, innerRy, '#9c27b0', false));
|
|
23
|
+
|
|
24
|
+
// Lip / Rim (Transitional edge) - Orange
|
|
25
|
+
// We represent the lip as the gap between the inner and outer ellipses at the top
|
|
26
|
+
commands.push(shapes.drawEllipse(cx, topY, r, topRy, '#ff9800', false));
|
|
27
|
+
|
|
28
|
+
// Handle (Tube surface) - Red
|
|
29
|
+
// Draw a curved handle on the right side
|
|
30
|
+
const hx = cx + r;
|
|
31
|
+
const hy = cy;
|
|
32
|
+
const hr = Math.floor(h * 0.3);
|
|
33
|
+
// Two nested semi-circles (arcs) for the handle thickness
|
|
34
|
+
const handleOuter = [];
|
|
35
|
+
const handleInner = [];
|
|
36
|
+
for(let i=0; i<=18; i++) {
|
|
37
|
+
const angle = (i/18) * Math.PI - Math.PI/2; // -90 to 90 degrees
|
|
38
|
+
handleOuter.push({ x: Math.round(hx + hr * Math.cos(angle)), y: Math.round(hy + hr * Math.sin(angle)) });
|
|
39
|
+
handleInner.push({ x: Math.round(hx + (hr-5) * Math.cos(angle)), y: Math.round(hy + (hr-5) * Math.sin(angle)) });
|
|
40
|
+
}
|
|
41
|
+
commands.push(shapes.drawPolyline(handleOuter, '#f44336'));
|
|
42
|
+
commands.push(shapes.drawPolyline(handleInner, '#f44336'));
|
|
43
|
+
|
|
44
|
+
return commands;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function drawChairAnalysis(cx, cy, w) {
|
|
48
|
+
const commands = [];
|
|
49
|
+
const h = Math.floor(w * 1.5);
|
|
50
|
+
const d = Math.floor(w * 0.8);
|
|
51
|
+
const seatY = cy;
|
|
52
|
+
const dx = Math.floor(d * 0.7);
|
|
53
|
+
const dy = Math.floor(d * 0.5);
|
|
54
|
+
|
|
55
|
+
// Seat (Flat plane) - Green
|
|
56
|
+
const fX = cx - Math.floor(w/2);
|
|
57
|
+
const bX = fX + dx;
|
|
58
|
+
const bY = seatY - dy;
|
|
59
|
+
// Seat wireframe
|
|
60
|
+
commands.push(shapes.drawRectangle(fX, seatY, w, 10, '#4caf50', false)); // Front thickness
|
|
61
|
+
commands.push(shapes.drawLine(fX, seatY, bX, bY, '#4caf50'));
|
|
62
|
+
commands.push(shapes.drawLine(fX+w, seatY, bX+w, bY, '#4caf50'));
|
|
63
|
+
commands.push(shapes.drawLine(bX, bY, bX+w, bY, '#4caf50'));
|
|
64
|
+
|
|
65
|
+
// Backrest (Flat plane + Hard edges) - Blue
|
|
66
|
+
commands.push(shapes.drawRectangle(bX, bY - Math.floor(h*0.5), w, Math.floor(h*0.5), '#2196f3', false));
|
|
67
|
+
// Inner rungs (Hard edges)
|
|
68
|
+
commands.push(shapes.drawRectangle(bX + 10, bY - Math.floor(h*0.4), 5, Math.floor(h*0.4), '#ff9800', false));
|
|
69
|
+
commands.push(shapes.drawRectangle(bX + w - 15, bY - Math.floor(h*0.4), 5, Math.floor(h*0.4), '#ff9800', false));
|
|
70
|
+
|
|
71
|
+
// Legs (Cylindrical surfaces) - Red
|
|
72
|
+
const legH = Math.floor(h*0.4);
|
|
73
|
+
const legR = 3;
|
|
74
|
+
// Front-left
|
|
75
|
+
commands.push(shapes.drawLine(fX+legR, seatY+10, fX+legR, seatY+10+legH, '#f44336'));
|
|
76
|
+
commands.push(shapes.drawLine(fX+10-legR, seatY+10, fX+10-legR, seatY+10+legH, '#f44336'));
|
|
77
|
+
// Front-right
|
|
78
|
+
commands.push(shapes.drawLine(fX+w-10+legR, seatY+10, fX+w-10+legR, seatY+10+legH, '#f44336'));
|
|
79
|
+
commands.push(shapes.drawLine(fX+w-legR, seatY+10, fX+w-legR, seatY+10+legH, '#f44336'));
|
|
80
|
+
|
|
81
|
+
return commands;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = {
|
|
85
|
+
drawCupAnalysis,
|
|
86
|
+
drawChairAnalysis
|
|
87
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
const { registerTool, sendCommand } = require('../../core/server');
|
|
3
|
+
const surfaceLessons = require('./surface_lessons');
|
|
4
|
+
|
|
5
|
+
const hexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/).describe('Hex color e.g. #ff0000');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Register all surface analysis tools on the MCP server.
|
|
9
|
+
* @param {McpServer} server
|
|
10
|
+
*/
|
|
11
|
+
function register(server) {
|
|
12
|
+
registerTool(server, 'art_tree_lesson_surface_types',
|
|
13
|
+
'Lesson: Surfaces - Analyze Flat, Convex, and Concave planes',
|
|
14
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#2196f3') },
|
|
15
|
+
async (p) => {
|
|
16
|
+
const commands = surfaceLessons.lessonSurfaceTypes(p.canvasSize, p.color);
|
|
17
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
18
|
+
return { content: [{ type: 'text', text: `✓ Surface Types lesson completed` }] };
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
registerTool(server, 'art_tree_lesson_edge_types',
|
|
22
|
+
'Lesson: Surfaces - Analyze Hard vs Soft (Transitional) edges',
|
|
23
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#ff5722') },
|
|
24
|
+
async (p) => {
|
|
25
|
+
const commands = surfaceLessons.lessonEdgeTypes(p.canvasSize, p.color);
|
|
26
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
27
|
+
return { content: [{ type: 'text', text: `✓ Edge Types lesson completed` }] };
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
registerTool(server, 'art_tree_lesson_surface_cup',
|
|
31
|
+
'Lesson: Surfaces - Cup Analysis (Real object breakdown)',
|
|
32
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#607d8b') },
|
|
33
|
+
async (p) => {
|
|
34
|
+
const commands = surfaceLessons.lessonCupAnalysis(p.canvasSize, p.color);
|
|
35
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
36
|
+
return { content: [{ type: 'text', text: `✓ Cup Surface Analysis lesson completed` }] };
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
registerTool(server, 'art_tree_lesson_surface_chair',
|
|
40
|
+
'Lesson: Surfaces - Chair Analysis (Real object breakdown)',
|
|
41
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#795548') },
|
|
42
|
+
async (p) => {
|
|
43
|
+
const commands = surfaceLessons.lessonChairAnalysis(p.canvasSize, p.color);
|
|
44
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
45
|
+
return { content: [{ type: 'text', text: `✓ Chair Surface Analysis lesson completed` }] };
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
registerTool(server, 'art_tree_lesson_surface_catalog',
|
|
49
|
+
'Get the catalog of all available Surface lessons',
|
|
50
|
+
{},
|
|
51
|
+
() => {
|
|
52
|
+
const catalog = surfaceLessons.getSurfaceLessonCatalog();
|
|
53
|
+
const text = catalog.map(l =>
|
|
54
|
+
` • ${l.id}: ${l.name} — ${l.description}`
|
|
55
|
+
).join('\n');
|
|
56
|
+
return { content: [{ type: 'text', text: `📚 Surface Lessons:\n\n${text}` }] };
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = { register };
|
|
@@ -96,72 +96,126 @@ function register(server) {
|
|
|
96
96
|
// ── Lesson Tools ──────────────────────────────────────────────────────
|
|
97
97
|
|
|
98
98
|
registerTool(server, 'art_tree_lesson_lines',
|
|
99
|
-
'Lesson:
|
|
99
|
+
'Lesson: Lines — Introduction to drawing straight lines',
|
|
100
100
|
{ canvasSize: z.number().int().min(16).max(64).default(32),
|
|
101
101
|
color: hexColor.default('#ff0000') },
|
|
102
102
|
async (p) => {
|
|
103
103
|
const commands = lessons.lessonLines(p.canvasSize, p.color);
|
|
104
104
|
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
105
|
-
return { content: [{ type: 'text', text: `✓
|
|
105
|
+
return { content: [{ type: 'text', text: `✓ Lines lesson completed (${commands.length} strokes)` }] };
|
|
106
106
|
});
|
|
107
107
|
|
|
108
108
|
registerTool(server, 'art_tree_lesson_squares',
|
|
109
|
-
'Lesson:
|
|
109
|
+
'Lesson: Squares & Rectangles — Understanding right angles and proportions',
|
|
110
110
|
{ canvasSize: z.number().int().min(16).max(64).default(32),
|
|
111
111
|
color: hexColor.default('#1565c0') },
|
|
112
112
|
async (p) => {
|
|
113
113
|
const commands = lessons.lessonSquares(p.canvasSize, p.color);
|
|
114
114
|
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
115
|
-
return { content: [{ type: 'text', text: `✓
|
|
115
|
+
return { content: [{ type: 'text', text: `✓ Squares & Rectangles lesson completed (${commands.length} strokes)` }] };
|
|
116
116
|
});
|
|
117
117
|
|
|
118
118
|
registerTool(server, 'art_tree_lesson_circles',
|
|
119
|
-
'Lesson:
|
|
119
|
+
'Lesson: Circles — Learning curves and symmetry',
|
|
120
120
|
{ canvasSize: z.number().int().min(16).max(64).default(32),
|
|
121
121
|
color: hexColor.default('#e91e63') },
|
|
122
122
|
async (p) => {
|
|
123
123
|
const commands = lessons.lessonCircles(p.canvasSize, p.color);
|
|
124
124
|
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
125
|
-
return { content: [{ type: 'text', text: `✓
|
|
125
|
+
return { content: [{ type: 'text', text: `✓ Circles lesson completed (${commands.length} strokes)` }] };
|
|
126
126
|
});
|
|
127
127
|
|
|
128
128
|
registerTool(server, 'art_tree_lesson_ellipses',
|
|
129
|
-
'Lesson:
|
|
129
|
+
'Lesson: Ellipses — Understanding oval shapes and proportions',
|
|
130
130
|
{ canvasSize: z.number().int().min(16).max(64).default(32),
|
|
131
131
|
color: hexColor.default('#9c27b0') },
|
|
132
132
|
async (p) => {
|
|
133
133
|
const commands = lessons.lessonEllipses(p.canvasSize, p.color);
|
|
134
134
|
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
135
|
-
return { content: [{ type: 'text', text: `✓
|
|
135
|
+
return { content: [{ type: 'text', text: `✓ Ellipses lesson completed (${commands.length} strokes)` }] };
|
|
136
136
|
});
|
|
137
137
|
|
|
138
138
|
registerTool(server, 'art_tree_lesson_triangles',
|
|
139
|
-
'Lesson:
|
|
139
|
+
'Lesson: Triangles — Three-point shapes and stability',
|
|
140
140
|
{ canvasSize: z.number().int().min(16).max(64).default(32),
|
|
141
141
|
color: hexColor.default('#ff5722') },
|
|
142
142
|
async (p) => {
|
|
143
143
|
const commands = lessons.lessonTriangles(p.canvasSize, p.color);
|
|
144
144
|
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
145
|
-
return { content: [{ type: 'text', text: `✓
|
|
145
|
+
return { content: [{ type: 'text', text: `✓ Triangles lesson completed (${commands.length} strokes)` }] };
|
|
146
146
|
});
|
|
147
147
|
|
|
148
148
|
registerTool(server, 'art_tree_lesson_polygons',
|
|
149
|
-
'Lesson:
|
|
149
|
+
'Lesson: Polygons — Multi-sided shapes',
|
|
150
150
|
{ canvasSize: z.number().int().min(16).max(64).default(32),
|
|
151
151
|
color: hexColor.default('#607d8b') },
|
|
152
152
|
async (p) => {
|
|
153
153
|
const commands = lessons.lessonPolygons(p.canvasSize, p.color);
|
|
154
154
|
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
155
|
-
return { content: [{ type: 'text', text: `✓
|
|
155
|
+
return { content: [{ type: 'text', text: `✓ Polygons lesson completed (${commands.length} strokes)` }] };
|
|
156
156
|
});
|
|
157
157
|
|
|
158
158
|
registerTool(server, 'art_tree_lesson_composition',
|
|
159
|
-
'Lesson:
|
|
159
|
+
'Lesson: Composition — Combining basic shapes to create simple objects (house, sun, tree)',
|
|
160
160
|
{ canvasSize: z.number().int().min(16).max(64).default(32) },
|
|
161
161
|
async (p) => {
|
|
162
162
|
const commands = lessons.lessonComposition(p.canvasSize);
|
|
163
163
|
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
164
|
-
return { content: [{ type: 'text', text: `✓
|
|
164
|
+
return { content: [{ type: 'text', text: `✓ Composition lesson completed (${commands.length} strokes)` }] };
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
registerTool(server, 'art_tree_lesson_freehand',
|
|
168
|
+
'Lesson: Freehand Drawing — Practice freehand drawing without rulers',
|
|
169
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#795548') },
|
|
170
|
+
async (p) => {
|
|
171
|
+
const commands = lessons.lessonFreehand(p.canvasSize, p.color);
|
|
172
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
173
|
+
return { content: [{ type: 'text', text: `✓ Freehand drawing lesson completed (${commands.length} small segments)` }] };
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
registerTool(server, 'art_tree_lesson_proportions',
|
|
177
|
+
'Lesson: Proportions and Angles — Practice maintaining proportions and angles',
|
|
178
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#3f51b5') },
|
|
179
|
+
async (p) => {
|
|
180
|
+
const commands = lessons.lessonProportionsAndAngles(p.canvasSize, p.color);
|
|
181
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
182
|
+
return { content: [{ type: 'text', text: `✓ Proportions and Angles lesson completed` }] };
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
registerTool(server, 'art_tree_lesson_curves',
|
|
186
|
+
'Lesson: Curves — C-curves and S-curves',
|
|
187
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#9c27b0') },
|
|
188
|
+
async (p) => {
|
|
189
|
+
const commands = lessons.lessonCurves(p.canvasSize, p.color);
|
|
190
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
191
|
+
return { content: [{ type: 'text', text: `✓ Curves (C, S) lesson completed (${commands.length} strokes)` }] };
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
registerTool(server, 'art_tree_lesson_spiral',
|
|
195
|
+
'Lesson: Spiral — Drawing spiral curves',
|
|
196
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#ff9800') },
|
|
197
|
+
async (p) => {
|
|
198
|
+
const commands = lessons.lessonSpiral(p.canvasSize, p.color);
|
|
199
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
200
|
+
return { content: [{ type: 'text', text: `✓ Spiral lesson completed (${commands.length} strokes)` }] };
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
registerTool(server, 'art_tree_lesson_strokes',
|
|
204
|
+
'Lesson: Stroke Types — Long strokes, short strokes, thick and thin lines',
|
|
205
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#2196f3') },
|
|
206
|
+
async (p) => {
|
|
207
|
+
const commands = lessons.lessonStrokes(p.canvasSize, p.color);
|
|
208
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
209
|
+
return { content: [{ type: 'text', text: `✓ Strokes lesson completed` }] };
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
registerTool(server, 'art_tree_lesson_parallel_intersecting',
|
|
213
|
+
'Lesson: Parallel & Intersecting Lines — Drawing parallel and intersecting lines',
|
|
214
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#00bcd4') },
|
|
215
|
+
async (p) => {
|
|
216
|
+
const commands = lessons.lessonParallelAndIntersecting(p.canvasSize, p.color);
|
|
217
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
218
|
+
return { content: [{ type: 'text', text: `✓ Parallel & Intersecting Lines lesson completed` }] };
|
|
165
219
|
});
|
|
166
220
|
|
|
167
221
|
registerTool(server, 'art_tree_lesson_catalog',
|
|
@@ -172,7 +226,7 @@ function register(server) {
|
|
|
172
226
|
const text = catalog.map(l =>
|
|
173
227
|
` • ${l.id}: ${l.name} — ${l.description}`
|
|
174
228
|
).join('\n');
|
|
175
|
-
return { content: [{ type: 'text', text: `📚
|
|
229
|
+
return { content: [{ type: 'text', text: `📚 Art Tree Lesson Catalog:\n\n${text}` }] };
|
|
176
230
|
});
|
|
177
231
|
}
|
|
178
232
|
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const shapes = require('./shapes');
|
|
2
|
+
const transformShapes = require('./transform_shapes');
|
|
3
|
+
const csShapes = require('./cross_section_shapes');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Lesson: Stretch (Elongate)
|
|
7
|
+
* Sphere -> Egg, Box -> Bar
|
|
8
|
+
*/
|
|
9
|
+
function lessonStretch(canvasSize = 32, color = '#2196f3') {
|
|
10
|
+
const commands = [];
|
|
11
|
+
const qX1 = Math.floor(canvasSize * 0.25);
|
|
12
|
+
const qX2 = Math.floor(canvasSize * 0.75);
|
|
13
|
+
const cy = Math.floor(canvasSize / 2);
|
|
14
|
+
const r = Math.floor(canvasSize * 0.15);
|
|
15
|
+
|
|
16
|
+
// 1. Stretch: Sphere -> Egg
|
|
17
|
+
commands.push(shapes.drawCircle(qX1, cy, r, '#bdbdbd', false)); // Original sphere (ghost)
|
|
18
|
+
commands.push(...transformShapes.drawEgg(qX1, cy, r, color)); // Stretched into egg
|
|
19
|
+
|
|
20
|
+
// 2. Stretch: Box -> Bar (Tall)
|
|
21
|
+
const w = Math.floor(canvasSize * 0.15);
|
|
22
|
+
// Original box (ghost)
|
|
23
|
+
commands.push(shapes.drawRectangle(qX2 - w/2, cy - w/2, w, w, '#bdbdbd', false));
|
|
24
|
+
// Stretched bar
|
|
25
|
+
commands.push(shapes.drawRectangle(qX2 - w/2, cy - w * 1.5, w, w * 3, '#9c27b0', false));
|
|
26
|
+
|
|
27
|
+
return commands;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Lesson: Squash (Compress)
|
|
32
|
+
* Sphere -> Disk, Box -> Plate
|
|
33
|
+
*/
|
|
34
|
+
function lessonSquash(canvasSize = 32, color = '#ff9800') {
|
|
35
|
+
const commands = [];
|
|
36
|
+
const qX1 = Math.floor(canvasSize * 0.25);
|
|
37
|
+
const qX2 = Math.floor(canvasSize * 0.75);
|
|
38
|
+
const cy = Math.floor(canvasSize / 2);
|
|
39
|
+
const r = Math.floor(canvasSize * 0.15);
|
|
40
|
+
|
|
41
|
+
// 1. Squash: Sphere -> Disk
|
|
42
|
+
commands.push(shapes.drawCircle(qX1, cy, r, '#bdbdbd', false)); // Original
|
|
43
|
+
// Squashed into a flat ellipse/disk
|
|
44
|
+
commands.push(shapes.drawEllipse(qX1, cy, r * 1.2, Math.floor(r * 0.3), color, false));
|
|
45
|
+
|
|
46
|
+
// 2. Squash: Box -> Plate
|
|
47
|
+
const w = Math.floor(canvasSize * 0.2);
|
|
48
|
+
commands.push(shapes.drawRectangle(qX2 - w/2, cy - w/2, w, w, '#bdbdbd', false)); // Original
|
|
49
|
+
// Squashed into plate
|
|
50
|
+
commands.push(shapes.drawRectangle(qX2 - w, cy - Math.floor(w*0.2), w * 2, Math.floor(w*0.4), '#4caf50', false));
|
|
51
|
+
|
|
52
|
+
return commands;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Lesson: Taper & Swell (Narrow or bulge)
|
|
57
|
+
* Cylinder -> Bottle
|
|
58
|
+
*/
|
|
59
|
+
function lessonTaperSwell(canvasSize = 32, color = '#00bcd4') {
|
|
60
|
+
const commands = [];
|
|
61
|
+
const cx = Math.floor(canvasSize / 2);
|
|
62
|
+
const totalH = Math.floor(canvasSize * 0.8);
|
|
63
|
+
const topY = Math.floor(canvasSize * 0.1);
|
|
64
|
+
const bottomY = topY + totalH;
|
|
65
|
+
const r = Math.floor(canvasSize * 0.25);
|
|
66
|
+
|
|
67
|
+
// Original Cylinder (ghost)
|
|
68
|
+
commands.push(shapes.drawLine(cx - r, topY, cx - r, bottomY, '#bdbdbd'));
|
|
69
|
+
commands.push(shapes.drawLine(cx + r, topY, cx + r, bottomY, '#bdbdbd'));
|
|
70
|
+
|
|
71
|
+
// Tapered and Swelled form (Bottle)
|
|
72
|
+
const sections = [
|
|
73
|
+
{ y: topY, r: Math.floor(r * 0.3) }, // Neck (Tapered)
|
|
74
|
+
{ y: topY + Math.floor(totalH * 0.4), r: r },// Body (Swelled back to original radius)
|
|
75
|
+
{ y: bottomY, r: Math.floor(r * 0.8) } // Base (Slightly tapered)
|
|
76
|
+
];
|
|
77
|
+
commands.push(...csShapes.drawLoftedForm(cx, topY, bottomY, sections, color, true));
|
|
78
|
+
|
|
79
|
+
return commands;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Lesson: Bend (Curved bending)
|
|
84
|
+
* Cylinder -> Bent Tube
|
|
85
|
+
*/
|
|
86
|
+
function lessonBend(canvasSize = 32, color = '#e91e63') {
|
|
87
|
+
const commands = [];
|
|
88
|
+
const cx = Math.floor(canvasSize / 2);
|
|
89
|
+
const cy = Math.floor(canvasSize / 2);
|
|
90
|
+
const r = Math.floor(canvasSize * 0.1);
|
|
91
|
+
const length = Math.floor(canvasSize * 0.35);
|
|
92
|
+
|
|
93
|
+
// Original straight tube (ghost, pointing right)
|
|
94
|
+
commands.push(shapes.drawLine(cx, cy - r, cx + length * 2, cy - r, '#bdbdbd'));
|
|
95
|
+
commands.push(shapes.drawLine(cx, cy + r, cx + length * 2, cy + r, '#bdbdbd'));
|
|
96
|
+
|
|
97
|
+
// Bent tube
|
|
98
|
+
commands.push(...transformShapes.drawBentTube(cx, cy + length, r, length, color));
|
|
99
|
+
|
|
100
|
+
return commands;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = {
|
|
104
|
+
lessonStretch,
|
|
105
|
+
lessonSquash,
|
|
106
|
+
lessonTaperSwell,
|
|
107
|
+
lessonBend
|
|
108
|
+
};
|