This commit is contained in:
Bane Stojkovic
2026-08-19 13:55:36 +02:00
parent 49862245eb
commit a2e52b5520
2 changed files with 293 additions and 59 deletions
@@ -28,8 +28,8 @@ import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import org.diylc.awt.StringUtils;
@@ -62,24 +62,29 @@ public class ArduinoMega extends AbstractMakerBoard {
public static Size BOARD_WIDTH = new Size(101.6d, SizeUnit.mm);
public static Size BOARD_HEIGHT = new Size(53.4d, SizeUnit.mm);
// Pin names in sequence (98 pins total)
public static final String[] PIN_NAMES = new String[] {
// Power (0..7)
// Power Header (0..7)
"NC", "IOREF", "RESET", "3.3V", "5V", "GND1", "GND2", "VIN",
// Analog Low A0..A7 (8..15)
"A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7",
// Analog High A8..A15 (16..23)
"A8", "A9", "A10", "A11", "A12", "A13", "A14", "A15",
// Digital Low (24..31)
"D0 (RX0)", "D1 (TX0)", "D2", "D3 (~)", "D4", "D5 (~)", "D6 (~)", "D7",
// Digital High (32..41)
"D8", "D9 (~)", "D10 (~)", "D11 (~)", "D12", "D13", "GND3", "AREF", "SDA", "SCL",
// Communication Header (42..49)
// Digital Low D0..D7 (24..31)
"D0 (RX0)", "D1 (TX0)", "D2 (~)", "D3 (~)", "D4 (~)", "D5 (~)", "D6 (~)", "D7 (~)",
// Digital High D8..D13, GND, AREF, SDA, SCL (32..41)
"D8 (~)", "D9 (~)", "D10 (~)", "D11 (~)", "D12 (~)", "D13 (~)", "GND3", "AREF", "SDA", "SCL",
// Communication Header D14..D21 (42..49)
"D14 (TX3)", "D15 (RX3)", "D16 (TX2)", "D17 (RX2)", "D18 (TX1)", "D19 (RX1)", "D20 (SDA)", "D21 (SCL)",
// Double Digital 2x18 Header (50..85: D22..D53, 5V, 5V, GND, GND)
// Double Digital 2x18 Header (50..85: D22..D53, GNDx2, 5Vx2)
"D22", "D23", "D24", "D25", "D26", "D27", "D28", "D29", "D30", "D31", "D32", "D33",
"D34", "D35", "D36", "D37", "D38", "D39", "D40", "D41", "D42", "D43", "D44", "D45",
"D46", "D47", "D48", "D49", "D50 (MISO)", "D51 (MOSI)", "D52 (SCK)", "D53 (SS)",
"GND_EXT1", "GND_EXT2", "5V_EXT1", "5V_EXT2"
"GND_EXT1", "GND_EXT2", "5V_EXT1", "5V_EXT2",
// Main ICSP Header (86..91, ATmega2560)
"MISO", "5V_ICSP", "SCK", "MOSI", "RST_ICSP", "GND_ICSP",
// Top-Left ICSP Header (92..97, ATmega16U2)
"MISO_16U2", "5V_16U2", "SCK_16U2", "MOSI_16U2", "RST_16U2", "GND_16U2"
};
public ArduinoMega() {
@@ -99,57 +104,77 @@ public class ArduinoMega extends AbstractMakerBoard {
@Override
protected void updateControlPoints() {
Point2D firstPoint = controlPoints[0];
double spacing = PIN_SPACING.convertToPixels(); // 20px for 0.1"
double spacing = PIN_SPACING.convertToPixels(); // 20px for 0.1" (100 mils)
// Reference: Pin 0 (NC) is at (boardX + 220, boardY + 400)
// Board is 800px wide, 420px high
// Reference: Pin 0 (NC) is at (boardX + 220.0, boardY + 400.0) [1100 mils, 100 mils from bottom-left]
// Board is 800px wide (4000 mils), 420px high (2100 mils)
double[][] relativeOffsets = new double[PIN_NAMES.length][2];
// Power header pins 0..7 (bottom row)
// Power header pins 0..7 (bottom row, 1100..1800 mils)
for (int i = 0; i < 8; i++) {
relativeOffsets[i][0] = i * spacing;
relativeOffsets[i][1] = 0;
}
// Analog Low A0..A7 (8..15) (bottom row, separated by 0.16" = 32px)
double a0X = 7 * spacing + 32;
// Analog Low A0..A7 pins 8..15 (bottom row, 2000..2700 mils, separated from VIN by 0.2" = 40px)
double a0X = 7 * spacing + 40; // 180px
for (int i = 0; i < 8; i++) {
relativeOffsets[8 + i][0] = a0X + i * spacing;
relativeOffsets[8 + i][1] = 0;
}
// Analog High A8..A15 (16..23) (bottom row, continuing from A7)
double a8X = a0X + 8 * spacing;
// Analog High A8..A15 pins 16..23 (bottom row, 2900..3600 mils, separated from A7 by 0.2" = 40px)
double a8X = a0X + 7 * spacing + 40; // 360px
for (int i = 0; i < 8; i++) {
relativeOffsets[16 + i][0] = a8X + i * spacing;
relativeOffsets[16 + i][1] = 0;
}
// Digital Low D0..D7 (24..31) (top row)
// Digital Low D0..D7 pins 24..31 (top row, 2500..1800 mils, Y=2000 mils)
double topRowY = -380;
double d0X = 300;
double d0X = 280; // aligns with A5 at 2500 mils
for (int i = 0; i < 8; i++) {
relativeOffsets[24 + i][0] = d0X - i * spacing;
relativeOffsets[24 + i][1] = topRowY;
}
// Digital High D8..D13/GND/AREF/SDA/SCL (32..41) (top row)
double d8X = 128;
// Digital High D8..D13/GND/AREF/SDA/SCL pins 32..41 (top row, 1640..740 mils, gap between D7 and D8 is 0.16" = 32px)
double d8X = 140 - 32; // 108px
for (int i = 0; i < 10; i++) {
relativeOffsets[32 + i][0] = d8X - i * spacing;
relativeOffsets[32 + i][1] = topRowY;
}
// Communication header D14..D21 (42..49) (top row)
double commX = a8X;
// Communication header D14..D21 pins 42..49 (top row, 2700..3400 mils, gap from D0 is 0.2" = 40px)
double d14X = d0X + 40; // 320px, aligns with A7 at 2700 mils
for (int i = 0; i < 8; i++) {
relativeOffsets[42 + i][0] = commX + i * spacing;
relativeOffsets[42 + i][0] = d14X + i * spacing;
relativeOffsets[42 + i][1] = topRowY;
}
// Double digital header 2x18 at far right (50..85: D22..D53, GND, GND, 5V, 5V)
double d22X = 510;
// Double digital header 2x18 at far right pins 50..85 (D22..D53, GNDx2, 5Vx2; X=3600, 3700 mils, Y=2000..300 mils)
double d22InnerX = 500; // (3600 - 1100) * 0.2
double d22OuterX = 520; // (3700 - 1100) * 0.2
for (int row = 0; row < 18; row++) {
double py = -360 + row * spacing;
relativeOffsets[50 + row * 2][0] = d22X;
double py = topRowY + row * spacing;
relativeOffsets[50 + row * 2][0] = d22InnerX;
relativeOffsets[50 + row * 2][1] = py;
relativeOffsets[50 + row * 2 + 1][0] = d22X + spacing;
relativeOffsets[50 + row * 2 + 1][0] = d22OuterX;
relativeOffsets[50 + row * 2 + 1][1] = py;
}
// Main ICSP header (2x3 pins, 86..91) for ATmega2560 at (2505, 1200) mils
double icspX = (2505 - 1100) * 0.2; // 281.0 px
double icspY = (100 - 1200) * 0.2; // -220.0 px
relativeOffsets[86] = new double[] {icspX, icspY};
relativeOffsets[87] = new double[] {icspX + spacing, icspY};
relativeOffsets[88] = new double[] {icspX, icspY + spacing};
relativeOffsets[89] = new double[] {icspX + spacing, icspY + spacing};
relativeOffsets[90] = new double[] {icspX, icspY + 2 * spacing};
relativeOffsets[91] = new double[] {icspX + spacing, icspY + 2 * spacing};
// Top-Left ICSP header (2x3 pins, 92..97) for ATmega16U2 near USB jack at (820, 1870) mils
double icsp2X = (820 - 1100) * 0.2; // -56.0 px
double icsp2Y = (100 - 1870) * 0.2; // -354.0 px
relativeOffsets[92] = new double[] {icsp2X, icsp2Y};
relativeOffsets[93] = new double[] {icsp2X, icsp2Y + spacing};
relativeOffsets[94] = new double[] {icsp2X - spacing, icsp2Y};
relativeOffsets[95] = new double[] {icsp2X - spacing, icsp2Y + spacing};
relativeOffsets[96] = new double[] {icsp2X - 2 * spacing, icsp2Y};
relativeOffsets[97] = new double[] {icsp2X - 2 * spacing, icsp2Y + spacing};
rotatePoints(firstPoint, relativeOffsets);
}
@@ -159,11 +184,25 @@ public class ArduinoMega extends AbstractMakerBoard {
Point2D p0 = controlPoints[0];
double x = p0.getX();
double y = p0.getY();
double boardW = BOARD_WIDTH.convertToPixels();
double boardH = BOARD_HEIGHT.convertToPixels();
double boardX = x - 220;
double boardY = y - 400;
return new RoundRectangle2D.Double(boardX, boardY, boardW, boardH, 16, 16);
double boardX = x - 220.0;
double boardY = y - 400.0;
Path2D.Double path = new Path2D.Double();
path.moveTo(boardX + 767.78, boardY + 0.00);
path.lineTo(boardX + 779.78, boardY + 12.00);
path.lineTo(boardX + 779.78, boardY + 102.00);
path.lineTo(boardX + 799.78, boardY + 122.00);
path.lineTo(boardX + 799.78, boardY + 380.00);
path.lineTo(boardX + 779.78, boardY + 400.00);
path.lineTo(boardX + 779.78, boardY + 412.00);
path.curveTo(boardX + 779.78, boardY + 416.44, boardX + 776.22, boardY + 419.78, boardX + 772.00, boardY + 419.78);
path.lineTo(boardX + 7.78, boardY + 419.78);
path.curveTo(boardX + 3.33, boardY + 419.78, boardX + 0.00, boardY + 416.22, boardX + 0.00, boardY + 412.00);
path.lineTo(boardX + 0.00, boardY + 7.78);
path.curveTo(boardX + 0.00, boardY + 3.33, boardX + 3.56, boardY + 0.00, boardX + 7.78, boardY + 0.00);
path.lineTo(boardX + 767.78, boardY + 0.00);
path.closePath();
return path;
}
@Override
@@ -182,15 +221,14 @@ public class ArduinoMega extends AbstractMakerBoard {
g2d.rotate(orientation.toRadians(), x, y);
}
double boardW = BOARD_WIDTH.convertToPixels();
double boardH = BOARD_HEIGHT.convertToPixels();
double boardX = x - 220;
double boardY = y - 400;
double boardX = x - 220.0;
double boardY = y - 400.0;
Shape boardShape = getBodyShape();
Composite oldComposite = applyAlpha(g2d, componentState);
// Draw PCB body
drawingObserver.startTracking();
g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : bodyColor);
g2d.fill(boardShape);
@@ -201,41 +239,48 @@ public class ArduinoMega extends AbstractMakerBoard {
g2d.draw(boardShape);
if (!outlineMode) {
// Mounting holes
drawMountingHole(g2d, boardX + 110, boardY + 30, 24);
drawMountingHole(g2d, boardX + 120, boardY + 390, 24);
drawMountingHole(g2d, boardX + boardW - 30, boardY + 30, 24);
drawMountingHole(g2d, boardX + boardW - 30, boardY + boardH - 30, 24);
// Mounting holes (6 mounting holes from technical design)
drawMountingHole(g2d, boardX + 110, boardY + 400, 24); // (550, 100 mils)
drawMountingHole(g2d, boardX + 120, boardY + 20, 24); // (600, 2000 mils)
drawMountingHole(g2d, boardX + 520, boardY + 140, 24); // (2600, 1400 mils)
drawMountingHole(g2d, boardX + 520, boardY + 360, 24); // (2600, 300 mils)
drawMountingHole(g2d, boardX + 710, boardY + 20, 24); // (3550, 2000 mils)
drawMountingHole(g2d, boardX + 760, boardY + 400, 24); // (3800, 100 mils)
// USB Type-B Jack & DC Power Jack
drawMetalConnector(g2d, boardX - 10, boardY + 40, 95, 80, "USB");
drawChip(g2d, boardX - 10, boardY + boardH - 115, 105, 75, "DC IN");
drawMetalConnector(g2d, boardX - 28, boardY + 75, 103, 90, "USB");
drawChip(g2d, boardX - 14, boardY + 325, 104, 70, "DC IN");
// ATmega2560 square QFP chip
drawChip(g2d, boardX + 370, boardY + 165, 90, 90, "m2560");
drawChip(g2d, boardX + 365, boardY + 165, 80, 80, "ATmega2560");
// Reset Button near USB
g2d.setColor(Color.decode("#CC3333"));
g2d.fill(new Ellipse2D.Double(boardX + 45, boardY + 135, 18, 18));
g2d.fill(new Ellipse2D.Double(boardX + 38, boardY + 20, 18, 18));
g2d.setColor(Color.WHITE);
g2d.setFont(SILK_FONT_SMALL);
StringUtils.drawCenteredText(g2d, "RST", boardX + 45, boardY + 160, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
StringUtils.drawCenteredText(g2d, "RST", boardX + 47, boardY + 48, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
// Arduino Infinity Logo
drawArduinoLogo(g2d, boardX + 240, boardY + 80);
// Silkscreen text & branding
g2d.setColor(Color.WHITE);
g2d.setFont(SILK_FONT_LARGE);
StringUtils.drawCenteredText(g2d, "ARDUINO MEGA 2560", boardX + 380, boardY + 100, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
StringUtils.drawCenteredText(g2d, "ARDUINO", boardX + 288, boardY + 145, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
StringUtils.drawCenteredText(g2d, "MEGA 2560", boardX + 288, boardY + 165, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
// Header silkscreen labels
g2d.setFont(SILK_FONT_SMALL);
StringUtils.drawCenteredText(g2d, "POWER", boardX + 290, boardY + 375, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
StringUtils.drawCenteredText(g2d, "ANALOG IN", boardX + 540, boardY + 375, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
StringUtils.drawCenteredText(g2d, "DIGITAL", boardX + 350, boardY + 45, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
StringUtils.drawCenteredText(g2d, "COMMUNICATION", boardX + 620, boardY + 45, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
StringUtils.drawCenteredText(g2d, "POWER", boardX + 290, boardY + 370, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
StringUtils.drawCenteredText(g2d, "ANALOG IN", boardX + 630, boardY + 370, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
StringUtils.drawCenteredText(g2d, "DIGITAL (PWM ~)", boardX + 370, boardY + 55, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
StringUtils.drawCenteredText(g2d, "COMMUNICATION", boardX + 610, boardY + 55, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
}
g2d.setTransform(oldTx);
// Draw header pins with continuity tracking
drawPins(g2d, 0, controlPoints.length, true, outlineMode, drawingObserver);
g2d.setComposite(oldComposite);
@@ -243,19 +288,56 @@ public class ArduinoMega extends AbstractMakerBoard {
@Override
public void drawIcon(Graphics2D g2d, int width, int height) {
double boardX = 5;
double boardY = 3;
double boardW = width - boardX - 2;
double boardH = height - 6;
// USB Type-B Jack (metallic silver, top left protruding)
double usbW = 7;
double usbH = 8;
double usbX = 1;
double usbY = boardY + 2;
// DC Power Jack (dark body, bottom left protruding)
double dcW = 7;
double dcH = 6;
double dcX = 1;
double dcY = boardY + boardH - dcH - 3;
// Board PCB
g2d.setColor(ARDUINO_BLUE);
g2d.fill(new RoundRectangle2D.Double(2, 6, width - 4, height - 12, 4, 4));
g2d.fill(new RoundRectangle2D.Double(boardX, boardY, boardW, boardH, 4, 4));
g2d.setColor(ARDUINO_BLUE.darker());
g2d.draw(new RoundRectangle2D.Double(2, 6, width - 4, height - 12, 4, 4));
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1));
g2d.draw(new RoundRectangle2D.Double(boardX, boardY, boardW, boardH, 4, 4));
// Draw USB Jack
g2d.setColor(USB_METAL_COLOR);
g2d.fillRect(3, 8, 5, 5);
g2d.fill(new RoundRectangle2D.Double(usbX, usbY, usbW, usbH, 2, 2));
g2d.setColor(METAL_SHIELD_BORDER);
g2d.draw(new RoundRectangle2D.Double(usbX, usbY, usbW, usbH, 2, 2));
// Draw DC Jack
g2d.setColor(IC_BODY_COLOR);
g2d.fillRect(16, 11, 8, 8);
g2d.fill(new RoundRectangle2D.Double(dcX, dcY, dcW, dcH, 2, 2));
g2d.setColor(Color.BLACK);
g2d.draw(new RoundRectangle2D.Double(dcX, dcY, dcW, dcH, 2, 2));
// Arduino Infinity logo
double scale = 14.0 / 95.56;
double logoW = 95.56 * scale;
double logoH = 45.33 * scale;
double logoX = boardX + (boardW - logoW) / 2.0 + 1.0;
double logoY = boardY + 3.5;
drawArduinoLogo(g2d, logoX, logoY, scale);
// MEGA text below logo
g2d.setColor(Color.WHITE);
g2d.setFont(new Font("SansSerif", Font.BOLD, 6));
StringUtils.drawCenteredText(g2d, "MEGA", width / 2 + 3, height / 2 + 1, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
int fontSize = Math.max(6, (int) Math.round(boardH * 0.26));
g2d.setFont(new Font("SansSerif", Font.BOLD, fontSize));
double textY = logoY + logoH + (boardY + boardH - (logoY + logoH)) / 2.0;
double textX = boardX + (boardW / 2.0) + 0.5;
StringUtils.drawCenteredText(g2d, "MEGA", textX, textY, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
}
}
@@ -0,0 +1,152 @@
package org.diylc.components.micro;
import java.awt.Shape;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import org.junit.Assert;
import org.junit.Test;
public class ArduinoMegaTest {
@Test
public void testControlPointCountAndNames() {
ArduinoMega mega = new ArduinoMega();
Assert.assertEquals(98, mega.getControlPointCount());
for (int i = 0; i < mega.getControlPointCount(); i++) {
String name = mega.getControlPointNodeName(i);
Assert.assertNotNull("Pin " + i + " name should not be null", name);
Assert.assertFalse("Pin " + i + " name should not be empty", name.trim().isEmpty());
}
// Power header (0..7)
Assert.assertEquals("NC", mega.getControlPointNodeName(0));
Assert.assertEquals("IOREF", mega.getControlPointNodeName(1));
Assert.assertEquals("RESET", mega.getControlPointNodeName(2));
Assert.assertEquals("3.3V", mega.getControlPointNodeName(3));
Assert.assertEquals("5V", mega.getControlPointNodeName(4));
Assert.assertEquals("GND1", mega.getControlPointNodeName(5));
Assert.assertEquals("GND2", mega.getControlPointNodeName(6));
Assert.assertEquals("VIN", mega.getControlPointNodeName(7));
// Analog Low header (8..15)
Assert.assertEquals("A0", mega.getControlPointNodeName(8));
Assert.assertEquals("A7", mega.getControlPointNodeName(15));
// Analog High header (16..23)
Assert.assertEquals("A8", mega.getControlPointNodeName(16));
Assert.assertEquals("A15", mega.getControlPointNodeName(23));
// Digital low header (24..31)
Assert.assertEquals("D0 (RX0)", mega.getControlPointNodeName(24));
Assert.assertEquals("D7 (~)", mega.getControlPointNodeName(31));
// Digital high header (32..41)
Assert.assertEquals("D8 (~)", mega.getControlPointNodeName(32));
Assert.assertEquals("SCL", mega.getControlPointNodeName(41));
// Communication header (42..49)
Assert.assertEquals("D14 (TX3)", mega.getControlPointNodeName(42));
Assert.assertEquals("D21 (SCL)", mega.getControlPointNodeName(49));
// Double Digital 2x18 Header (50..85)
Assert.assertEquals("D22", mega.getControlPointNodeName(50));
Assert.assertEquals("D23", mega.getControlPointNodeName(51));
Assert.assertEquals("D52 (SCK)", mega.getControlPointNodeName(80));
Assert.assertEquals("D53 (SS)", mega.getControlPointNodeName(81));
Assert.assertEquals("GND_EXT1", mega.getControlPointNodeName(82));
Assert.assertEquals("GND_EXT2", mega.getControlPointNodeName(83));
Assert.assertEquals("5V_EXT1", mega.getControlPointNodeName(84));
Assert.assertEquals("5V_EXT2", mega.getControlPointNodeName(85));
// Main ICSP header (86..91, ATmega2560)
Assert.assertEquals("MISO", mega.getControlPointNodeName(86));
Assert.assertEquals("5V_ICSP", mega.getControlPointNodeName(87));
Assert.assertEquals("SCK", mega.getControlPointNodeName(88));
Assert.assertEquals("MOSI", mega.getControlPointNodeName(89));
Assert.assertEquals("RST_ICSP", mega.getControlPointNodeName(90));
Assert.assertEquals("GND_ICSP", mega.getControlPointNodeName(91));
// Top-left ICSP header (92..97, ATmega16U2)
Assert.assertEquals("MISO_16U2", mega.getControlPointNodeName(92));
Assert.assertEquals("5V_16U2", mega.getControlPointNodeName(93));
Assert.assertEquals("SCK_16U2", mega.getControlPointNodeName(94));
Assert.assertEquals("MOSI_16U2", mega.getControlPointNodeName(95));
Assert.assertEquals("RST_16U2", mega.getControlPointNodeName(96));
Assert.assertEquals("GND_16U2", mega.getControlPointNodeName(97));
}
@Test
public void testPinGeometryAndGaps() {
ArduinoMega mega = new ArduinoMega();
// Power header spacing (20px per pin)
for (int i = 0; i < 7; i++) {
Point2D p1 = mega.getControlPoint(i);
Point2D p2 = mega.getControlPoint(i + 1);
Assert.assertEquals(20.0, p1.distance(p2), 0.01);
Assert.assertEquals(p1.getY(), p2.getY(), 0.01);
}
// Gap between VIN (Pin 7) and A0 (Pin 8) is 0.2" = 40px
Point2D pVin = mega.getControlPoint(7);
Point2D pA0 = mega.getControlPoint(8);
Assert.assertEquals(40.0, pVin.distance(pA0), 0.01);
// Gap between A7 (Pin 15) and A8 (Pin 16) is 0.2" = 40px
Point2D pA7 = mega.getControlPoint(15);
Point2D pA8 = mega.getControlPoint(16);
Assert.assertEquals(40.0, pA7.distance(pA8), 0.01);
// D0 aligns with A5 (dx = 280 from NC)
Point2D pD0 = mega.getControlPoint(24);
Point2D pA5 = mega.getControlPoint(13);
Assert.assertEquals(pA5.getX(), pD0.getX(), 0.01);
// D7 aligns with VIN (dx = 140 from NC)
Point2D pD7 = mega.getControlPoint(31);
Assert.assertEquals(pVin.getX(), pD7.getX(), 0.01);
// Gap between D7 (Pin 31) and D8 (Pin 32) is 0.16" = 32px
Point2D pD8 = mega.getControlPoint(32);
Assert.assertEquals(32.0, pD7.distance(pD8), 0.01);
// D14 aligns with A7 (dx = 320 from NC)
Point2D pD14 = mega.getControlPoint(42);
Assert.assertEquals(pA7.getX(), pD14.getX(), 0.01);
// Gap between D0 (Pin 24) and D14 (Pin 42) is 0.2" = 40px
Assert.assertEquals(40.0, pD0.distance(pD14), 0.01);
// Double Digital 2x18 Header (Pin 50 inner, Pin 51 outer)
Point2D pNc = mega.getControlPoint(0);
Point2D pD22 = mega.getControlPoint(50);
Point2D pD23 = mega.getControlPoint(51);
Assert.assertEquals(20.0, pD22.distance(pD23), 0.01);
Assert.assertEquals(pD22.getY(), pD23.getY(), 0.01);
Assert.assertEquals(500.0, pD22.getX() - pNc.getX(), 0.01);
Assert.assertEquals(520.0, pD23.getX() - pNc.getX(), 0.01);
// Main ICSP header (Pin 86 MISO, Pin 87 5V_ICSP)
Point2D pMiso = mega.getControlPoint(86);
Point2D p5vIcsp = mega.getControlPoint(87);
Assert.assertEquals(20.0, pMiso.distance(p5vIcsp), 0.01);
// Top-left ICSP header (Pin 92 MISO_16U2, Pin 93 5V_16U2)
Point2D pMiso16 = mega.getControlPoint(92);
Point2D p5v16 = mega.getControlPoint(93);
Assert.assertEquals(20.0, pMiso16.distance(p5v16), 0.01);
}
@Test
public void testBodyShape() {
ArduinoMega mega = new ArduinoMega();
Shape body = mega.getBodyShape();
Assert.assertNotNull(body);
Rectangle2D bounds = body.getBounds2D();
Assert.assertEquals(799.78, bounds.getWidth(), 1.0);
Assert.assertEquals(419.78, bounds.getHeight(), 1.0);
}
}